Files
Core_Be/Controllers/CustomerPaymentsController.cs
T
Dhananjaya99 bffc9b1686 first commit
2026-09-28 11:44:26 +05:30

155 lines
7.5 KiB
C#

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<ActionResult<ApiResponse<object>>> 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<ActionResult<ApiResponse<object>>> 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<ActionResult<ApiResponse<object>>> 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<IActionResult> 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<ActionResult<ApiResponse<object>>> 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<object>(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<ActionResult<ApiResponse<object>>> 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<ActionResult<ApiResponse<object>>> 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<ActionResult<ApiResponse<object>>> GetInvoiceByPaymentToken(string token)
=> Response(await _paymentService.GetInvoiceByPaymentToken(token));
[HttpPost("pay/{token}/charge")]
public async Task<ActionResult<ApiResponse<object>>> 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<ActionResult<ApiResponse<object>>> GetSubscriptionPayments(Guid id)
=> Response(await _paymentService.GetSubscriptionPayments(new { subscriptionId = id }, HttpContext));
[CustomMiddleware]
[TenantResolution]
[RequiresPermission("customer-payments")]
[HttpPost("subscriptions/{id:guid}/payments")]
public async Task<ActionResult<ApiResponse<object>>> RecordSubscriptionPayment(Guid id, [FromBody] RecordSubscriptionPaymentRequest request)
{
var payload = new Dictionary<string, object>
{
["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; }
}
}
}