Skip to content

fix: adjust flat-fee shape to match usage-based#4128

Merged
turip merged 4 commits intomainfrom
refactor/flat-fee-shape
Apr 10, 2026
Merged

fix: adjust flat-fee shape to match usage-based#4128
turip merged 4 commits intomainfrom
refactor/flat-fee-shape

Conversation

@turip
Copy link
Copy Markdown
Member

@turip turip commented Apr 10, 2026

Overview

Unify external data structure format between usage-based and flat fee charges.

Notes for reviewer

Summary by CodeRabbit

  • New Features

    • Flat-fee charges now persist a dedicated, more detailed status and surface realized data (payments, credits, accrued usage) separately so readers see committed realizations explicitly.
  • Refactor

    • Charge representation split into a durable base row and separate realization data, and services now update base vs. realization data independently to avoid redundant writes and clarify lifecycle behavior.

@turip turip requested a review from a team as a code owner April 10, 2026 14:05
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 10, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7ee797a8-8fb9-4b6d-8fdb-d99b0df4226e

📥 Commits

Reviewing files that changed from the base of the PR and between c478221 and c432df1.

📒 Files selected for processing (1)
  • openmeter/billing/worker/subscriptionsync/service/creditsonly_test.go

📝 Walkthrough

Walkthrough

Separated durable flat-fee base-row fields into ChargeBase and moved realization-side data into Realizations. Decomposed flat-fee adapter into focused interfaces and changed UpdateCharge to accept/return ChargeBase. Added persisted status_detailed column and updated services, ledger, and tests to read/write realizations via Realizations.* and use flatfee.Status*.

Changes

