using CMSMicroservice.Application.Common.Interfaces; using CMSMicroservice.Application.DiscountShopCQ.Commands.CompleteOrderPayment; using CMSMicroservice.Application.WalletCQ.Commands.VerifyDiscountWalletCharge; using CMSMicroservice.Application.WalletCQ.Commands.VerifyMagicWalletCharge; using MediatR; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace CMSMicroservice.WebApi.Controllers; /// /// Callback endpoint for payment gateways (ZarinPal, etc.) /// درگاه پرداخت بعد از پرداخت (یا لغو) کاربر را به اینجا redirect می‌کند /// [ApiController] [AllowAnonymous] // کاربر از درگاه بانک برمی‌گردد — JWT ندارد [ApiExplorerSettings(GroupName = "cms")] public class PaymentCallbackController : ControllerBase { private readonly ISender _sender; private readonly IPaymentGatewayService _paymentGateway; private readonly IApplicationDbContext _context; private readonly IConfiguration _configuration; private readonly ILogger _logger; public PaymentCallbackController( ISender sender, IPaymentGatewayService paymentGateway, IApplicationDbContext context, IConfiguration configuration, ILogger logger) { _sender = sender; _paymentGateway = paymentGateway; _context = context; _configuration = configuration; _logger = logger; } /// /// Callback برای پرداخت سفارش فروشگاه تخفیفی /// زرین‌پال کاربر را با Authority و Status به این endpoint برمی‌گرداند /// [HttpGet("/api/payment/discount-order/callback")] public async Task DiscountOrderCallback( [FromQuery] long orderId, [FromQuery(Name = "Authority")] string? authority, [FromQuery(Name = "Status")] string? status, CancellationToken cancellationToken) { var frontOfficeBaseUrl = _configuration["FrontOfficeBaseUrl"] ?? "https://localhost:5268"; _logger.LogInformation( "Payment callback received: OrderId={OrderId}, Authority={Authority}, Status={Status}", orderId, authority, status); try { // پیدا کردن سفارش و تراکنش var order = await _context.DiscountOrders .Include(o => o.OrderDetails) .FirstOrDefaultAsync(o => o.Id == orderId, cancellationToken); if (order == null) { _logger.LogError("Payment callback: Order #{OrderId} not found", orderId); return Redirect($"{frontOfficeBaseUrl}/discount-store/orders?error=order-not-found"); } var transaction = order.TransactionId.HasValue ? await _context.Transactions.FirstOrDefaultAsync( t => t.Id == order.TransactionId.Value, cancellationToken) : null; // تأیید پرداخت از درگاه bool paymentSuccess = false; string? refId = null; if (string.Equals(status, "OK", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(authority)) { // Verify با مبلغ از دیتابیس (تومان) var verifyResult = await _paymentGateway.VerifyPaymentAsync( authority, status!, order.GatewayAmountPaid, // مبلغ به تومان cancellationToken); paymentSuccess = verifyResult.IsSuccess; refId = verifyResult.TrackingCode ?? verifyResult.RefId; // آپدیت PaymentTransaction با نتیجه verify var paymentTx = await _context.PaymentTransactions .FirstOrDefaultAsync(pt => pt.Authority == authority, cancellationToken); if (paymentTx != null) { paymentTx.PaymentStatus = verifyResult.IsSuccess; paymentTx.VerificationStatusCode = verifyResult.VerificationCode; paymentTx.VerificationStatusMessage = verifyResult.Message; paymentTx.CardPan = verifyResult.CardPan; paymentTx.CardHash = verifyResult.CardHash; paymentTx.RefId = verifyResult.TrackingCode; await _context.SaveChangesAsync(cancellationToken); } _logger.LogInformation( "Payment verification for Order #{OrderId}: Success={Success}, RefId={RefId}, Message={Message}", orderId, paymentSuccess, refId, verifyResult.Message); } else { _logger.LogWarning("Payment cancelled by user for Order #{OrderId}", orderId); } // تکمیل سفارش از طریق CQRS var completeResult = await _sender.Send(new CompleteOrderPaymentCommand { OrderId = orderId, TransactionId = transaction?.Id ?? 0, PaymentSuccess = paymentSuccess, RefId = refId }, cancellationToken); // Redirect به FrontOffice if (paymentSuccess && completeResult.Success) { _logger.LogInformation("Payment completed successfully for Order #{OrderId}", orderId); return Redirect( $"{frontOfficeBaseUrl}/discount-store/order/{orderId}?payment=success"); } else { _logger.LogWarning("Payment failed for Order #{OrderId}", orderId); return Redirect( $"{frontOfficeBaseUrl}/discount-store/order/{orderId}?payment=failed"); } } catch (Exception ex) { _logger.LogError(ex, "Payment callback error for Order #{OrderId}", orderId); return Redirect( $"{frontOfficeBaseUrl}/discount-store/order/{orderId}?payment=error"); } } /// /// Callback برای شارژ کیف‌پول جادویی — زرین‌پال بعد از پرداخت کاربر را اینجا برمی‌گرداند /// [HttpGet("/api/wallet/verify-magic-charge")] public async Task MagicChargeCallback( [FromQuery(Name = "Authority")] string? authority, [FromQuery(Name = "Status")] string? status, CancellationToken cancellationToken) { var frontOfficeBaseUrl = _configuration["FrontOfficeBaseUrl"] ?? "https://localhost:5268"; _logger.LogInformation( "Magic charge callback received: Authority={Authority}, Status={Status}", authority, status); try { if (string.IsNullOrEmpty(authority)) { _logger.LogError("Magic charge callback: Authority is missing"); return Redirect($"{frontOfficeBaseUrl}/magic-wallet?payment=error&reason=no-authority"); } var result = await _sender.Send(new VerifyMagicWalletChargeCommand { Authority = authority, Status = status ?? "NOK" }, cancellationToken); _logger.LogInformation( "Magic charge completed successfully. Authority={Authority}", authority); return Redirect($"{frontOfficeBaseUrl}/magic-wallet?payment=success"); } catch (Exception ex) { _logger.LogError(ex, "Magic charge callback error. Authority={Authority}", authority); return Redirect($"{frontOfficeBaseUrl}/magic-wallet?payment=failed"); } } /// /// Callback برای شارژ کیف‌پول تخفیفی — زرین‌پال بعد از پرداخت کاربر را اینجا برمی‌گرداند /// [HttpGet("/api/wallet/verify-discount-charge")] public async Task DiscountChargeCallback( [FromQuery(Name = "Authority")] string? authority, [FromQuery(Name = "Status")] string? status, CancellationToken cancellationToken) { var frontOfficeBaseUrl = _configuration["FrontOfficeBaseUrl"] ?? "https://localhost:5268"; _logger.LogInformation( "Discount charge callback received: Authority={Authority}, Status={Status}", authority, status); try { if (string.IsNullOrEmpty(authority)) { _logger.LogError("Discount charge callback: Authority is missing"); return Redirect($"{frontOfficeBaseUrl}/profile/charge-discount-wallet?payment=error&reason=no-authority"); } // پیدا کردن PaymentTransaction برای استخراج UserId و Amount var paymentTx = await _context.PaymentTransactions .FirstOrDefaultAsync(pt => pt.Authority == authority, cancellationToken); if (paymentTx == null || !paymentTx.UserId.HasValue) { _logger.LogError("Discount charge callback: PaymentTransaction not found for Authority={Authority}", authority); return Redirect($"{frontOfficeBaseUrl}/profile/charge-discount-wallet?payment=error&reason=tx-not-found"); } if (!string.Equals(status, "OK", StringComparison.OrdinalIgnoreCase)) { _logger.LogWarning("Discount charge cancelled by user. Authority={Authority}", authority); return Redirect($"{frontOfficeBaseUrl}/profile/charge-discount-wallet?payment=cancelled"); } var result = await _sender.Send(new VerifyDiscountWalletChargeCommand { UserId = paymentTx.UserId.Value, Amount = paymentTx.Amount, Authority = authority }, cancellationToken); _logger.LogInformation( "Discount charge completed successfully. Authority={Authority}, UserId={UserId}", authority, paymentTx.UserId.Value); return Redirect($"{frontOfficeBaseUrl}/profile/charge-discount-wallet?payment=success"); } catch (Exception ex) { _logger.LogError(ex, "Discount charge callback error. Authority={Authority}", authority); return Redirect($"{frontOfficeBaseUrl}/profile/charge-discount-wallet?payment=failed"); } } }