using FluentValidation; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Meezi.API.Models.Billing; using Meezi.API.Services; using Meezi.Core.Authorization; using Meezi.Core.Interfaces; using Meezi.Shared; namespace Meezi.API.Controllers; [ApiController] public class BillingController : CafeApiControllerBase { private readonly IBillingService _billing; private readonly IValidator _subscribeValidator; public BillingController(IBillingService billing, IValidator subscribeValidator) { _billing = billing; _subscribeValidator = subscribeValidator; } [Authorize] [HttpPost("api/billing/subscribe")] public async Task Subscribe( [FromBody] SubscribeRequest request, ITenantContext tenant, CancellationToken ct) { if (EnsurePermission(tenant, Permission.ManageBilling) is { } permDenied) return permDenied; if (string.IsNullOrEmpty(tenant.CafeId)) return Unauthorized(); var validation = await _subscribeValidator.ValidateAsync(request, ct); if (!validation.IsValid) { var first = validation.Errors.First(); return BadRequest(new ApiResponse(false, null, new ApiError("VALIDATION_ERROR", first.ErrorMessage, first.PropertyName))); } var (data, code, message) = await _billing.InitiateSubscriptionAsync(tenant.CafeId, request, ct); if (data is null) return BadRequest(new ApiResponse(false, null, new ApiError(code ?? "ERROR", message ?? "Failed."))); return Ok(new ApiResponse(true, data)); } [AllowAnonymous] [HttpGet("api/billing/verify")] public async Task Verify( [FromQuery] string Authority, [FromQuery] string? Status, CancellationToken ct) { var result = await _billing.VerifyZarinPalAsync(Authority, Status, ct); return Redirect(result.RedirectUrl); } [AllowAnonymous] [HttpGet("api/billing/verify/snapppay")] public async Task VerifySnappPay( [FromQuery] string? paymentToken, [FromQuery] string? state, CancellationToken ct) { var result = await _billing.VerifySnappPayAsync(paymentToken, state, ct); return Redirect(result.RedirectUrl); } [AllowAnonymous] [HttpGet("api/billing/verify/tara")] public async Task VerifyTara( [FromQuery] string? traceNumber, [FromQuery] string? status, CancellationToken ct) { var result = await _billing.VerifyTaraAsync(traceNumber, status, ct); return Redirect(result.RedirectUrl); } [Authorize] [HttpGet("api/billing/payment-methods")] public async Task PaymentMethods(CancellationToken ct) { var methods = await _billing.GetPaymentMethodsAsync(ct); return Ok(new ApiResponse>(true, methods)); } [Authorize] [HttpGet("api/billing/status")] public async Task Status(ITenantContext tenant, CancellationToken ct) { if (string.IsNullOrEmpty(tenant.CafeId) || tenant.PlanTier is null) return Unauthorized(); var data = await _billing.GetStatusAsync(tenant.CafeId, tenant.PlanTier.Value, ct); if (data is null) return NotFound(new ApiResponse(false, null, new ApiError("NOT_FOUND", "Cafe not found."))); return Ok(new ApiResponse(true, data)); } [Authorize] [HttpDelete("api/billing/queued/{paymentId}")] public async Task CancelQueued(string paymentId, ITenantContext tenant, CancellationToken ct) { if (EnsurePermission(tenant, Permission.ManageBilling) is { } permDenied) return permDenied; if (string.IsNullOrEmpty(tenant.CafeId)) return Unauthorized(); var (ok, code, message) = await _billing.CancelQueuedAsync(tenant.CafeId, paymentId, ct); if (!ok) { return code == "NOT_FOUND" ? NotFound(new ApiResponse(false, null, new ApiError(code, message ?? "Not found."))) : BadRequest(new ApiResponse(false, null, new ApiError(code ?? "ERROR", message ?? "Failed."))); } return Ok(new ApiResponse(true, new { id = paymentId })); } }