{"record":{"id":"15ddd7216386076b","repo":"fullstackhero/dotnet-starter-kit","slug":"cannot-credit-a-invoice-currency-top-up-to-a-wallet-currency","errorCode":null,"errorMessage":"Cannot credit a {invoice.Currency} top-up to a {wallet.Currency} wallet.","messagePattern":"Cannot credit a (.+?) top-up to a (.+?) wallet\\.","errorType":"http","errorClass":"CustomException","httpStatus":409,"severity":"error","filePath":"src/Modules/Billing/Modules.Billing/Services/BillingService.cs","lineNumber":261,"sourceCode":"        {\n            var topupRequest = await _db.TopupRequests\n                .FirstOrDefaultAsync(r => r.InvoiceId == invoice.Id, cancellationToken)\n                .ConfigureAwait(false);\n\n            if (topupRequest is { Status: TopupRequestStatus.Invoiced })\n            {\n                var wallet = await _db.Wallets\n                    .FirstOrDefaultAsync(w => w.TenantId == invoice.TenantId, cancellationToken)\n                    .ConfigureAwait(false);\n\n                if (wallet is null)\n                {\n                    wallet = Wallet.Create(invoice.TenantId, invoice.Currency);\n                    _db.Wallets.Add(wallet);\n                }\n                else if (!string.Equals(wallet.Currency, invoice.Currency, StringComparison.Ordinal))\n                {\n                    throw new CustomException(\n                        $\"Cannot credit a {invoice.Currency} top-up to a {wallet.Currency} wallet.\",\n                        errors: null,\n                        HttpStatusCode.Conflict);\n                }\n\n                wallet.Credit(\n                    invoice.SubtotalAmount,\n                    WalletTransactionKind.Topup,\n                    \"WhatsApp wallet top-up\",\n                    topupRequest.Id.ToString());\n\n                topupRequest.MarkCompleted();\n            }\n        }\n\n        await _db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);\n    }\n","sourceCodeStart":243,"sourceCodeEnd":279,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Billing/Modules.Billing/Services/BillingService.cs#L243-L279","documentation":"When a top-up invoice is marked paid, the service credits the tenant's wallet in the invoice's currency. If the tenant already has a wallet in a different currency, crediting would mix currencies, so CustomException with HTTP 409 Conflict is thrown.","triggerScenarios":"Paying a top-up invoice whose Currency differs from the tenant's existing wallet.Currency — e.g. tenant opened a USD wallet, then an EUR top-up is paid; a plan/config change altered the top-up currency.","commonSituations":"Multi-currency tenants issuing top-ups in a currency other than their wallet; configuration change of default currency mid-life; importing invoices from another environment with different currencies.","solutions":["Issue top-ups in the same currency as the tenant's existing wallet, or create the top-up after checking wallet.Currency.","If a different currency is genuinely needed, support multi-wallet-per-tenant or implement explicit FX conversion before crediting.","Reject/void the mismatched invoice and re-issue it in the wallet's currency.","Validate currency at top-up request creation time so the mismatch never reaches payment."],"exampleFix":"// before\nvar invoice = Invoice.CreateTopupDraft(tenantId, \"EUR\", ...); // wallet is USD\n// after\nvar wallet = await db.Wallets.SingleAsync(w => w.TenantId == tenantId);\nvar invoice = Invoice.CreateTopupDraft(tenantId, wallet.Currency, ...);","handlingStrategy":"validation","validationCode":"var wallet = await db.Wallets.SingleOrDefaultAsync(w => w.TenantId == tenantId);\nif (wallet is not null && wallet.Currency != invoiceCurrency)\n    throw new CustomException(\"Currency mismatch\", null, HttpStatusCode.Conflict);","typeGuard":"bool currencyMatches(Wallet? w, string currency) => w is null || string.Equals(w.Currency, currency, StringComparison.Ordinal);","tryCatchPattern":"try { await billing.MarkInvoicePaidAsync(invoiceId, ct); }\ncatch (CustomException ex) when (ex.StatusCode == HttpStatusCode.Conflict && ex.Message.Contains(\"wallet\")) { /* re-issue invoice in wallet currency */ }","preventionTips":["Create top-up requests in the wallet's currency; validate at request creation.","Support multi-wallets or explicit FX conversion for multi-currency tenants.","Never change a tenant's default currency without a migration plan."],"tags":["billing","currency","conflict"],"backgroundTag":"invalid-state-transition","analyzedSha":"3f2959e683e9f83f13e55e1678c9119f63c7e8e5","analyzedAt":"2026-09-15T22:20:53.684Z","contentChangedAt":"2026-09-15T22:20:53.684Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}