using Hutopy.Infrastructure.Security; using Hutopy.Modules.Creators.Contracts; using Hutopy.Modules.Memberships.Contracts; using Hutopy.Modules.Memberships.Data; namespace Hutopy.Modules.Memberships.Handlers; [PublicAPI] public class SubscribeRequest { public Guid CreatorId { get; set; } public Guid MembershipTierId { get; set; } public required string CheckoutSuccessUrl { get; init; } public required string CheckoutCancelledUrl { get; init; } } [PublicAPI] public record struct SubscriptionResponse( string StripeCheckoutUrl); [PublicAPI] public class SubscribeValidator : Validator { public SubscribeValidator() { RuleFor(x => x.MembershipTierId).NotEmpty(); } } [PublicAPI] public class SubscribeHandler( MembershipsDbContext dbContext, ICreatorLookup creatorLookup, IMembershipPaymentProcessor membershipPaymentProcessor) : Endpoint { public override void Configure() { Post("/api/memberships/subscribe"); Options(o => o.WithTags("Memberships")); } public override async Task HandleAsync( SubscribeRequest req, CancellationToken ct) { MembershipTier? tier = await dbContext .MembershipTiers .Where(tier => tier.Id == req.MembershipTierId) .FirstOrDefaultAsync(ct); if (tier == null) { await SendNotFoundAsync(ct); return; } CreatorReference? creator = await creatorLookup.GetCreatorAsync(tier.CreatorId, ct); if (creator == null) { await SendNotFoundAsync(ct); return; } if (!creator.AcceptCharges) { await SendErrorsAsync(StatusCodes.Status400BadRequest, ct); return; } // Process Stripe subscription MembershipCheckoutSession checkoutSession = await membershipPaymentProcessor.CreateCheckoutSessionAsync( User.GetUserId(), creator, tier.Id, tier.StripePriceId, req.CheckoutSuccessUrl, req.CheckoutCancelledUrl); await SendOkAsync( new SubscriptionResponse { StripeCheckoutUrl = checkoutSession.Url }, ct); } }