using Hutopy.Web.Common.Security; using Hutopy.Web.Features.Memberships.Data; using Hutopy.Web.Features.Memberships.Infrastructure; namespace Hutopy.Web.Features.Memberships.Handlers; [PublicAPI] public record SendTipRequest( Guid CreatorId, decimal Amount, string Currency, string Message, string CheckoutSuccessUrl, string CheckoutCancelledUrl); [PublicAPI] public record SendTipResponse( string Status, string StripeCheckoutUrl); [PublicAPI] public class SendTipRequestValidator : Validator { public SendTipRequestValidator() { RuleFor(x => x.Amount) .GreaterThan(0) .WithMessage("Tip amount must be greater than 0"); RuleFor(x => x.CreatorId) .NotEmpty() .WithMessage("Creator ID is required"); RuleFor(x => x.CheckoutSuccessUrl) .NotEmpty() .WithMessage("CheckoutSuccessUrl is required"); RuleFor(x => x.CheckoutCancelledUrl) .NotEmpty() .WithMessage("CheckoutCancelledUrl is required"); } } [PublicAPI] public class SendTipHandler( MembershipDbContext dbContext, StripeService stripeService, ILogger logger) : Endpoint { public override void Configure() { Post("/api/tips"); Options(o => o.WithTags("Memberships")); AllowAnonymous(); } public override async Task HandleAsync( SendTipRequest req, CancellationToken ct) { try { var creator = await dbContext.Creators.FindAsync( [req.CreatorId], cancellationToken: ct); if (creator == null) { await SendNotFoundAsync(ct); return; } var checkoutSession = await stripeService.CreateTipCheckoutSessionAsync( creator.Id, creator.Name, req.Amount, req.Currency, req.Message, creator.StripeAccountId!, req.CheckoutSuccessUrl, req.CheckoutCancelledUrl, ct); await SendAsync( new SendTipResponse("Pending", checkoutSession.Url), cancellation: ct); } catch (Exception e) { logger.LogError(e.Message); } } }