Skip to content

refactor(ledger): correct settlement accounting#4236

Open
GAlexIHU wants to merge 1 commit intomainfrom
refactor/ledger-payment-settlement
Open

refactor(ledger): correct settlement accounting#4236
GAlexIHU wants to merge 1 commit intomainfrom
refactor/ledger-payment-settlement

Conversation

@GAlexIHU
Copy link
Copy Markdown
Contributor

@GAlexIHU GAlexIHU commented Apr 27, 2026

Overview

  • Authorization now only moves receivable from open to authorized.
  • Settlement now records the actual cash movement from wash to authorized receivable.
  • Adds new transaction templates for the corrected semantics:
    • AuthorizeCustomerReceivablePaymentTemplate
    • SettleCustomerReceivableFromPaymentTemplate
  • Keeps old transaction template names archived in legacy.go with their original implementations, so persisted annotations remain resolvable without reusing old names for new behavior.

Notes for reviewer

Summary by CodeRabbit

Release Notes

  • Improvements

    • Refactored payment authorization and settlement workflows for credit purchases, flat fees, and usage-based charges to improve ledger accuracy and account balance tracking.
    • Enhanced receivable status transitions during payment processing to provide more granular tracking of authorized and settled amounts.
  • Chores

    • Reorganized internal ledger transaction templates for better maintainability; legacy templates archived with backward compatibility preserved.
@GAlexIHU GAlexIHU added the release-note/misc Miscellaneous changes label Apr 27, 2026
@GAlexIHU GAlexIHU requested a review from a team as a code owner April 27, 2026 08:37
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 27, 2026

📝 Walkthrough

Walkthrough

This PR refactors the ledger system's customer receivable payment templates by renaming FundCustomerReceivableTemplate to SettleCustomerReceivableFromPaymentTemplate and SettleCustomerReceivablePaymentTemplate to AuthorizeCustomerReceivablePaymentTemplate. Legacy implementations are preserved in a new file, and all charge adapters (credit purchase, flat fee, usage-based) are updated to use the new template names with corresponding test assertions adjusted.

Changes

Cohort / File(s) Summary
Charge Adapters – Payment Handlers
openmeter/ledger/chargeadapter/creditpurchase.go, openmeter/ledger/chargeadapter/flatfee.go, openmeter/ledger/chargeadapter/usagebased.go
Swap template references: FundCustomerReceivableTemplateAuthorizeCustomerReceivablePaymentTemplate and SettleCustomerReceivablePaymentTemplateSettleCustomerReceivableFromPaymentTemplate during payment authorization and settlement.
Charge Adapter Tests
openmeter/ledger/chargeadapter/creditpurchase_test.go, openmeter/ledger/chargeadapter/flatfee_test.go, openmeter/ledger/chargeadapter/usagebased_test.go
Update ledger balance assertions to reflect new authorization/settlement model: receivable and wash balances zeroed after authorization, authorized receivable now carries negative sign instead of positive.
Transaction Template Definitions
openmeter/ledger/transactions/customer.go
Rename and re-scope two customer transaction templates with updated resolve logic for new authorization/settlement semantics between open and authorized receivable sub-accounts.
Legacy Transaction Templates
openmeter/ledger/transactions/legacy.go
New file introducing archived implementations of FundCustomerReceivableTemplate and SettleCustomerReceivablePaymentTemplate for backward compatibility and correction handling.
Transaction Template Tests
openmeter/ledger/transactions/customer_test.go, openmeter/ledger/transactions/correction_test.go
Update template-related test assertions and add new table-driven test for archived legacy template correction dispatch.
Test Infrastructure
openmeter/ledger/customerbalance/testenv_test.go, openmeter/ledger/transactions/testenv_test.go
Replace template names in test environment setup for customer receivable funding flows.
Routing Rules & Integration Tests
openmeter/ledger/routingrules/routingrules_test.go, test/credits/sanity_test.go
Rename test functions for clarity and adjust integration test ledger assertions for new authorization/settlement balance behavior across credit purchase, flat-fee, and usage-based flows.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related PRs

  • PR #4068: Modifies credit-purchase adapter and customer receivable templates; this PR's template refactoring directly affects those flows.
  • PR #4192: Affects promotional-credit settlement using the same receivable payment templates that are renamed in this PR.
  • PR #4179: Updates usage-based payment handlers and transaction templates that are refactored here.

Suggested labels

area/billing

Suggested reviewers

  • turip
  • tothandras
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main refactoring focus: correcting settlement accounting by changing how receivable and payment settlement transactions are handled across the ledger system.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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/ledger-payment-settlement

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.

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.

🧹 Nitpick comments (3)
openmeter/ledger/transactions/customer.go (1)

187-228: Tiny nit: error messages could specify which receivable bucket failed.

The authorizedReceivable lookup wraps with "failed to get authorized receivable sub-account" in AuthorizeCustomerReceivablePaymentTemplate (line 283), which is great — but the rec lookup here at line 199 (for the authorized receivable) and the open-receivable lookup at line 292 in AuthorizeCustomerReceivablePaymentTemplate both still use the generic "failed to get receivable sub-account". Aligning the wording would make production debugging a smidge nicer.

