feat: round currencies to smallest denominator#4063
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
✅ Files skipped from review due to trivial changes (2)
🚧 Files skipped from review as they are similar to previous changes (3)
📝 WalkthroughWalkthroughThis PR adds currency normalization (rounding) throughout the billing charges domain. It introduces Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
openmeter/billing/charges/flatfee/service/invoice.go (1)
37-42:⚠️ Potential issue | 🟠 MajorRe-check the rounded total before persisting these allocations.
Unlike
openmeter/billing/charges/flatfee/service/creditsonly.go, this path normalizes the handler output and then stores it without verifying that the rounded allocations still add up toPreTaxTotalAmount. With the new per-row rounding, a one-cent drift can now get persisted silently.💡 Suggested guard
creditAllocations = creditAllocations.NormalizeWith(currencyCalculator) + if !creditAllocations.Sum().Equal(input.PreTaxTotalAmount) { + return nil, fmt.Errorf( + "credit allocations do not match total [charge_id=%s, total=%s, allocations_sum=%s]", + charge.ID, input.PreTaxTotalAmount.String(), creditAllocations.Sum().String(), + ) + } if len(creditAllocations) == 0 { return nil, nil }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@openmeter/billing/charges/flatfee/service/invoice.go` around lines 37 - 42, After calling s.handler.OnAssignedToInvoice and normalizing via creditAllocations.NormalizeWith(currencyCalculator), re-calc the rounded sum of creditAllocations (using the same rounding/currency rules) and compare it to the invoice PreTaxTotalAmount before persisting; if there is a cent-level drift, adjust (e.g., distribute the rounding remainder or return an error) so the persisted allocations exactly equal PreTaxTotalAmount. Ensure you reference the handler method OnAssignedToInvoice, the variable creditAllocations, the NormalizeWith(currencyCalculator) call, and the invoice PreTaxTotalAmount when adding this guard.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@openmeter/billing/charges/models/creditrealization/correction_test.go`:
- Around line 317-329: Add a new subtest in correction_test.go that verifies
tiny negatives that round to zero are treated as a no-op: use
newAllocationBuilder() to build the allocation, call
Realizations{alloc}.CreateCorrectionRequest with
alpacadecimal.NewFromFloat(-0.004) and testCurrency(t), then require no error
and assert the returned slice is length 0 (no corrections). Place it alongside
the existing "amount is rounded to currency precision before planning" test to
lock in the "zero after rounding" contract.
In `@openmeter/billing/charges/models/creditrealization/realizations.go`:
- Around line 53-60: The code rounds the aggregate correction target but
compares it against unrounded stored realization balances, causing
ErrInsufficientFunds for pre-PR rows with sub-cent values; update CorrectAll
(and any logic that uses RemainingAmount) to normalize stored realization
balances by applying currency.RoundToPrecision to each per-allocation
RemainingAmount before aggregation and before any comparison, or alternatively
perform per-allocation rounding when building the aggregate target so that
comparisons use consistently rounded values; adjust any places that read
RemainingAmount to use the rounded value so the CorrectionRequest/CorrectAll
flow uses normalized balances.
---
Outside diff comments:
In `@openmeter/billing/charges/flatfee/service/invoice.go`:
- Around line 37-42: After calling s.handler.OnAssignedToInvoice and normalizing
via creditAllocations.NormalizeWith(currencyCalculator), re-calc the rounded sum
of creditAllocations (using the same rounding/currency rules) and compare it to
the invoice PreTaxTotalAmount before persisting; if there is a cent-level drift,
adjust (e.g., distribute the rounding remainder or return an error) so the
persisted allocations exactly equal PreTaxTotalAmount. Ensure you reference the
handler method OnAssignedToInvoice, the variable creditAllocations, the
NormalizeWith(currencyCalculator) call, and the invoice PreTaxTotalAmount when
adding this guard.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 22a43b5a-155b-4886-9717-d3a7e56a9e46
📒 Files selected for processing (12)
.agents/skills/charges/SKILL.mdopenmeter/billing/charges/creditpurchase/chargecreditpurchase.goopenmeter/billing/charges/creditpurchase/service/create.goopenmeter/billing/charges/flatfee/charge.goopenmeter/billing/charges/flatfee/service/creditsonly.goopenmeter/billing/charges/flatfee/service/invoice.goopenmeter/billing/charges/models/creditrealization/allocation.goopenmeter/billing/charges/models/creditrealization/correction.goopenmeter/billing/charges/models/creditrealization/correction_test.goopenmeter/billing/charges/models/creditrealization/models.goopenmeter/billing/charges/models/creditrealization/realizations.goopenmeter/billing/charges/usagebased/service/creditsonly.go
Summary
This change makes currency normalization a charge-domain responsibility across the charge lifecycle.
It ensures that:
What Changed
AmountAfterProrationis persisted as already-normalized state.creditrealizationCorrect/CorrectAllhelpers.Summary by CodeRabbit
Release Notes
New Features
Bug Fixes
Documentation