{"record":{"id":"59ca45deca923043","repo":"fullstackhero/dotnet-starter-kit","slug":"top-up-request-command-id-not-found","errorCode":null,"errorMessage":"Top-up request {command.Id} not found.","messagePattern":"Top-up request (.+?) not found\\.","errorType":"exception","errorClass":"NotFoundException","httpStatus":404,"severity":"error","filePath":"src/Modules/Billing/Modules.Billing/Features/v1/Wallets/ApproveTopupRequest/ApproveTopupRequestCommandHandler.cs","lineNumber":29,"sourceCode":"\npublic sealed class ApproveTopupRequestCommandHandler(\n    BillingDbContext db,\n    IBillingService billing,\n    IMultiTenantContextAccessor<AppTenantInfo> tenantAccessor)\n    : ICommandHandler<ApproveTopupRequestCommand, Guid>\n{\n    public async ValueTask<Guid> Handle(ApproveTopupRequestCommand command, CancellationToken cancellationToken)\n    {\n        ArgumentNullException.ThrowIfNull(command);\n\n        var callerTenantId = tenantAccessor.MultiTenantContext?.TenantInfo?.Id\n            ?? throw new UnauthorizedException(\"Tenant context is required.\");\n        var isRoot = callerTenantId == MultitenancyConstants.Root.Id;\n\n        var request = await db.TopupRequests\n            .FirstOrDefaultAsync(r => r.Id == command.Id, cancellationToken)\n            .ConfigureAwait(false)\n            ?? throw new NotFoundException($\"Top-up request {command.Id} not found.\");\n\n        if (!isRoot && request.TenantId != callerTenantId)\n        {\n            throw new UnauthorizedException(\"You can only approve top-up requests for your own tenant.\");\n        }\n\n        // For root, operate on the request's own tenant; for non-root, callerTenantId equals request.TenantId.\n        var invoice = await billing.CreateTopupInvoiceAsync(request.TenantId, command.Id, cancellationToken)\n            .ConfigureAwait(false);\n\n        return invoice.Id;\n    }\n}\n","sourceCodeStart":11,"sourceCodeEnd":43,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Billing/Modules.Billing/Features/v1/Wallets/ApproveTopupRequest/ApproveTopupRequestCommandHandler.cs#L11-L43","documentation":"After resolving the caller tenant, ApproveTopupRequestCommandHandler loads the TopupRequest by Id from the (non-tenant-filtered) BillingDbContext and throws NotFoundException('Top-up request {id} not found.') when no row matches. Because BillingDbContext lacks a global tenant query filter, this NotFoundException is authoritative: the id genuinely does not exist, not merely hidden by a tenant filter.","triggerScenarios":"Approving with a GUID that was never created, an id from a different database/environment (dev vs prod), or an id of a top-up request that was deleted; also passing a malformed/empty Guid that maps to Guid.Empty.","commonSituations":"Stale links in a UI after reseeding the database; copying an id from logs of another environment; race where the request was rejected/deleted between listing and approving; typo'd id in a test payload.","solutions":["Verify the top-up request id exists: query GET /api/v1/wallets/topup-requests (root for cross-tenant) and use an id from the response.","Check you are pointed at the correct environment/database; ids are not portable across environments.","If the request was deleted, create a new top-up request rather than approving the stale id.","Confirm the client is not sending Guid.Empty due to a serialization bug — validate the id is a non-empty Guid before calling."],"exampleFix":"// before\nvar id = Guid.Empty;\nawait mediator.Send(new ApproveTopupRequestCommand(id));\n// after\nvar id = /* fetch from GET /topup-requests */ loaded.Id;\nif (id == Guid.Empty) throw new InvalidOperationException(\"Top-up request id missing\");\nawait mediator.Send(new ApproveTopupRequestCommand(id));","handlingStrategy":"validation","validationCode":"if (id == Guid.Empty) throw new ArgumentException(\"Top-up request id required\");\nvar known = await api.GetTopupRequestsAsync(); // ensure id comes from server data, not stale state\nif (known.Items.All(r => r.Id != id)) throw new InvalidOperationException($\"Unknown top-up request {id}\");","typeGuard":"bool IsValidTopupRequestId(Guid id) => id != Guid.Empty;","tryCatchPattern":"try { await api.ApproveTopupRequestAsync(id); }\ncatch (NotFoundException ex) when (ex.Message.Contains(\"Top-up request\"))\n{\n    // refresh the list; the request no longer exists in this environment\n}","preventionTips":["Always source ids from a fresh GET, never from cached/stale UI state.","Don't reuse ids across environments (dev/test/prod).","Handle reject/delete flows in the UI so users can't approve removed requests.","Log the id and environment on NotFound to spot cross-environment misuse quickly."],"tags":["wallet","billing","not-found","guid"],"backgroundTag":"record-not-found","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"}