{"record":{"id":"b6d2270fbd58d35c","repo":"fullstackhero/dotnet-starter-kit","slug":"top-up-request-command-id-cannot-be-rejected-because-it-is","errorCode":null,"errorMessage":"Top-up request {command.Id} cannot be rejected because it is {request.Status} (only Pending requests can be rejected).","messagePattern":"Top-up request (.+?) cannot be rejected because it is (.+?) \\(only Pending requests can be rejected\\)\\.","errorType":"http","errorClass":"CustomException","httpStatus":409,"severity":"warning","filePath":"src/Modules/Billing/Modules.Billing/Features/v1/Wallets/RejectTopupRequest/RejectTopupRequestCommandHandler.cs","lineNumber":38,"sourceCode":"        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 reject top-up requests for your own tenant.\");\n        }\n\n        if (request.Status != TopupRequestStatus.Pending)\n        {\n            throw new CustomException(\n                $\"Top-up request {command.Id} cannot be rejected because it is {request.Status} (only Pending requests can be rejected).\",\n                (IEnumerable<string>?)null,\n                HttpStatusCode.Conflict);\n        }\n\n        request.Reject(command.Reason);\n        await db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);\n        return request.Id;\n    }\n}\n","sourceCodeStart":20,"sourceCodeEnd":49,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Billing/Modules.Billing/Features/v1/Wallets/RejectTopupRequest/RejectTopupRequestCommandHandler.cs#L20-L49","documentation":"Only top-up requests in TopupRequestStatus.Pending can be rejected. When request.Status is anything else (Approved, Rejected, etc.), the handler throws CustomException with HTTP 409 Conflict explaining the current state. This is an optimistic-concurrency/state-machine guard.","triggerScenarios":"Rejecting an already-approved or already-rejected top-up request — e.g. two admins acting concurrently, a retry after a successful rejection, or rejecting after the invoice was generated and marked paid.","commonSituations":"Double-click / duplicate submission of the reject action; another operator approved the request moments earlier; a webhook or billing job already transitioned the request; stale page data.","solutions":["Refresh the top-up request status and only reject while it shows Pending.","Make the reject action idempotent on the client: on 409, re-fetch and reflect the actual state instead of retrying blindly.","Serialize workflows so one operator/process owns a pending request, or use a conditional update (WHERE Status == Pending).","Surface the exception message's current status in the UI so users understand why rejection failed."],"exampleFix":"// before\nawait mediator.Send(new RejectTopupRequestCommand(id, reason)); // may 409\n// after\nvar req = await db.TopupRequests.FindAsync(id);\nif (req?.Status != TopupRequestStatus.Pending) { /* refresh UI, skip reject */ }\nelse await mediator.Send(new RejectTopupRequestCommand(id, reason));","handlingStrategy":"try-catch","validationCode":"var status = await db.TopupRequests.Where(r => r.Id == id).Select(r => (TopupRequestStatus?)r.Status).SingleOrDefaultAsync();\nif (status != TopupRequestStatus.Pending) return Conflict($\"Request is {status}, not Pending.\");","typeGuard":"bool isPending(TopupRequest r) => r.Status == TopupRequestStatus.Pending;","tryCatchPattern":"try { await mediator.Send(cmd); }\ncatch (CustomException ex) when (ex.StatusCode == HttpStatusCode.Conflict) { /* re-fetch and show current state */ }","preventionTips":["Disable the reject button unless the request shows Pending in fresh data.","Make reject actions idempotent on the client; on 409, re-fetch.","Serialize workflows so a single actor owns a pending request."],"tags":["conflict","state-machine","billing"],"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"}