139 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			139 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using DysonNetwork.Shared.Models;
 | |
| using Microsoft.EntityFrameworkCore;
 | |
| using NodaTime;
 | |
| using Quartz;
 | |
| 
 | |
| namespace DysonNetwork.Pass.Wallet;
 | |
| 
 | |
| public class SubscriptionRenewalJob(
 | |
|     AppDatabase db,
 | |
|     SubscriptionService subscriptionService,
 | |
|     PaymentService paymentService,
 | |
|     WalletService walletService,
 | |
|     ILogger<SubscriptionRenewalJob> logger
 | |
| ) : IJob
 | |
| {
 | |
|     public async Task Execute(IJobExecutionContext context)
 | |
|     {
 | |
|         logger.LogInformation("Starting subscription auto-renewal job...");
 | |
| 
 | |
|         // First update expired subscriptions
 | |
|         var expiredCount = await subscriptionService.UpdateExpiredSubscriptionsAsync();
 | |
|         logger.LogInformation("Updated {ExpiredCount} expired subscriptions", expiredCount);
 | |
| 
 | |
|         var now = SystemClock.Instance.GetCurrentInstant();
 | |
|         const int batchSize = 100; // Process in smaller batches
 | |
|         var processedCount = 0;
 | |
|         var renewedCount = 0;
 | |
|         var failedCount = 0;
 | |
| 
 | |
|         // Find subscriptions that need renewal (due for renewal and are still active)
 | |
|         var subscriptionsToRenew = await db.WalletSubscriptions
 | |
|             .Where(s => s.RenewalAt.HasValue && s.RenewalAt.Value <= now) // Due for renewal
 | |
|             .Where(s => s.Status == SubscriptionStatus.Active) // Only paid subscriptions
 | |
|             .Where(s => s.IsActive) // Only active subscriptions
 | |
|             .Where(s => !s.IsFreeTrial) // Exclude free trials
 | |
|             .OrderBy(s => s.RenewalAt) // Process oldest first
 | |
|             .Take(batchSize)
 | |
|             .Include(s => s.Coupon) // Include coupon information
 | |
|             .ToListAsync();
 | |
| 
 | |
|         var totalSubscriptions = subscriptionsToRenew.Count;
 | |
|         logger.LogInformation("Found {TotalSubscriptions} subscriptions due for renewal", totalSubscriptions);
 | |
| 
 | |
|         foreach (var subscription in subscriptionsToRenew)
 | |
|         {
 | |
|             try
 | |
|             {
 | |
|                 processedCount++;
 | |
|                 logger.LogDebug(
 | |
|                     "Processing renewal for subscription {SubscriptionId} (Identifier: {Identifier}) for account {AccountId}",
 | |
|                     subscription.Id, subscription.Identifier, subscription.AccountId);
 | |
| 
 | |
|                 if (subscription.RenewalAt is null)
 | |
|                 {
 | |
|                     logger.LogWarning(
 | |
|                         "Subscription {SubscriptionId} (Identifier: {Identifier}) has no renewal date or has been cancelled.",
 | |
|                         subscription.Id, subscription.Identifier);
 | |
|                     subscription.Status = SubscriptionStatus.Cancelled;
 | |
|                     db.WalletSubscriptions.Update(subscription);
 | |
|                     await db.SaveChangesAsync();
 | |
|                     continue;
 | |
|                 }
 | |
| 
 | |
|                 // Calculate next cycle duration based on current cycle
 | |
|                 var currentCycle = subscription.EndedAt!.Value - subscription.BegunAt;
 | |
| 
 | |
|                 // Create an order for the renewal payment
 | |
|                 var order = await paymentService.CreateOrderAsync(
 | |
|                     null,
 | |
|                     WalletCurrency.GoldenPoint,
 | |
|                     subscription.FinalPrice,
 | |
|                     appIdentifier: "internal",
 | |
|                     productIdentifier: subscription.Identifier,
 | |
|                     meta: new Dictionary<string, object>()
 | |
|                     {
 | |
|                         ["subscription_id"] = subscription.Id.ToString(),
 | |
|                         ["subscription_identifier"] = subscription.Identifier,
 | |
|                         ["is_renewal"] = true
 | |
|                     }
 | |
|                 );
 | |
| 
 | |
|                 // Try to process the payment automatically
 | |
|                 if (subscription.PaymentMethod == SubscriptionPaymentMethod.InAppWallet)
 | |
|                 {
 | |
|                     try
 | |
|                     {
 | |
|                         var wallet = await walletService.GetWalletAsync(subscription.AccountId);
 | |
|                         if (wallet is null) continue;
 | |
| 
 | |
|                         // Process automatic payment from wallet
 | |
|                         await paymentService.PayOrderAsync(order.Id, wallet);
 | |
| 
 | |
|                         // Update subscription details
 | |
|                         subscription.BegunAt = subscription.EndedAt!.Value;
 | |
|                         subscription.EndedAt = subscription.BegunAt.Plus(currentCycle);
 | |
|                         subscription.RenewalAt = subscription.EndedAt;
 | |
| 
 | |
|                         db.WalletSubscriptions.Update(subscription);
 | |
|                         await db.SaveChangesAsync();
 | |
| 
 | |
|                         renewedCount++;
 | |
|                         logger.LogInformation("Successfully renewed subscription {SubscriptionId}", subscription.Id);
 | |
|                     }
 | |
|                     catch (Exception ex)
 | |
|                     {
 | |
|                         // If auto-payment fails, mark for manual payment
 | |
|                         logger.LogWarning(ex, "Failed to auto-renew subscription {SubscriptionId} with wallet payment",
 | |
|                             subscription.Id);
 | |
|                         failedCount++;
 | |
|                     }
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     // For other payment methods, mark as pending payment
 | |
|                     logger.LogInformation("Subscription {SubscriptionId} requires manual payment via {PaymentMethod}",
 | |
|                         subscription.Id, subscription.PaymentMethod);
 | |
|                     failedCount++;
 | |
|                 }
 | |
|             }
 | |
|             catch (Exception ex)
 | |
|             {
 | |
|                 logger.LogError(ex, "Error processing subscription {SubscriptionId}", subscription.Id);
 | |
|                 failedCount++;
 | |
|             }
 | |
| 
 | |
|             // Log progress periodically
 | |
|             if (processedCount % 20 == 0 || processedCount == totalSubscriptions)
 | |
|             {
 | |
|                 logger.LogInformation(
 | |
|                     "Progress: processed {ProcessedCount}/{TotalSubscriptions} subscriptions, {RenewedCount} renewed, {FailedCount} failed",
 | |
|                     processedCount, totalSubscriptions, renewedCount, failedCount);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         logger.LogInformation(
 | |
|             "Completed subscription renewal job. Processed: {ProcessedCount}, Renewed: {RenewedCount}, Failed: {FailedCount}",
 | |
|             processedCount, renewedCount, failedCount);
 | |
|     }
 | |
| } |