{"record":{"id":"676bbf6cfa5f4c2d","repo":"fullstackhero/dotnet-starter-kit","slug":"insufficient-wallet-balance","errorCode":null,"errorMessage":"Insufficient wallet balance.","messagePattern":"Insufficient wallet balance\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Modules/Billing/Modules.Billing/Domain/Wallet.cs","lineNumber":55,"sourceCode":"        ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(amount.Amount, 0m);\n        // Validate currency before touching the aggregate: Money.Add throws on a\n        // mismatch, so computing the new balance first keeps a rejected credit from\n        // leaving a phantom ledger row behind.\n        var newBalance = Balance.Add(amount);\n        var tx = WalletTransaction.Create(Id, TenantId, amount, kind, description, referenceId);\n        _transactions.Add(tx);\n        Balance = newBalance;\n        UpdatedAtUtc = DateTime.UtcNow;\n        return tx;\n    }\n\n    public WalletTransaction Debit(Money amount, WalletTransactionKind kind, string description, string? referenceId)\n    {\n        ArgumentNullException.ThrowIfNull(amount);\n        ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(amount.Amount, 0m);\n        var remaining = Balance.Subtract(amount);\n        if (remaining.Amount < 0m)\n            throw new InvalidOperationException(\"Insufficient wallet balance.\");\n        var tx = WalletTransaction.Create(Id, TenantId, amount.Multiply(-1m), kind, description, referenceId);\n        _transactions.Add(tx);\n        Balance = remaining;\n        UpdatedAtUtc = DateTime.UtcNow;\n        return tx;\n    }\n}\n","sourceCodeStart":37,"sourceCodeEnd":63,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Billing/Modules.Billing/Domain/Wallet.cs#L37-L63","documentation":"Wallet.Dbit throws InvalidOperationException when the requested debit amount exceeds the wallet's current balance — i.e. Balance.Subtract(amount) would go negative. The wallet does not support overdrafts.","triggerScenarios":"Debiting an amount greater than Balance; two concurrent debits each valid individually but overdrafting together; debiting before a prior top-up/credit transaction was committed.","commonSituations":"Charging a customer whose wallet was not topped up; race conditions between parallel charge jobs on the same wallet; currency/amount unit mismatches (cents vs whole units) inflating the debit.","solutions":["Check wallet.Balance >= amount before calling Debit, or surface a 'top up required' flow.","Top up the wallet (Credit) to cover the amount.","Serialize debits per wallet (row lock / optimistic concurrency) to prevent overdraft races.","Verify amount units and Money construction so the debit value is correct."],"exampleFix":"// before\nwallet.Debit(amount, kind, desc, refId); // throws when balance short\n\n// after\nif (wallet.Balance.Amount >= amount.Amount)\n{\n    wallet.Debit(amount, kind, desc, refId);\n}","handlingStrategy":"validation","validationCode":"if (wallet.Balance.Amount < amount.Amount) throw new InsufficientBalanceException(wallet.Balance, amount);","typeGuard":null,"tryCatchPattern":"try { wallet.Debit(amount, kind, desc, refId); } catch (InvalidOperationException) { // prompt wallet top-up }","preventionTips":["Pre-check balance before charging","Top up wallets proactively based on spend forecasts","Serialize debits per wallet to avoid overdraft races","Confirm amount units (cents vs units) when constructing Money"],"tags":["domain","billing","wallet","balance"],"backgroundTag":"value-out-of-range","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"}