using Stripe; using Stripe.Checkout; namespace QuestionsApi.Services; public sealed record StripeCheckoutResult(string SessionId, string CheckoutUrl); public interface IStripeDonationGateway { Task CreateCheckoutAsync(string donationId, long amountCents, CancellationToken cancellationToken); } public sealed class StripeDonationGateway(IConfiguration configuration) : IStripeDonationGateway { public async Task CreateCheckoutAsync(string donationId, long amountCents, CancellationToken cancellationToken) { var secretKey = configuration["Stripe:SecretKey"]; if (string.IsNullOrWhiteSpace(secretKey)) throw new InvalidOperationException("Stripe secret key is not configured."); var successUrl = configuration["Stripe:SuccessUrl"] ?? "https://doctor.ranaze.com/donation-complete.html?session_id={CHECKOUT_SESSION_ID}"; var cancelUrl = configuration["Stripe:CancelUrl"] ?? "https://doctor.ranaze.com/donation-canceled.html"; var client = new StripeClient(secretKey); var service = new SessionService(client); var metadata = new Dictionary { ["donation_id"] = donationId }; var session = await service.CreateAsync(new SessionCreateOptions { Mode = "payment", SuccessUrl = successUrl, CancelUrl = cancelUrl, ClientReferenceId = donationId, CustomerCreation = "always", BillingAddressCollection = "auto", Metadata = metadata, PaymentIntentData = new SessionPaymentIntentDataOptions { Metadata = metadata }, LineItems = [ new SessionLineItemOptions { Quantity = 1, PriceData = new SessionLineItemPriceDataOptions { Currency = "usd", UnitAmount = amountCents, ProductData = new SessionLineItemPriceDataProductDataOptions { Name = "Support RanaZe: Step 3 Trainer", Description = "One-time contribution supporting RanaZe development and medical education content." } } } ] }, null, cancellationToken); if (string.IsNullOrWhiteSpace(session.Id) || string.IsNullOrWhiteSpace(session.Url)) throw new InvalidOperationException("Stripe did not return a checkout URL."); return new StripeCheckoutResult(session.Id, session.Url); } }