Cohort / File(s) Summary
Schema & Migrations
openmeter/ent/schema/chargesflatfee.go, tools/migrate/migrations/20260410132752_flat_fee_status_detailed.up.sql, tools/migrate/migrations/20260410132752_flat_fee_status_detailed.down.sql
Add status_detailed enum column to charge_flat_fees, backfill from status, make NOT NULL, and provide rollback.
Charge Model Restructuring
openmeter/billing/charges/flatfee/charge.go
Introduce ChargeBase (durable base + Status) and Realizations (credit realizations, accrued usage, payment); remove realization fields from State; split validation between base and realizations.
Adapter Interface Refactor
openmeter/billing/charges/flatfee/adapter.go
Decompose Adapter into ChargeAdapter, ChargeCreditAllocationAdapter, ChargeInvoicedUsageAdapter, ChargePaymentAdapter; UpdateCharge now takes/returns ChargeBase; IntentWithInitialStatus.InitialStatus uses local Status.
Adapter Impl & Mapping
openmeter/billing/charges/flatfee/adapter/charge.go, openmeter/billing/charges/flatfee/adapter/mapper.go
UpdateCharge updated to operate on ChargeBase and return mapped base; DB writes set status_detailed; added MapChargeBaseFromDB; when meta.ExpandRealizations set, load realizations into charge.Realizations.
Flat-fee Services
openmeter/billing/charges/flatfee/service/*.go
Services updated to check/update realization data on charge.Realizations.*, use flatfee.Status* constants, and call UpdateCharge(ctx, charge.ChargeBase) then replace returned base.
Usage-based API Cleanup
openmeter/billing/charges/usagebased/adapter.go, .../adapter/charge.go, .../statemachine.go
Removed UpdateStatus API/type from usage-based ChargeAdapter and dropped UpdateStatus implementation and related types.
Ledger & Balance
openmeter/ledger/chargeadapter/flatfee.go, openmeter/ledger/customerbalance/calculation.go
Consume accrued usage and credit realizations from charge.Realizations.* instead of State.
Tests & Helpers
various under openmeter/billing/charges/..., openmeter/ledger/..., test/credits/*
Test helpers and assertions updated to construct/read ChargeBase and Realizations; insert helpers now set status_detailed; assertions switched to flatfee.Status*.
Docs
.agents/skills/charges/SKILL.md
Documented ChargeBase/Realizations model and revised adapter guidance (when to call UpdateCharge vs. realization writers).

Sequence Diagram(s)

sequenceDiagram
  participant Service as Service / StateMachine
  participant Adapter as flatfee.Adapter
  participant DB as Database (ent)
  participant Realizers as Realization writers
  Service->>Adapter: UpdateCharge(ctx, ChargeBase)
  Adapter->>DB: Persist base-row (chargemeta Create/Update)\nSet `status_detailed`
  DB-->>Adapter: Saved ChargeFlatFee entity
  Adapter-->>Service: return ChargeBase (mapped)
  alt Create or update realizations
    Service->>Realizers: CreateCreditAllocations / CreatePayment / CreateInvoicedUsage
    Realizers->>DB: Persist realization rows (credit, payment, accrued usage)
    DB-->>Realizers: persisted realization records
    Realizers-->>Service: realizations persisted
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

area/billing

Suggested reviewers

  • tothandras
  • GAlexIHU
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main objective: unifying the flat-fee charge data structure to match the usage-based format, which is the primary change across all modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/flat-fee-shape

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@turip turip added the release-note/misc Miscellaneous changes label Apr 10, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
test/credits/sanity_test.go (1)

948-959: ⚠️ Potential issue | 🟡 Minor

Use flatfee.StatusFinal consistently in this subtest.

This block still mixes the old and new status types: Line 951 compares advancedFlatFee.Status to meta.ChargeStatusFinal, while Lines 958-959 already use flatfee.StatusFinal. With suite.Equal, that stale assertion can fail on type mismatch even when the string value matches.

Suggested fix
-		s.Equal(meta.ChargeStatusFinal, advancedFlatFee.Status)
+		s.Equal(flatfee.StatusFinal, advancedFlatFee.Status)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/credits/sanity_test.go` around lines 948 - 959, The assertion mixes
status enum types: replace the use of meta.ChargeStatusFinal in the comparison
with advancedFlatFee.Status by using flatfee.StatusFinal so both checks use the
same status type; update the assertion that currently reads
s.Equal(meta.ChargeStatusFinal, advancedFlatFee.Status) to compare
flatfee.StatusFinal with advancedFlatFee.Status (keeping the later check against
updatedFlatFeeCharge unchanged).
openmeter/billing/charges/flatfee/charge.go (1)

181-203: ⚠️ Potential issue | 🟠 Major

Re-add validation for AmountAfterProration.

State.Validate() no longer guards this field, so ChargeBase.Validate() will now allow UpdateCharge() to persist a negative AmountAfterProration. That turns an internal bug into durable bad billing state.

Suggested fix
 func (s State) Validate() error {
 	var errs []error
 
 	if s.AdvanceAfter != nil {
 		if s.AdvanceAfter.IsZero() {
 			errs = append(errs, fmt.Errorf("advance after is required"))
 		}
 	}
+
+	if s.AmountAfterProration.IsNegative() {
+		errs = append(errs, fmt.Errorf("amount after proration cannot be negative"))
+	}
 
 	return models.NewNillableGenericValidationError(errors.Join(errs...))
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/billing/charges/flatfee/charge.go` around lines 181 - 203,
State.Validate currently omits checks for AmountAfterProration, allowing
negative values to be persisted; update State.Validate to validate the
AmountAfterProration field (on the State struct) by ensuring it is present/valid
and non-negative (e.g., append an error when AmountAfterProration < 0 or is
otherwise invalid) so ChargeBase.Validate/UpdateCharge cannot persist a negative
amount; add a clear error message like "amountAfterProration must be
non-negative" to the errs slice before returning via
models.NewNillableGenericValidationError.
openmeter/billing/charges/flatfee/adapter.go (1)

55-71: ⚠️ Potential issue | 🟠 Major

InitialStatus is effectively required now.

Validate() still allows the zero value, but the create path now calls InitialStatus.ToMetaChargeStatus() unconditionally in openmeter/billing/charges/flatfee/adapter/charge.go. So invalid input gets through validation and only fails later while building the DB row.

Suggested fix
 	// Initial status is optional, but if it is set, it must be valid
-	if i.InitialStatus != "" {
-		if err := i.InitialStatus.Validate(); err != nil {
-			errs = append(errs, fmt.Errorf("initial status: %w", err))
-		}
-	}
+	if i.InitialStatus == "" {
+		errs = append(errs, fmt.Errorf("initial status is required"))
+	} else if err := i.InitialStatus.Validate(); err != nil {
+		errs = append(errs, fmt.Errorf("initial status: %w", err))
+	}
 	return models.NewNillableGenericValidationError(errors.Join(errs...))
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/billing/charges/flatfee/adapter.go` around lines 55 - 71,
IntentWithInitialStatus.Validate currently treats InitialStatus as optional but
Create path calls InitialStatus.ToMetaChargeStatus unconditionally (see
ToMetaChargeStatus usage in charge building), so invalid/zero InitialStatus
slips through; update Validate in IntentWithInitialStatus.Validate to require a
non-empty, valid InitialStatus by adding a check that InitialStatus != "" and
returning a wrapped error (e.g., "initial status: required") if empty, and keep
the existing Validate() call to ensure it's well-formed; adjust error returned
via models.NewNillableGenericValidationError as needed so Create-time callers no
longer receive invalid InitialStatus.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@openmeter/billing/charges/flatfee/adapter.go`:
- Around line 55-71: IntentWithInitialStatus.Validate currently treats
InitialStatus as optional but Create path calls InitialStatus.ToMetaChargeStatus
unconditionally (see ToMetaChargeStatus usage in charge building), so
invalid/zero InitialStatus slips through; update Validate in
IntentWithInitialStatus.Validate to require a non-empty, valid InitialStatus by
adding a check that InitialStatus != "" and returning a wrapped error (e.g.,
"initial status: required") if empty, and keep the existing Validate() call to
ensure it's well-formed; adjust error returned via
models.NewNillableGenericValidationError as needed so Create-time callers no
longer receive invalid InitialStatus.

In `@openmeter/billing/charges/flatfee/charge.go`:
- Around line 181-203: State.Validate currently omits checks for
AmountAfterProration, allowing negative values to be persisted; update
State.Validate to validate the AmountAfterProration field (on the State struct)
by ensuring it is present/valid and non-negative (e.g., append an error when
AmountAfterProration < 0 or is otherwise invalid) so
ChargeBase.Validate/UpdateCharge cannot persist a negative amount; add a clear
error message like "amountAfterProration must be non-negative" to the errs slice
before returning via models.NewNillableGenericValidationError.

In `@test/credits/sanity_test.go`:
- Around line 948-959: The assertion mixes status enum types: replace the use of
meta.ChargeStatusFinal in the comparison with advancedFlatFee.Status by using
flatfee.StatusFinal so both checks use the same status type; update the
assertion that currently reads s.Equal(meta.ChargeStatusFinal,
advancedFlatFee.Status) to compare flatfee.StatusFinal with
advancedFlatFee.Status (keeping the later check against updatedFlatFeeCharge
unchanged).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f85b65ef-c9c5-4f90-a106-995853e6768e

📥 Commits

Reviewing files that changed from the base of the PR and between 5560d74 and 45a98c3.

⛔ Files ignored due to path filters (8)
  • openmeter/ent/db/chargeflatfee.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargeflatfee/chargeflatfee.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargeflatfee/where.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargeflatfee_create.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargeflatfee_update.go is excluded by !**/ent/db/**
  • openmeter/ent/db/migrate/schema.go is excluded by !**/ent/db/**
  • openmeter/ent/db/mutation.go is excluded by !**/ent/db/**
  • tools/migrate/migrations/atlas.sum is excluded by !**/*.sum, !**/*.sum
📒 Files selected for processing (24)
  • .agents/skills/charges/SKILL.md
  • openmeter/billing/charges/adapter/search_test.go
  • openmeter/billing/charges/flatfee/adapter.go
  • openmeter/billing/charges/flatfee/adapter/charge.go
  • openmeter/billing/charges/flatfee/adapter/mapper.go
  • openmeter/billing/charges/flatfee/charge.go
  • openmeter/billing/charges/flatfee/service/create.go
  • openmeter/billing/charges/flatfee/service/creditsonly.go
  • openmeter/billing/charges/flatfee/service/invoice.go
  • openmeter/billing/charges/flatfee/service/payment.go
  • openmeter/billing/charges/flatfee/statemachine.go
  • openmeter/billing/charges/service/invoicable_test.go
  • openmeter/billing/charges/service/lineage_test.go
  • openmeter/billing/charges/service/truncation_test.go
  • openmeter/billing/charges/usagebased/adapter.go
  • openmeter/billing/charges/usagebased/adapter/charge.go
  • openmeter/billing/charges/usagebased/statemachine.go
  • openmeter/ent/schema/chargesflatfee.go
  • openmeter/ledger/chargeadapter/flatfee.go
  • openmeter/ledger/chargeadapter/flatfee_test.go
  • openmeter/ledger/customerbalance/calculation.go
  • test/credits/sanity_test.go
  • tools/migrate/migrations/20260410132752_flat_fee_status_detailed.down.sql
  • tools/migrate/migrations/20260410132752_flat_fee_status_detailed.up.sql
💤 Files with no reviewable changes (3)
  • openmeter/billing/charges/usagebased/adapter.go
  • openmeter/billing/charges/usagebased/adapter/charge.go
  • openmeter/billing/charges/usagebased/statemachine.go

GAlexIHU
GAlexIHU previously approved these changes Apr 10, 2026
Copy link
Copy Markdown
Contributor

@GAlexIHU GAlexIHU left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thx for doing this!

@turip turip merged commit 8975a97 into main Apr 10, 2026
23 checks passed
@turip turip deleted the refactor/flat-fee-shape branch April 10, 2026 14:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-note/misc Miscellaneous changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants