Skip to content

fix(billing): enforce currency match on wallet credit/debit#1325

Open
marcelo-maciel wants to merge 1 commit into
fullstackhero:mainfrom
marcelo-maciel:fix/billing-wallet-currency-match
Open

fix(billing): enforce currency match on wallet credit/debit#1325
marcelo-maciel wants to merge 1 commit into
fullstackhero:mainfrom
marcelo-maciel:fix/billing-wallet-currency-match

Conversation

@marcelo-maciel

Copy link
Copy Markdown
Contributor

Problem

Wallet.Credit and Wallet.Debit took a raw decimal and stamped the wallet's own currency onto the resulting Money. BillingService.MarkInvoicePaidAsync credited a top-up by passing invoice.SubtotalAmount.Amount, dropping invoice.Currency on the way in.

The effect: a top-up invoice in one currency credited to a wallet in another currency was applied at face value with the wallet's currency label. For example, a 100 EUR invoice paid against a pre-existing USD wallet credited 100 USD. The Money.Add/Money.Subtract currency guard never fired, because by the time it ran both operands had already been forced to the wallet's currency.

This is latent while the product is single-currency (a freshly created wallet at line 255 inherits invoice.Currency, so it always matches). It becomes a real, silent balance error the moment a pre-existing wallet's currency diverges from a later invoice.

Fix

Now that both sides are Money (following the recent Money value-object adoption in Billing):

  • Wallet.Credit/Wallet.Debit take a Money instead of a decimal and route the balance through Money.Add/Money.Subtract, which throw on a currency mismatch. The hole closes at the domain boundary.
  • BillingService passes invoice.SubtotalAmount through unchanged and guards the top-up credit: if a pre-existing wallet's currency differs from the invoice, it throws a CustomException with HttpStatusCode.Conflict and a clear message, so the mismatch surfaces as a meaningful error rather than a raw 500 bubbling out of Money.

Testing

  • dotnet build clean, 0 warnings (TreatWarningsAsErrors).
  • Full Billing.Tests suite: 124 passed, 0 failed. Adds Credit_rejects_currency_mismatch as a regression guard for the closed hole; existing Wallet tests migrated to the Money signature.
  • Integration tests (WalletTopupServiceTests) were not run in this environment (require Docker/Testcontainers); the change to the credit path is value-preserving on the happy path and the new guard only fires on a currency mismatch those tests do not set up.

Notes

  • Wallet.Credit/Debit signature changes decimal -> Money. Wallet is internal domain, so no public HTTP surface changes; no endpoint, config, or docs change is required. Flagging the signature change for reviewer awareness.

Wallet.Credit/Debit took a raw decimal and stamped the wallet's own
currency, so MarkInvoicePaidAsync crediting invoice.SubtotalAmount.Amount
silently treated a foreign-currency invoice as a wallet-currency credit at
face value. Latent while single-currency, but wrong the moment a pre-existing
wallet's currency diverges from a later invoice.

Both sides are now Money: Credit/Debit take Money and route the balance
through Money.Add/Subtract, which throw on a currency mismatch. BillingService
guards the top-up credit with a Conflict CustomException so the mismatch
surfaces as a meaningful error instead of a raw 500 from deep in Money.

Adds Credit_rejects_currency_mismatch as a regression guard.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant