{"record":{"id":"b4fab15533febec3","repo":"fullstackhero/dotnet-starter-kit","slug":"this-tenant-s-subscription-has-expired-please-renew-to","errorCode":null,"errorMessage":"This tenant's subscription has expired. Please renew to continue.","messagePattern":"This tenant's subscription has expired\\. Please renew to continue\\.","errorType":"exception","errorClass":"ForbiddenException","httpStatus":403,"severity":"critical","filePath":"src/Modules/Multitenancy/Modules.Multitenancy/MultitenancyModule.cs","lineNumber":194,"sourceCode":"                }\n\n                if (tenant is not null &&\n                    !string.Equals(tenant.Id, MultitenancyConstants.Root.Id, StringComparison.Ordinal))\n                {\n                    if (!tenant.IsActive)\n                    {\n                        throw new ForbiddenException(\"This tenant has been deactivated. Contact your administrator.\");\n                    }\n\n                    // Expiry is enforced on every request (not just at login) with a grace period:\n                    // a tenant past ValidUpto still works until ValidUpto + grace, then is hard-blocked.\n                    var graceDays = ctx.RequestServices\n                        .GetRequiredService<IOptions<TenantBillingOptions>>().Value.GracePeriodDays;\n                    var nowUtc = ctx.RequestServices.GetRequiredService<TimeProvider>().GetUtcNow().UtcDateTime;\n                    var graceEndsUtc = tenant.ValidUpto.AddDays(graceDays);\n                    if (nowUtc > graceEndsUtc)\n                    {\n                        throw new ForbiddenException(\"This tenant's subscription has expired. Please renew to continue.\");\n                    }\n\n                    // Inside the grace period: surface days-left so clients can warn. Set via OnStarting so\n                    // the header survives even when an exception handler rewrites the response.\n                    if (nowUtc > tenant.ValidUpto)\n                    {\n                        var daysLeft = (int)Math.Ceiling((graceEndsUtc - nowUtc).TotalDays);\n                        var headerValue = daysLeft.ToString(System.Globalization.CultureInfo.InvariantCulture);\n                        ctx.Response.OnStarting(static state =>\n                        {\n                            var (response, value) = ((HttpResponse, string))state;\n                            response.Headers[\"X-Subscription-Grace\"] = value;\n                            return Task.CompletedTask;\n                        }, (ctx.Response, headerValue));\n                    }\n                }\n            }\n","sourceCodeStart":176,"sourceCodeEnd":212,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Multitenancy/Modules.Multitenancy/MultitenancyModule.cs#L176-L212","documentation":"The multitenancy middleware throws ForbiddenException (\"This tenant's subscription has expired. Please renew to continue.\") when the current UTC time is past the tenant's ValidUpto plus the configured TenantBillingOptions.GracePeriodDays. Inside the grace period requests still pass, with a days-left warning header; after it, requests are blocked.","triggerScenarios":"Any API request after ValidUpto + GracePeriodDays has elapsed for the tenant; clock skew between app servers making nowUtc appear later than it is; a subscription that lapsed without renewal.","commonSituations":"Credit card expired / payment failed and nobody renewed; grace period configured too short (GracePeriodDays=0); server clock drift triggering expiry early.","solutions":["Renew the tenant subscription (extend ValidUpto via the billing/multitenancy admin APIs).","Increase GracePeriodDays in TenantBillingOptions to allow a longer window.","Verify server clock accuracy (NTP) if expiry seems premature.","Correct an invalid ValidUpto value if it was set wrong during provisioning."],"exampleFix":"// before\n\"TenantBilling\": { \"GracePeriodDays\": 0 }\n// after\n\"TenantBilling\": { \"GracePeriodDays\": 14 }","handlingStrategy":"try-catch","validationCode":"var daysLeft = (tenant.ValidUpto - DateTime.UtcNow).TotalDays;\nif (daysLeft <= 3) triggerRenewalReminder();","typeGuard":null,"tryCatchPattern":"try { await apiFetch(url); }\ncatch (ApiError e) when (e.Message.Contains(\"expired\"))\n{ showSubscriptionExpiredScreen(); }","preventionTips":["Read the days-left grace header the middleware emits and warn users early.","Set a sane GracePeriodDays (e.g. 7-14) instead of 0.","Automate renewal emails before ValidUpto.","Keep server clocks NTP-synced."],"tags":["multitenancy","billing","subscription","forbidden"],"backgroundTag":"expired-subscription","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"}