{"record":{"id":"b071d95dddc2c153","repo":"fullstackhero/dotnet-starter-kit","slug":"plan-planid-not-found-for-tenant-tenantid","errorCode":null,"errorMessage":"Plan {planId} not found for tenant {tenantId}.","messagePattern":"Plan (.+?) not found for tenant (.+?)\\.","errorType":"exception","errorClass":"NotFoundException","httpStatus":404,"severity":"error","filePath":"src/Modules/Billing/Modules.Billing/Services/BillingService.cs","lineNumber":311,"sourceCode":"        var isRoot = callerTenantId == MultitenancyConstants.Root.Id;\n\n        return await _db.Invoices\n            .FirstOrDefaultAsync(i => i.Id == invoiceId && (isRoot || i.TenantId == callerTenantId), cancellationToken)\n            .ConfigureAwait(false)\n            ?? throw new NotFoundException($\"Invoice {invoiceId} not found.\");\n    }\n\n    public async Task<Invoice?> CreateSubscriptionInvoiceAsync(\n        string tenantId,\n        Guid planId,\n        DateTime periodStartUtc,\n        DateTime periodEndUtc,\n        CancellationToken cancellationToken = default)\n    {\n        ArgumentException.ThrowIfNullOrWhiteSpace(tenantId);\n\n        var plan = await _db.Plans.FirstOrDefaultAsync(p => p.Id == planId, cancellationToken).ConfigureAwait(false)\n            ?? throw new NotFoundException($\"Plan {planId} not found for tenant {tenantId}.\");\n\n        var termPrice = plan.TermPrice;\n        if (termPrice.Amount <= 0m)\n        {\n            // Free / trial plan — validity is still set, but there is nothing to bill.\n            if (_logger.IsEnabled(LogLevel.Information))\n            {\n                _logger.LogInformation(\"[Billing] plan {PlanKey} term price is zero for tenant {TenantId}, no subscription invoice\", plan.Key, tenantId);\n            }\n            return null;\n        }\n\n        var periodStart = DateTime.SpecifyKind(periodStartUtc, DateTimeKind.Utc);\n        var periodEnd = DateTime.SpecifyKind(periodEndUtc, DateTimeKind.Utc);\n        var invoiceNumber = BuildSubscriptionInvoiceNumber(tenantId, periodStart);\n\n        // Idempotency: redelivery of the subscribe/renew event must not double-invoice the term.\n        var existing = await _db.Invoices","sourceCodeStart":293,"sourceCodeEnd":329,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Billing/Modules.Billing/Services/BillingService.cs#L293-L329","documentation":"CreateSubscriptionInvoiceAsync loads the plan by planId before computing term price. If no Plan row with that Id exists, NotFoundException(\"Plan {planId} not found for tenant {tenantId}.\") is thrown and no subscription invoice is created.","triggerScenarios":"Creating a subscription invoice with a planId that doesn't exist in the Plans table — deleted plan, wrong GUID, Id from another environment, or plan seeded after the invoice job ran.","commonSituations":"Tenant provisioning passing a hard-coded/stale plan Id; plans re-seeded with new GUIDs while tenants reference old ones; environment clones diverging; admin tooling sending unsaved plan Ids.","solutions":["Validate the planId against the Plans table before creating the subscription invoice.","Re-create or restore the missing plan row, or switch to an existing plan's Id.","Avoid hard-coding plan Ids across environments; resolve plans by stable code/slug instead of GUID.","Ensure seed/migrations that insert plans ran in the target environment before provisioning tenants."],"exampleFix":"// before\nawait billing.CreateSubscriptionInvoiceAsync(tenantId, Guid.Parse(config[\"PlanId\"]), ...);\n// after\nvar plan = await db.Plans.FirstOrDefaultAsync(p => p.Code == config[\"PlanCode\"])\n    ?? throw new NotFoundException($\"Plan {config[\"PlanCode\"]} not found.\");\nawait billing.CreateSubscriptionInvoiceAsync(tenantId, plan.Id, ...);","handlingStrategy":"validation","validationCode":"var plan = await db.Plans.FirstOrDefaultAsync(p => p.Code == planCode)\n    ?? throw new NotFoundException($\"Plan {planCode} not found.\");\n// then use plan.Id","typeGuard":"bool planExists(Guid id) => db.Plans.Any(p => p.Id == id); // await","tryCatchPattern":"try { await billing.CreateSubscriptionInvoiceAsync(tenantId, planId, start, end, ct); }\ncatch (NotFoundException ex) { log.Error(\"Missing plan {PlanId}\", planId); }","preventionTips":["Resolve plans by stable code/slug, not hard-coded GUIDs.","Run plan seed data before tenant provisioning in every environment.","Validate planId against the DB before creating subscription invoices."],"tags":["billing","not-found","referential-integrity"],"backgroundTag":"entity-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"}