💄 Optimized subscriptions
This commit is contained in:
		| @@ -7,16 +7,43 @@ namespace DysonNetwork.Sphere.Wallet; | ||||
|  | ||||
| public record class SubscriptionTypeData( | ||||
|     string Identifier, | ||||
|     decimal BasePrice | ||||
|     string? GroupIdentifier, | ||||
|     string Currency, | ||||
|     decimal BasePrice, | ||||
|     int? RequiredLevel = null | ||||
| ) | ||||
| { | ||||
|     public static readonly Dictionary<string, SubscriptionTypeData> SubscriptionDict = | ||||
|         new() | ||||
|         { | ||||
|             [SubscriptionType.Twinkle] = new SubscriptionTypeData(SubscriptionType.Twinkle, 0), | ||||
|             [SubscriptionType.Stellar] = new SubscriptionTypeData(SubscriptionType.Stellar, 10), | ||||
|             [SubscriptionType.Nova] = new SubscriptionTypeData(SubscriptionType.Nova, 20), | ||||
|             [SubscriptionType.Supernova] = new SubscriptionTypeData(SubscriptionType.Supernova, 30) | ||||
|             [SubscriptionType.Twinkle] = new SubscriptionTypeData( | ||||
|                 SubscriptionType.Twinkle, | ||||
|                 SubscriptionType.StellarProgram, | ||||
|                 WalletCurrency.SourcePoint, | ||||
|                 0, | ||||
|                 1 | ||||
|             ), | ||||
|             [SubscriptionType.Stellar] = new SubscriptionTypeData( | ||||
|                 SubscriptionType.Stellar, | ||||
|                 SubscriptionType.StellarProgram, | ||||
|                 WalletCurrency.SourcePoint, | ||||
|                 1200, | ||||
|                 3 | ||||
|             ), | ||||
|             [SubscriptionType.Nova] = new SubscriptionTypeData( | ||||
|                 SubscriptionType.Nova, | ||||
|                 SubscriptionType.StellarProgram, | ||||
|                 WalletCurrency.SourcePoint, | ||||
|                 2400, | ||||
|                 6 | ||||
|             ), | ||||
|             [SubscriptionType.Supernova] = new SubscriptionTypeData( | ||||
|                 SubscriptionType.Supernova, | ||||
|                 SubscriptionType.StellarProgram, | ||||
|                 WalletCurrency.SourcePoint, | ||||
|                 3600, | ||||
|                 9 | ||||
|             ) | ||||
|         }; | ||||
|  | ||||
|     public static readonly Dictionary<string, string> SubscriptionHumanReadable = | ||||
|   | ||||
| @@ -32,16 +32,22 @@ public class SubscriptionService( | ||||
|         bool noop = false | ||||
|     ) | ||||
|     { | ||||
|         var subscriptionTemplate = SubscriptionTypeData | ||||
|         var subscriptionInfo = SubscriptionTypeData | ||||
|             .SubscriptionDict.TryGetValue(identifier, out var template) | ||||
|             ? template | ||||
|             : null; | ||||
|         if (subscriptionTemplate is null) | ||||
|         if (subscriptionInfo is null) | ||||
|             throw new ArgumentOutOfRangeException(nameof(identifier), $@"Subscription {identifier} was not found."); | ||||
|         var subscriptionsInGroup = subscriptionInfo.GroupIdentifier is not null | ||||
|             ? SubscriptionTypeData.SubscriptionDict | ||||
|                 .Where(s => s.Value.GroupIdentifier == subscriptionInfo.GroupIdentifier) | ||||
|                 .Select(s => s.Value.Identifier) | ||||
|                 .ToArray() | ||||
|             : [identifier]; | ||||
|  | ||||
|         cycleDuration ??= Duration.FromDays(30); | ||||
|  | ||||
|         var existingSubscription = await GetSubscriptionAsync(account.Id, identifier); | ||||
|         var existingSubscription = await GetSubscriptionAsync(account.Id, subscriptionsInGroup); | ||||
|         if (existingSubscription is not null && !noop) | ||||
|             throw new InvalidOperationException($"Active subscription with identifier {identifier} already exists."); | ||||
|         if (existingSubscription is not null) | ||||
| @@ -77,7 +83,7 @@ public class SubscriptionService( | ||||
|             Status = SubscriptionStatus.Unpaid, | ||||
|             PaymentMethod = paymentMethod, | ||||
|             PaymentDetails = paymentDetails, | ||||
|             BasePrice = subscriptionTemplate.BasePrice, | ||||
|             BasePrice = subscriptionInfo.BasePrice, | ||||
|             CouponId = couponData?.Id, | ||||
|             Coupon = couponData, | ||||
|             RenewalAt = (isFreeTrial || !isAutoRenewal) ? null : now.Plus(cycleDuration.Value), | ||||
| @@ -220,10 +226,16 @@ public class SubscriptionService( | ||||
|             .OrderByDescending(s => s.BegunAt) | ||||
|             .FirstOrDefaultAsync(); | ||||
|         if (subscription is null) throw new InvalidOperationException("No matching subscription found."); | ||||
|          | ||||
|         var subscriptionInfo = SubscriptionTypeData.SubscriptionDict | ||||
|             .TryGetValue(subscription.Identifier, out var template) | ||||
|             ? template | ||||
|             : null; | ||||
|         if (subscriptionInfo is null) throw new InvalidOperationException("No matching subscription found."); | ||||
|  | ||||
|         return await payment.CreateOrderAsync( | ||||
|             null, | ||||
|             WalletCurrency.GoldenPoint, | ||||
|             subscriptionInfo.Currency, | ||||
|             subscription.FinalPrice, | ||||
|             appIdentifier: SubscriptionOrderIdentifier, | ||||
|             meta: new Dictionary<string, object>() | ||||
| @@ -319,7 +331,7 @@ public class SubscriptionService( | ||||
|     { | ||||
|         var account = await db.Accounts.FirstOrDefaultAsync(a => a.Id == subscription.AccountId); | ||||
|         if (account is null) return; | ||||
|          | ||||
|  | ||||
|         AccountService.SetCultureInfo(account); | ||||
|  | ||||
|         var humanReadableName = | ||||
| @@ -345,10 +357,10 @@ public class SubscriptionService( | ||||
|  | ||||
|     private const string SubscriptionCacheKeyPrefix = "subscription:"; | ||||
|  | ||||
|     public async Task<Subscription?> GetSubscriptionAsync(Guid accountId, string identifier) | ||||
|     public async Task<Subscription?> GetSubscriptionAsync(Guid accountId, params string[] identifiers) | ||||
|     { | ||||
|         // Create a unique cache key for this subscription | ||||
|         var cacheKey = $"{SubscriptionCacheKeyPrefix}{accountId}:{identifier}"; | ||||
|         var cacheKey = $"{SubscriptionCacheKeyPrefix}{accountId}:{string.Join(",", identifiers)}"; | ||||
|  | ||||
|         // Try to get the subscription from cache first | ||||
|         var (found, cachedSubscription) = await cache.GetAsyncWithStatus<Subscription>(cacheKey); | ||||
| @@ -359,15 +371,13 @@ public class SubscriptionService( | ||||
|  | ||||
|         // If not in cache, get from database | ||||
|         var subscription = await db.WalletSubscriptions | ||||
|             .Where(s => s.AccountId == accountId && s.Identifier == identifier) | ||||
|             .Where(s => s.AccountId == accountId && identifiers.Contains(s.Identifier)) | ||||
|             .OrderByDescending(s => s.BegunAt) | ||||
|             .FirstOrDefaultAsync(); | ||||
|  | ||||
|         // Cache the result if found (with 30 minutes expiry) | ||||
|         if (subscription != null) | ||||
|         { | ||||
|             await cache.SetAsync(cacheKey, subscription, TimeSpan.FromMinutes(30)); | ||||
|         } | ||||
|  | ||||
|         return subscription; | ||||
|     } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user