first commit
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user