using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; namespace Fitness_core_BE.Helpers { // Mints/validates the short-lived token for the public "pay by link" page used for // platform (HexaFitness -> gym) invoices. Parallel to InvoicePaymentTokenHelper // (which is scoped to member-level CustomerInvoice) rather than a shared/generalized // helper, to avoid touching the token shape already relied on by live member links. public class PlatformInvoicePaymentTokenHelper { private readonly string _signingKey; private readonly int _expiresInDays; public PlatformInvoicePaymentTokenHelper(IConfiguration configuration) { _signingKey = configuration["InvoicePaymentToken:SigningKey"] ?? throw new InvalidOperationException("InvoicePaymentToken:SigningKey is not configured."); _expiresInDays = int.TryParse(configuration["InvoicePaymentToken:ExpiresInDays"], out var d) ? d : 7; } public string Generate(Guid clientInvoiceId) { var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_signingKey)); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var claims = new[] { new Claim("ClientInvoiceId", clientInvoiceId.ToString()), new Claim("Purpose", "PlatformInvoicePayment") }; var expires = DateTime.UtcNow.AddDays(_expiresInDays); var token = new JwtSecurityToken(claims: claims, expires: expires, signingCredentials: creds); return new JwtSecurityTokenHandler().WriteToken(token); } public Guid? ValidateAndGetInvoiceId(string token) { var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_signingKey)); var handler = new JwtSecurityTokenHandler(); try { var principal = handler.ValidateToken(token, new TokenValidationParameters { ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = true, ValidateIssuerSigningKey = true, IssuerSigningKey = key, ClockSkew = TimeSpan.FromMinutes(2) }, out _); if (principal.FindFirst("Purpose")?.Value != "PlatformInvoicePayment") return null; var idClaim = principal.FindFirst("ClientInvoiceId")?.Value; return Guid.TryParse(idClaim, out var id) ? id : null; } catch { return null; } } } }