using System.Security.Claims; using FastEndpoints; using FluentValidation; using TrackQrApi.Features.Auth.Common; using TrackQrApi.Features.Billing.Common; using TrackQrApi.Features.Billing.Services; namespace TrackQrApi.Features.Billing.Endpoints; public class CreatePortalSessionValidator : Validator { public CreatePortalSessionValidator() { RuleFor(x => x.ReturnUrl).NotEmpty().Must(BeValidUrl); } private static bool BeValidUrl(string url) { return Uri.TryCreate(url, UriKind.Absolute, out var uri) && (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps); } } public class CreatePortalSessionEndpoint(IStripeService stripeService) : Endpoint { public override void Configure() { Post("/billing/portal"); } public override async Task HandleAsync(PortalSessionRequest req, CancellationToken ct) { var userId = Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!); try { var portalUrl = await stripeService.CreateCustomerPortalSessionAsync( userId, req.ReturnUrl, ct); await HttpContext.Response.SendAsync(new PortalSessionResponse(portalUrl), cancellation: ct); } catch (InvalidOperationException ex) { await HttpContext.Response.SendAsync( new MessageResponse(ex.Message), 400, cancellation: ct); } catch (Exception ex) { await HttpContext.Response.SendAsync( new MessageResponse($"Failed to create portal session: {ex.Message}"), 500, cancellation: ct); } } }