Skip to content

fix(ledger): select flat-fee credits at booking time - #5220

Draft
GAlexIHU wants to merge 2 commits into
mainfrom
codex/fix-flatfee-credit-booking-time
Draft

GAlexIHU wants to merge 2 commits into
mainfrom
codex/fix-flatfee-credit-booking-time

Conversation

@GAlexIHU

@GAlexIHU GAlexIHU commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Overview

In-advance flat fees added mid-period could ignore credits granted after the billing-period start, leaving the credit balance untouched and invoicing the full converted amount. Collect credits at the charge's booking time instead of its invoice scheduling time.

Use a single BookedAt input for both credit eligibility and ledger posting in the accrued and receivable collectors. Remove SourceBalanceAsOf and update callers, fixtures, and domain documentation.

Validation

  • Reproduced the failure before the fix with custom-currency flat fees.
  • Added regressions for full credit coverage, partial coverage, and excluding grants effective after service start during delayed collection.
  • PostgreSQL-backed collector, charge-adapter, and credits suites passed.
  • Scoped Go lint passed with zero issues.

Summary by CodeRabbit

  • Bug Fixes
    • Credit collection now checks eligibility and expiry at the effective booking time, preventing delayed collections from using credits granted afterward.
    • Flat-fee charges use the service-period start to determine eligible credits, so invoice scheduling time no longer changes which credits can cover the fee.
    • Any amount not covered by eligible credits continues to be invoiced in fiat.

RetriggerConfidence Score: 4/5

The PR is not safe to merge until delayed flat-fee collection cannot spend credit already consumed by another charge.

Fix All in Claude CodeFindings

  1. P1 Backdated collection spends credit twice ▶
Fix with agent prompt
### Issue 1
openmeter/ledger/collector/collect.go:226
When an in-advance flat fee is collected late, its booking time is the earlier service start. Using that time to select credits hides any charge booked afterward, even if that charge has already spent the same credit. The flat fee can then spend it again: the ledger does not check that the resulting balance remains sufficient, so the customer’s credit balance can become negative.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

The PR makes BookedAt the sole timestamp for credit selection and ledger posting, removes SourceBalanceAsOf, and adds flat-fee credit-allocation regressions.

  • Mid-period flat fees can now use credits available at service start rather than at their invoice scheduling time.
  • Backdated selection also needs protection against credit spent between service start and collection.
Diagram
sequenceDiagram
  participant G as Credit grant
  participant O as Other charge
  participant F as Delayed flat-fee collection
  participant L as Ledger
  G->>L: Grant credit before service start T1
  O->>L: Spend credit at T2
  F->>L: Query FBO balance as of T1
  L-->>F: Balance excludes T2 spend
  F->>L: Post another spend at T1
Loading

Reviews (1) · Last reviewed commit: "test(credits): keep booking-time regress..."

@GAlexIHU
GAlexIHU requested a review from a team as a code owner September 28, 2026 15:45
@GAlexIHU GAlexIHU added the release-note/bug-fix Release note: Bug Fixes label Sep 28, 2026
@coderabbitai

coderabbitai Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: openmeterio/openmeter/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: ff160c83-f5f4-4c71-8e53-6f1b89ffbd70

📥 Commits

Reviewing files that changed from the base of the PR and between 3304601 and f8df131.

📒 Files selected for processing (10)
  • openmeter/ledger/chargeadapter/flatfee.go
  • openmeter/ledger/chargeadapter/flatfee_test.go
  • openmeter/ledger/chargeadapter/origin_test.go
  • openmeter/ledger/chargeadapter/usagebased.go
  • openmeter/ledger/collector/README.md
  • openmeter/ledger/collector/collect.go
  • openmeter/ledger/collector/collection_fbo_test.go
  • openmeter/ledger/collector/correct_test.go
  • openmeter/ledger/collector/service.go
  • test/credits/customcurrency_test.go

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

Changes

The collection API removes SourceBalanceAsOf. BookedAt now controls credit eligibility and ledger posting time. Charge adapters and tests stop supplying the removed timestamp. Custom-currency tests verify service-start eligibility.

Collection timing contract

Layer / File(s) Summary
Collector contract and cutoff
openmeter/ledger/collector/service.go, openmeter/ledger/collector/collect.go, openmeter/ledger/collector/README.md
Collection inputs use BookedAt for eligibility checks and ledger postings. FBO selection uses BookedAt.
Charge adapter collection inputs
openmeter/ledger/chargeadapter/*.go, openmeter/ledger/chargeadapter/*_test.go
Flat-fee and usage-based adapters stop passing SourceBalanceAsOf. Related fixtures use forward service periods and earlier invoice timing.
Collection validation and eligibility tests
openmeter/ledger/collector/*_test.go, test/credits/customcurrency_test.go
Existing tests omit SourceBalanceAsOf. New custom-currency cases verify that only credits available at service start fund the fee.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~15 minutes

Change: Bug fix

Suggested reviewers: mark-vass-konghq, turip

Merge Risk: ⚪ Minimal · up to f8df1

The booking-time credit selection change is mergeable after normal checks; no concrete remaining issue is established.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to f8df1

The change affects financial balances and invoice amounts. The normal billing flow supplies a service-period-derived booking time, and no introduced security issue was established. Direct-caller access and retry behavior remain unverified.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The direct balance-selection effect is confined to the supplied customer's namespace and FBO account, requested currency and route, positive eligible source balances, and requested collection amount. This does not establish who may invoke the handlers.

Trust Boundaries and Controls

  • observed — The adapter forwards BookedAt from its input without imposing a service-period bound. The observed billing reconciliation caller derives that time from the payment term and service period; externally controlled access to the handler was not established.

Resilience and Maintainability Implications

  • observed — The collector checks credit availability under a customer-account posting lock and returns errors from ledger-group commit or breakage-record persistence through its transaction boundary. Grant-side lock participation and ambiguous-commit retry protection remain unverified.
🚥 Pre-merge checks | ✅ 4
✅ 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 clearly and concisely describes the main change: selecting flat-fee credits at booking time.
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.
✨ Finishing Touches 💡 1
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR

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.

@GAlexIHU
GAlexIHU enabled auto-merge (squash) September 28, 2026 15:46
}

selections, err := c.collectCustomerFBOSelections(ctx, c.customerID(input), input.Currency, ledger.Route{Filters: input.Filters}, amount, input.SourceBalanceAsOf)
selections, err := c.collectCustomerFBOSelections(ctx, c.customerID(input), input.Currency, ledger.Route{Filters: input.Filters}, amount, input.BookedAt)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Backdated collection spends credit twice

When an in-advance flat fee is collected late, its booking time is the earlier service start. Using that time to select credits hides any charge booked afterward, even if that charge has already spent the same credit. The flat fee can then spend it again: the ledger does not check that the resulting balance remains sufficient, so the customer’s credit balance can become negative.

Knowledge Base Used: Credit and ledger accounting

Prompt To Fix With AI
This is a comment left during a code review.
Path: openmeter/ledger/collector/collect.go
Line: 226

Comment:
**Backdated collection spends credit twice**

When an in-advance flat fee is collected late, its booking time is the earlier service start. Using that time to select credits hides any charge booked afterward, even if that charge has already spent the same credit. The flat fee can then spend it again: the ledger does not check that the resulting balance remains sufficient, so the customer’s credit balance can become negative.

**Knowledge Base Used:** [Credit and ledger accounting](https://app.greptile.com/openmeter/-/custom-context/knowledge-base/openmeterio/openmeter/-/docs/credit-and-ledger-accounting.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-note/bug-fix Release note: Bug Fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant