Repository navigation
Keep every credit grant as a lot and draw debits oldest-first - #107
Merged
Merged
Conversation
Each top-up, automatic top-up and admin credit opens a lot with its currency, amount paid and USD per unit. The credit entry trigger draws debits from lots oldest-first, one movement per lot, under the account lock; refunds draw their own purchase's lot first, give-backs return to debt, the reversed lot and then the newest lot, and debt beyond the lots is paid by the next grant. Lots and balance must agree after every entry. Existing accounts are backfilled as rate-1 USD lots. The balance is valued in the region currency at each lot's own rate, with USD lots at a rate the region captures once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
|
The 4 Maxis timeouts reproduce identically on main (a369af4), in the same tunnelled environment: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Every grant of credits is now a lot that keeps what was paid for it. Debits draw lots oldest-first, so a balance can be valued in the currency it was bought in.
billing_credit_lots,billing_credit_lot_movements, migration20261012210000_credit_lots): each top-up, automatic top-up and admin credit opens a lot. A lot records its currency, the amount paid, the USD per unit its credits were granted at (the checkout's stored rate; 1 for USD) and what is left.billing_credit_entries_apply_lotstrigger,billing_credit_lots_apply_entry): lots change only inside the entry insert, under the account row lock the balance trigger already holds.billing_credit_lots_backfill): every existing account gets lots.billing-credit-lot-value.service.ts):readCreditBalanceValuevalues the available balance in the organisation's region currency.billing_organisation_regions.usd_per_unit, Stripe's live rate; 1 for US). A region that could not read a rate captures it on its first valuation. This is the only update a region row accepts.creditMovementValuevalues one debit from the lots it drew.Docs/Requirements/billing-tariffs.md(Credit lots).Unchanged: balances, every existing debit and credit path, and the entry rules. The lot trigger only adds rows next to them.
Testing
On Maxis (Windows) at 9f6d83e (the same change before the rebase onto main):
build, API/Admin/protocoltypecheckandlint, API unit tests (all files), protocol tests: all exit 0.billing-credit-lot-value.service.test.ts. It covers:billing-credit-lots.persistence.test.ts(6 tests). It covers:billing-*PostgreSQL files, run serially through the Fedora Postgres tunnel: 53 files and 206 tests pass.billing-prepaid-reservation(30 s) andbilling-stripe-corrections(40 s).Not verified: valuation shown to a product (the display PR) and refunds per lot (the refunds PR).
🤖 Generated with Claude Code