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 used by the public "pay by link" page // (GET/POST /api/client/pay/{token}). Same self-contained HMAC approach as // RequestAccessTokenHelper, just scoped to a single CustomerInvoiceId. public class InvoicePaymentTokenHelper { private readonly string _signingKey; private readonly int _expiresInDays; public InvoicePaymentTokenHelper(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 customerInvoiceId) { var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_signingKey)); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var claims = new[] { new Claim("CustomerInvoiceId", customerInvoiceId.ToString()), new Claim("Purpose", "InvoicePayment") }; 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 != "InvoicePayment") return null; var idClaim = principal.FindFirst("CustomerInvoiceId")?.Value; return Guid.TryParse(idClaim, out var id) ? id : null; } catch { return null; } } } }