using Fitness_core_BE.Helpers; using Fitness_core_BE.Interfaces.Services; using Fitness_core_BE.Models.IOs; using Microsoft.AspNetCore.Mvc; using static Fitness_core_BE.Helpers.JwtMiddleware; using static Fitness_core_BE.Helpers.ApiResponseHelper; namespace Fitness_core_BE.Controllers { // Invoices billed to gym members (CustomerInvoice) and their payments — distinct // from ClientInvoiceController, which handles the gym owner's own platform bill. [ApiController] [Route("api/client")] public class CustomerPaymentsController : ControllerBase { private readonly ICustomerInvoiceService _customerInvoiceService; private readonly IPaymentService _paymentService; private readonly IReceiptService _receiptService; private readonly UserRoleHelper _userRoleHelper; public CustomerPaymentsController( ICustomerInvoiceService customerInvoiceService, IPaymentService paymentService, IReceiptService receiptService, UserRoleHelper userRoleHelper) { _customerInvoiceService = customerInvoiceService; _paymentService = paymentService; _receiptService = receiptService; _userRoleHelper = userRoleHelper; } // scope=latest: one row per customer (their latest approved invoice) -- used by the // regular invoices tab now that a customer can have older Paid/RolledOver/Cancelled // invoices sitting alongside their current one. Omitted keeps the full, unfiltered // history for any other existing caller of this endpoint. [CustomMiddleware] [TenantResolution] [RequiresPermission("customer-payments")] [HttpGet("member-invoices")] public async Task>> GetMemberInvoices([FromQuery] string? scope = null) => Response(await _customerInvoiceService.GetCustomerInvoicesByClientId(new { scope }, HttpContext)); [CustomMiddleware] [TenantResolution] [RequiresPermission("customer-payments")] [HttpGet("member-invoices/{id:guid}")] public async Task>> GetMemberInvoice(Guid id) => Response(await _customerInvoiceService.GetCustomerInvoiceById(new { customerInvoiceId = id }, HttpContext)); // Full invoice history for one customer (previous cycles) -- used by the regular // invoices tab's "view previous invoices" action, since the tab itself now only shows // each customer's latest (see scope=latest above). [CustomMiddleware] [TenantResolution] [RequiresPermission("customer-payments")] [HttpGet("member-invoices/customer/{customerId:guid}")] public async Task>> GetMemberInvoicesForCustomer(Guid customerId) => Response(await _customerInvoiceService.GetCustomerInvoicesByCustomerId(new { customerId }, HttpContext)); // Branded "GYM OS" receipt PDF for a member invoice (download). [CustomMiddleware] [TenantResolution] [RequiresPermission("customer-payments")] [HttpGet("member-invoices/{id:guid}/receipt.pdf")] public async Task DownloadReceipt(Guid id) { var clientId = await _userRoleHelper.GetClientId(DataExtractor.GetUserIdFromContext(HttpContext)); var model = await _receiptService.BuildForInvoiceAsync(id, requireClientId: clientId); var pdf = _receiptService.GeneratePdf(model); return File(pdf, "application/pdf", $"{model.ReceiptNo}.pdf"); } // Manually (re)send the receipt to the member via email + SMS. [CustomMiddleware] [TenantResolution] [RequiresPermission("customer-payments")] [HttpPost("member-invoices/{id:guid}/send-receipt")] public async Task>> SendReceipt(Guid id) { var clientId = await _userRoleHelper.GetClientId(DataExtractor.GetUserIdFromContext(HttpContext)); var model = await _receiptService.BuildForInvoiceAsync(id, requireClientId: clientId); await _receiptService.SendReceiptAsync(model); return Response(new { success = true, message = "Receipt sent" }); } // "Charge now" — attempt to auto-deduct from the member's consented saved card. [CustomMiddleware] [TenantResolution] [RequiresPermission("customer-payments")] [HttpPost("member-invoices/{id:guid}/auto-charge")] public async Task>> AttemptAutoCharge(Guid id) => Response(await _paymentService.AttemptAutoCharge(id, HttpContext)); // Email/SMS the member a link to pay this invoice online themselves. [CustomMiddleware] [TenantResolution] [RequiresPermission("customer-payments")] [HttpPost("member-invoices/{id:guid}/payment-link")] public async Task>> SendPaymentLink(Guid id) => Response(await _paymentService.SendPaymentLink(id, HttpContext)); // ---------- Public pay-by-link page (anonymous; the token itself is the authorization) ---------- [HttpGet("pay/{token}")] public async Task>> GetInvoiceByPaymentToken(string token) => Response(await _paymentService.GetInvoiceByPaymentToken(token)); [HttpPost("pay/{token}/charge")] public async Task>> ChargeInvoiceByPaymentToken(string token, [FromBody] PayByLinkChargeRequest request) => Response(await _paymentService.ChargeInvoiceByPaymentToken(token, new { cardToken = request.CardToken })); // ---------- Subscription payments ---------- [CustomMiddleware] [TenantResolution] [RequiresPermission("customer-payments")] [HttpGet("subscriptions/{id:guid}/payments")] public async Task>> GetSubscriptionPayments(Guid id) => Response(await _paymentService.GetSubscriptionPayments(new { subscriptionId = id }, HttpContext)); [CustomMiddleware] [TenantResolution] [RequiresPermission("customer-payments")] [HttpPost("subscriptions/{id:guid}/payments")] public async Task>> RecordSubscriptionPayment(Guid id, [FromBody] RecordSubscriptionPaymentRequest request) { var payload = new Dictionary { ["subscriptionId"] = id, ["amount"] = request.Amount, ["paymentMethod"] = request.PaymentMethod }; if (request.Note != null) payload["note"] = request.Note; if (request.BankRefNo != null) payload["bankRefNo"] = request.BankRefNo; return Response(await _paymentService.RecordSubscriptionPayment(payload, HttpContext)); } public class PayByLinkChargeRequest { public string CardToken { get; set; } = string.Empty; } public class RecordSubscriptionPaymentRequest { public decimal Amount { get; set; } public string? Note { get; set; } // "Cash" | "Bank" -- required so the owner picks one instead of it being implicitly cash. public string PaymentMethod { get; set; } = string.Empty; // Optional reference/slip number, relevant when PaymentMethod is "Bank". public string? BankRefNo { get; set; } } } }