299 lines
11 KiB
C#
299 lines
11 KiB
C#
using Stripe;
|
|
using Stripe.Checkout;
|
|
using QRRapidoApp.Models;
|
|
|
|
namespace QRRapidoApp.Services
|
|
{
|
|
public class StripeService
|
|
{
|
|
private readonly IConfiguration _config;
|
|
private readonly IUserService _userService;
|
|
private readonly ILogger<StripeService> _logger;
|
|
|
|
public StripeService(IConfiguration config, IUserService userService, ILogger<StripeService> logger)
|
|
{
|
|
_config = config;
|
|
_userService = userService;
|
|
_logger = logger;
|
|
}
|
|
|
|
public async Task<string> CreateCheckoutSessionAsync(string userId, string priceId)
|
|
{
|
|
try
|
|
{
|
|
var options = new SessionCreateOptions
|
|
{
|
|
PaymentMethodTypes = new List<string> { "card" },
|
|
Mode = "subscription",
|
|
LineItems = new List<SessionLineItemOptions>
|
|
{
|
|
new SessionLineItemOptions
|
|
{
|
|
Price = priceId,
|
|
Quantity = 1
|
|
}
|
|
},
|
|
ClientReferenceId = userId,
|
|
SuccessUrl = $"{_config["App:BaseUrl"]}/Premium/Success?session_id={{CHECKOUT_SESSION_ID}}",
|
|
CancelUrl = $"{_config["App:BaseUrl"]}/Premium/Cancel",
|
|
CustomerEmail = await _userService.GetUserEmailAsync(userId),
|
|
AllowPromotionCodes = true,
|
|
BillingAddressCollection = "auto",
|
|
Metadata = new Dictionary<string, string>
|
|
{
|
|
{ "userId", userId },
|
|
{ "product", "QR Rapido Premium" }
|
|
}
|
|
};
|
|
|
|
var service = new SessionService();
|
|
var session = await service.CreateAsync(options);
|
|
|
|
_logger.LogInformation($"Created Stripe checkout session for user {userId}: {session.Id}");
|
|
|
|
return session.Url;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, $"Error creating Stripe checkout session for user {userId}: {ex.Message}");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public async Task HandleWebhookAsync(string json, string signature)
|
|
{
|
|
var webhookSecret = _config["Stripe:WebhookSecret"];
|
|
|
|
try
|
|
{
|
|
var stripeEvent = EventUtility.ConstructEvent(json, signature, webhookSecret);
|
|
|
|
_logger.LogInformation($"Processing Stripe webhook: {stripeEvent.Type}");
|
|
|
|
switch (stripeEvent.Type)
|
|
{
|
|
case "checkout.session.completed":
|
|
var session = stripeEvent.Data.Object as Session;
|
|
if (session != null)
|
|
{
|
|
await ActivatePremiumAsync(session.ClientReferenceId, session.CustomerId, session.SubscriptionId);
|
|
}
|
|
break;
|
|
|
|
case "invoice.payment_succeeded":
|
|
var invoice = stripeEvent.Data.Object as Invoice;
|
|
if (invoice != null && invoice.SubscriptionId != null)
|
|
{
|
|
await RenewPremiumSubscriptionAsync(invoice.SubscriptionId);
|
|
}
|
|
break;
|
|
|
|
case "invoice.payment_failed":
|
|
var failedInvoice = stripeEvent.Data.Object as Invoice;
|
|
if (failedInvoice != null && failedInvoice.SubscriptionId != null)
|
|
{
|
|
await HandleFailedPaymentAsync(failedInvoice.SubscriptionId);
|
|
}
|
|
break;
|
|
|
|
case "customer.subscription.deleted":
|
|
var deletedSubscription = stripeEvent.Data.Object as Subscription;
|
|
if (deletedSubscription != null)
|
|
{
|
|
await DeactivatePremiumAsync(deletedSubscription);
|
|
}
|
|
break;
|
|
|
|
case "customer.subscription.updated":
|
|
var updatedSubscription = stripeEvent.Data.Object as Subscription;
|
|
if (updatedSubscription != null)
|
|
{
|
|
await UpdateSubscriptionAsync(updatedSubscription);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
_logger.LogWarning($"Unhandled Stripe webhook event type: {stripeEvent.Type}");
|
|
break;
|
|
}
|
|
}
|
|
catch (StripeException ex)
|
|
{
|
|
_logger.LogError(ex, $"Stripe webhook error: {ex.Message}");
|
|
throw;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, $"Error processing Stripe webhook: {ex.Message}");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
private async Task ActivatePremiumAsync(string? userId, string? customerId, string? subscriptionId)
|
|
{
|
|
if (string.IsNullOrEmpty(userId)) return;
|
|
|
|
try
|
|
{
|
|
var user = await _userService.GetUserAsync(userId);
|
|
if (user == null)
|
|
{
|
|
_logger.LogWarning($"User not found for premium activation: {userId}");
|
|
return;
|
|
}
|
|
|
|
user.IsPremium = true;
|
|
user.StripeCustomerId = customerId;
|
|
user.StripeSubscriptionId = subscriptionId;
|
|
user.PremiumExpiresAt = DateTime.UtcNow.AddDays(32); // Buffer for billing cycles
|
|
|
|
await _userService.UpdateUserAsync(user);
|
|
|
|
_logger.LogInformation($"Activated premium for user {userId}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, $"Error activating premium for user {userId}: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private async Task RenewPremiumSubscriptionAsync(string subscriptionId)
|
|
{
|
|
try
|
|
{
|
|
// Find user by subscription ID
|
|
var user = await FindUserBySubscriptionIdAsync(subscriptionId);
|
|
if (user == null) return;
|
|
|
|
// Extend premium expiry
|
|
user.PremiumExpiresAt = DateTime.UtcNow.AddDays(32);
|
|
await _userService.UpdateUserAsync(user);
|
|
|
|
_logger.LogInformation($"Renewed premium subscription for user {user.Id}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, $"Error renewing premium subscription {subscriptionId}: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private async Task HandleFailedPaymentAsync(string subscriptionId)
|
|
{
|
|
try
|
|
{
|
|
var user = await FindUserBySubscriptionIdAsync(subscriptionId);
|
|
if (user == null) return;
|
|
|
|
// Don't immediately deactivate - Stripe will retry
|
|
_logger.LogWarning($"Payment failed for user {user.Id}, subscription {subscriptionId}");
|
|
|
|
// Could send notification email here
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, $"Error handling failed payment for subscription {subscriptionId}: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private async Task DeactivatePremiumAsync(Subscription subscription)
|
|
{
|
|
try
|
|
{
|
|
var user = await FindUserBySubscriptionIdAsync(subscription.Id);
|
|
if (user == null) return;
|
|
|
|
// ADICIONAR: marcar data de cancelamento
|
|
await _userService.MarkPremiumCancelledAsync(user.Id, DateTime.UtcNow);
|
|
|
|
_logger.LogInformation($"Deactivated premium for user {user.Id}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, $"Error deactivating premium for subscription {subscription.Id}: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private async Task UpdateSubscriptionAsync(Subscription subscription)
|
|
{
|
|
try
|
|
{
|
|
var user = await FindUserBySubscriptionIdAsync(subscription.Id);
|
|
if (user == null) return;
|
|
|
|
// Update based on subscription status
|
|
if (subscription.Status == "active")
|
|
{
|
|
user.IsPremium = true;
|
|
user.PremiumExpiresAt = subscription.CurrentPeriodEnd.AddDays(2); // Small buffer
|
|
}
|
|
else if (subscription.Status == "canceled" || subscription.Status == "unpaid")
|
|
{
|
|
user.IsPremium = false;
|
|
user.PremiumExpiresAt = DateTime.UtcNow;
|
|
}
|
|
|
|
await _userService.UpdateUserAsync(user);
|
|
|
|
_logger.LogInformation($"Updated subscription for user {user.Id}: {subscription.Status}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, $"Error updating subscription {subscription.Id}: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private async Task<User?> FindUserBySubscriptionIdAsync(string subscriptionId)
|
|
{
|
|
try
|
|
{
|
|
// This would require implementing a method in UserService to find by subscription ID
|
|
// For now, we'll leave this as a placeholder
|
|
return null;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, $"Error finding user by subscription ID {subscriptionId}: {ex.Message}");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public async Task<string> GetSubscriptionStatusAsync(string? subscriptionId)
|
|
{
|
|
if (string.IsNullOrEmpty(subscriptionId))
|
|
return "None";
|
|
|
|
try
|
|
{
|
|
var service = new SubscriptionService();
|
|
var subscription = await service.GetAsync(subscriptionId);
|
|
return subscription.Status;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, $"Error getting subscription status for {subscriptionId}: {ex.Message}");
|
|
return "Unknown";
|
|
}
|
|
}
|
|
|
|
public async Task<bool> CancelSubscriptionAsync(string subscriptionId)
|
|
{
|
|
try
|
|
{
|
|
var service = new SubscriptionService();
|
|
var subscription = await service.CancelAsync(subscriptionId, new SubscriptionCancelOptions
|
|
{
|
|
InvoiceNow = false,
|
|
Prorate = false
|
|
});
|
|
|
|
_logger.LogInformation($"Canceled subscription {subscriptionId}");
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, $"Error canceling subscription {subscriptionId}: {ex.Message}");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
} |