♻️ Suggested error message tweaks
@@ openmeter/ledger/transactions/customer.go
 	rec, err := customerAccounts.ReceivableAccount.GetSubAccountForRoute(ctx, ledger.CustomerReceivableRouteParams{
 		Currency:                       t.Currency,
 		CostBasis:                      t.CostBasis,
 		TransactionAuthorizationStatus: ledger.TransactionAuthorizationStatusAuthorized,
 	})
 	if err != nil {
-		return nil, fmt.Errorf("failed to get receivable sub-account: %w", err)
+		return nil, fmt.Errorf("failed to get authorized receivable sub-account: %w", err)
 	}
@@ openmeter/ledger/transactions/customer.go (AuthorizeCustomerReceivablePaymentTemplate.resolve)
 	openReceivable, err := customerAccounts.ReceivableAccount.GetSubAccountForRoute(ctx, ledger.CustomerReceivableRouteParams{
 		Currency:                       t.Currency,
 		CostBasis:                      t.CostBasis,
 		TransactionAuthorizationStatus: ledger.TransactionAuthorizationStatusOpen,
 	})
 	if err != nil {
-		return nil, fmt.Errorf("failed to get receivable sub-account: %w", err)
+		return nil, fmt.Errorf("failed to get open receivable sub-account: %w", err)
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/ledger/transactions/customer.go` around lines 187 - 228, The error
string for the receivable sub-account lookup in
SettleCustomerReceivableFromPaymentTemplate.resolve is too generic; update the
fmt.Errorf call that wraps the error from rec (the call to
customerAccounts.ReceivableAccount.GetSubAccountForRoute) to say "failed to get
authorized receivable sub-account: %w" (or equivalent) so it matches the wording
used by AuthorizeCustomerReceivablePaymentTemplate; also scan
AuthorizeCustomerReceivablePaymentTemplate (and its
authorizedReceivable/openReceivable lookups) and make the error messages
consistent (use "authorized receivable" for the authorized lookup and "open
receivable" for the open lookup) to aid debugging.
openmeter/ledger/transactions/legacy.go (2)

20-25: Optional: tag both types with // Deprecated: so IDEs/linters can nudge accidental new uses.

Since these are explicitly archived for annotation replay only, a // Deprecated: line in the doc comment plays nicely with staticcheck/IDE warnings and makes the archived status hard to miss.

♻️ Optional Deprecated marker
 // FundCustomerReceivableTemplate is an archived template name kept for
 // persisted ledger annotations. ...
+//
+// Deprecated: use AuthorizeCustomerReceivablePaymentTemplate (and
+// SettleCustomerReceivableFromPaymentTemplate for the cash-movement leg) in
+// new code; this type exists only to keep persisted annotations resolvable.
 type FundCustomerReceivableTemplate struct {
 // SettleCustomerReceivablePaymentTemplate is an archived template name kept for
 // persisted ledger annotations. ...
+//
+// Deprecated: use SettleCustomerReceivableFromPaymentTemplate in new code;
+// this type exists only to keep persisted annotations resolvable.
 type SettleCustomerReceivablePaymentTemplate struct {

Also applies to: 108-113

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/ledger/transactions/legacy.go` around lines 20 - 25, Add a
deprecation doc comment to the archived types so linters/IDEs warn on accidental
use: add a comment line starting with "// Deprecated:" above the type
declarations for FundCustomerReceivableTemplate and the other archived type at
lines ~108-113 (referencing their type names) indicating they are archived for
annotation replay only; ensure the comment sits immediately above the type
keyword so staticcheck/IDEs pick up the deprecation marker.

15-25: Tiny doc nit: the "should use X instead" pointers might trip up future readers.

The way these are wired:

  • FundCustomerReceivableTemplate was the authorize stage (in OnPaymentAuthorized); its lifecycle replacement is AuthorizeCustomerReceivablePaymentTemplate.
  • SettleCustomerReceivablePaymentTemplate was the settle stage (in OnPaymentSettled); its lifecycle replacement is SettleCustomerReceivableFromPaymentTemplate.

But the comments cross these — pointing Fund... at SettleFrom... and SettlePayment... at Authorize... — which seems to map by "behavioral similarity" (Fund did wash→authorized, like SettleFrom now does) rather than by adapter call-site replacement. Worse, SettlePayment.resolve does authorized→open while the new Authorize does open→authorized (literally inverse), so anyone migrating an old call site by following this pointer would book the wrong direction.

If the intent is "what to use in new code at the same lifecycle position," I'd suggest swapping the targets and noting the behavior shift. Something like:

📝 Suggested doc tweak
-// FundCustomerReceivableTemplate is an archived template name kept for
-// persisted ledger annotations. New payment flows should use
-// SettleCustomerReceivableFromPaymentTemplate.
-//
-// Original semantics: funds the authorized receivable sub-account from wash.
+// FundCustomerReceivableTemplate is an archived template name kept for
+// persisted ledger annotations. New payment-authorized flows should use
+// AuthorizeCustomerReceivablePaymentTemplate; the wash→authorized cash
+// movement that used to happen here has been deferred to
+// SettleCustomerReceivableFromPaymentTemplate.
+//
+// Original semantics: funds the authorized receivable sub-account from wash.
-// SettleCustomerReceivablePaymentTemplate is an archived template name kept for
-// persisted ledger annotations. New payment flows should use
-// AuthorizeCustomerReceivablePaymentTemplate.
-//
-// Original semantics: moves authorized receivable staging into the open
-// receivable route.
+// SettleCustomerReceivablePaymentTemplate is an archived template name kept
+// for persisted ledger annotations. New payment-settled flows should use
+// SettleCustomerReceivableFromPaymentTemplate, which now performs the actual
+// wash→authorized cash movement instead of the legacy authorized→open shuffle.
+//
+// Original semantics: moves authorized receivable staging into the open
+// receivable route.

Also applies to: 102-113

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/ledger/transactions/legacy.go` around lines 15 - 25, The doc
comment on FundCustomerReceivableTemplate incorrectly points to
SettleCustomerReceivableFromPaymentTemplate; update the pointer targets so the
lifecycle replacements map by call-site lifecycle: point
FundCustomerReceivableTemplate (authorize-stage) to
AuthorizeCustomerReceivablePaymentTemplate and point
SettleCustomerReceivablePaymentTemplate (settle-stage) to
SettleCustomerReceivableFromPaymentTemplate; also add a short note near each
struct (FundCustomerReceivableTemplate and
SettleCustomerReceivablePaymentTemplate) explaining that the new templates have
different directional behavior (e.g., authorize→open vs. open→authorized) so
callers should verify semantics when migrating from OnPaymentAuthorized /
OnPaymentSettled and from SettlePayment.resolve.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@openmeter/ledger/transactions/customer.go`:
- Around line 187-228: The error string for the receivable sub-account lookup in
SettleCustomerReceivableFromPaymentTemplate.resolve is too generic; update the
fmt.Errorf call that wraps the error from rec (the call to
customerAccounts.ReceivableAccount.GetSubAccountForRoute) to say "failed to get
authorized receivable sub-account: %w" (or equivalent) so it matches the wording
used by AuthorizeCustomerReceivablePaymentTemplate; also scan
AuthorizeCustomerReceivablePaymentTemplate (and its
authorizedReceivable/openReceivable lookups) and make the error messages
consistent (use "authorized receivable" for the authorized lookup and "open
receivable" for the open lookup) to aid debugging.

In `@openmeter/ledger/transactions/legacy.go`:
- Around line 20-25: Add a deprecation doc comment to the archived types so
linters/IDEs warn on accidental use: add a comment line starting with "//
Deprecated:" above the type declarations for FundCustomerReceivableTemplate and
the other archived type at lines ~108-113 (referencing their type names)
indicating they are archived for annotation replay only; ensure the comment sits
immediately above the type keyword so staticcheck/IDEs pick up the deprecation
marker.
- Around line 15-25: The doc comment on FundCustomerReceivableTemplate
incorrectly points to SettleCustomerReceivableFromPaymentTemplate; update the
pointer targets so the lifecycle replacements map by call-site lifecycle: point
FundCustomerReceivableTemplate (authorize-stage) to
AuthorizeCustomerReceivablePaymentTemplate and point
SettleCustomerReceivablePaymentTemplate (settle-stage) to
SettleCustomerReceivableFromPaymentTemplate; also add a short note near each
struct (FundCustomerReceivableTemplate and
SettleCustomerReceivablePaymentTemplate) explaining that the new templates have
different directional behavior (e.g., authorize→open vs. open→authorized) so
callers should verify semantics when migrating from OnPaymentAuthorized /
OnPaymentSettled and from SettlePayment.resolve.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 38622c2a-0a9e-4340-bad0-1ed8c831afe2

📥 Commits

Reviewing files that changed from the base of the PR and between acf60a9 and 7854d0d.

📒 Files selected for processing (15)
  • openmeter/ledger/chargeadapter/creditpurchase.go
  • openmeter/ledger/chargeadapter/creditpurchase_test.go
  • openmeter/ledger/chargeadapter/flatfee.go
  • openmeter/ledger/chargeadapter/flatfee_test.go
  • openmeter/ledger/chargeadapter/usagebased.go
  • openmeter/ledger/chargeadapter/usagebased_test.go
  • openmeter/ledger/customerbalance/testenv_test.go
  • openmeter/ledger/routingrules/routingrules_test.go
  • openmeter/ledger/transactions/correction.go
  • openmeter/ledger/transactions/correction_test.go
  • openmeter/ledger/transactions/customer.go
  • openmeter/ledger/transactions/customer_test.go
  • openmeter/ledger/transactions/legacy.go
  • openmeter/ledger/transactions/testenv_test.go
  • test/credits/sanity_test.go
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-note/misc Miscellaneous changes

2 participants