using EntitlementBLL.Auth; using EntitlementBLL.Interfaces; using EntitlementSL.Common; using FastEndpoints; using GB5Shared.DTO.Framework.Login; using GB5Shared.DTO.Framework.ResponseStandard; using GB5Shared.FastEndPoint; using ErrorResponse = GB5Shared.Resource.Response.ErrorResponse; using static GB5Shared.GB5Constant.Constant; namespace EntitlementSL.Endpoints.Subscription; // Client-facing — Entitlement↔Payment loose coupling (Docs/Platform-Architecture-Integration- // Reference.md §9.1). A commercial action (initiating a real payment), so — mirroring // ChangeRequest/RequestChangePlan.cs's own gating — restricted to CLIENT_ADMIN with the // COMMERCIAL_ADMIN capability, not any authenticated client user. public class InitiatePayment : BaseEndpoint> { private const string ClientJwtSchemeName = "ClientJwtBearer"; private readonly ISubscriptionService _SubscriptionService; public InitiatePayment(ISubscriptionService subscriptionService) => _SubscriptionService = subscriptionService; public override void Configure() { Post("/lic/Subscription.svc/InitiatePayment"); AuthSchemes(ClientJwtSchemeName); Roles(ClientRoleCodes.ClientAdmin); } public record Params( [property: FromHeader] string Login, [property: FromBody] InitiatePaymentBody Body); public record InitiatePaymentBody( int SubscriptionId, string GatewayCode, string CustomerName, string CustomerEmail, string CustomerPhone, string CallbackUrl, // Legal/Contract Agreement Consent (tracker §51.10) — the AgreementVersionIds shown via // Legal/GetApplicableAgreements?AgreementTypeCodes=SUBSCRIPTIONTERMS and ticked to accept. int[]? AcceptedAgreementVersionIds = null); protected override string? GetCacheKey(Params req, LoginDTO loginDTO) => null; protected override async Task> ExecuteAsync(Params req, LoginDTO loginDTO, CancellationToken ct) { var caller = ClientCallerContext.FromClaims(HttpContext.User.Claims); if (caller.Role != ClientRoleCodes.ClientAdmin || caller.ClientId <= 0) return await GB5Shared.ResponseStandard.Response.CreateErrorResponse( ErrorResponse.AccessDeniedMessage, CacheKeyLevel.NOT_REQUIRED, loginDTO, statusCode: 403); if (!caller.HasCapability(EntitlementClientCapabilityCodes.CommercialAdmin)) return await GB5Shared.ResponseStandard.Response.CreateErrorResponse( ErrorResponse.AccessDeniedMessage, CacheKeyLevel.NOT_REQUIRED, loginDTO, statusCode: 403); // Defense in depth beyond the caller's own SubscriptionId claim — GetAsync(clientId) // is the caller's own subscription; reject outright rather than trust a client-supplied // SubscriptionId that might belong to a different tenant. var mySub = await _SubscriptionService.GetAsync(caller.ClientId, ct); if (mySub.SubscriptionId != req.Body.SubscriptionId) return await GB5Shared.ResponseStandard.Response.CreateErrorResponse( ErrorResponse.AccessDeniedMessage, CacheKeyLevel.NOT_REQUIRED, loginDTO, statusCode: 403); var result = await _SubscriptionService.InitiatePaymentAsync(new EntitlementBLL.Interfaces.InitiatePaymentRequest { SubscriptionId = req.Body.SubscriptionId, GatewayCode = req.Body.GatewayCode, CustomerName = req.Body.CustomerName, CustomerEmail = req.Body.CustomerEmail, CustomerPhone = req.Body.CustomerPhone, CallbackUrl = req.Body.CallbackUrl, AcceptedAgreementVersionIds = req.Body.AcceptedAgreementVersionIds ?? Array.Empty() }, loginDTO.UserId, ct); return await GB5Shared.ResponseStandard.Response.CreateSuccessResponse(result, CacheKeyLevel.NOT_REQUIRED, loginDTO); } }