From c88dde3693d6c9c18ebf35e3466b10dc11f01db5 Mon Sep 17 00:00:00 2001 From: Nick Beckley Date: Fri, 12 Jun 2026 19:51:47 +0100 Subject: [PATCH] Phase 7C User Delete --- PlotLine/Services/SubscriptionServices.cs | 37 +++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/PlotLine/Services/SubscriptionServices.cs b/PlotLine/Services/SubscriptionServices.cs index da9a01f..8d792f8 100644 --- a/PlotLine/Services/SubscriptionServices.cs +++ b/PlotLine/Services/SubscriptionServices.cs @@ -6,6 +6,7 @@ namespace PlotLine.Services; public interface ISubscriptionService { Task GetCurrentSubscriptionAsync(int userId); + Task HasPaidBillingRiskAsync(int userId); Task GetSubscriptionLevelAsync(int subscriptionLevelId); Task> GetActiveSubscriptionLevelsAsync(); Task CanCreateBookAsync(int userId); @@ -25,6 +26,12 @@ public sealed class SubscriptionService(ISubscriptionRepository subscriptions, I public Task GetCurrentSubscriptionAsync(int userId) => subscriptions.GetCurrentSubscriptionAsync(userId); + public async Task HasPaidBillingRiskAsync(int userId) + { + var subscription = await subscriptions.GetCurrentSubscriptionAsync(userId); + return HasPaidBillingRisk(subscription, DateTime.UtcNow); + } + public Task GetSubscriptionLevelAsync(int subscriptionLevelId) => subscriptions.GetSubscriptionLevelAsync(subscriptionLevelId); @@ -121,6 +128,36 @@ public sealed class SubscriptionService(ISubscriptionRepository subscriptions, I } private static long ToBytes(int megabytes) => megabytes * 1024L * 1024L; + + internal static bool HasPaidBillingRisk(UserSubscriptionDetail? subscription, DateTime utcNow) + { + if (subscription is null || string.IsNullOrWhiteSpace(subscription.StripeSubscriptionId)) + { + return false; + } + + var stripeStatus = (subscription.StripeStatus ?? string.Empty).Trim().ToLowerInvariant(); + if (stripeStatus is "canceled" or "cancelled" or "incomplete_expired") + { + return false; + } + + if (subscription.CancelAtPeriodEnd + && (!subscription.CurrentPeriodEndUTC.HasValue || subscription.CurrentPeriodEndUTC.Value > utcNow)) + { + return true; + } + + if (subscription.CurrentPeriodEndUTC.HasValue + && subscription.CurrentPeriodEndUTC.Value <= utcNow + && stripeStatus is "ended" or "expired" or "inactive") + { + return false; + } + + return stripeStatus is "active" or "trialing" or "past_due" or "unpaid" or "incomplete" or "paused" + || string.IsNullOrWhiteSpace(stripeStatus); + } } public sealed class SubscriptionLimitException(string message) : InvalidOperationException(message);