// EN: Handler for PayOrderCommand — routes to cash, card, or online payment flow. // VI: Handler cho PayOrderCommand — điều hướng đến thanh toán tiền mặt, thẻ, hoặc trực tuyến. using MediatR; using OrderService.API.Hubs; using OrderService.Domain.AggregatesModel.OrderAggregate; using OrderService.Domain.Exceptions; using OrderService.Infrastructure.ExternalServices; namespace OrderService.API.Application.Commands; /// /// EN: Handler for processing payment based on payment method. /// VI: Handler xử lý thanh toán dựa trên phương thức thanh toán. /// public class PayOrderCommandHandler : IRequestHandler { private readonly IOrderRepository _orderRepository; private readonly IWalletServiceClient _walletServiceClient; private readonly IPosNotificationService _posNotificationService; private readonly ILogger _logger; public PayOrderCommandHandler( IOrderRepository orderRepository, IWalletServiceClient walletServiceClient, IPosNotificationService posNotificationService, ILogger logger) { _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); _walletServiceClient = walletServiceClient ?? throw new ArgumentNullException(nameof(walletServiceClient)); _posNotificationService = posNotificationService ?? throw new ArgumentNullException(nameof(posNotificationService)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task Handle( PayOrderCommand request, CancellationToken cancellationToken) { _logger.LogInformation( "EN: Processing {PaymentMethod} payment for order {OrderId} / VI: Xử lý thanh toán {PaymentMethod} cho order {OrderId}", request.PaymentMethod, request.OrderId); // EN: Load order / VI: Load order var order = await _orderRepository.GetByIdAsync(request.OrderId, cancellationToken); if (order == null) { throw new DomainException($"Order not found: {request.OrderId}"); } // EN: Verify shop ownership / VI: Xác minh quyền sở hữu shop if (order.ShopId != request.ShopId) { throw new DomainException("Order does not belong to this shop"); } var method = request.PaymentMethod.ToLowerInvariant(); return method switch { "cash" => await ProcessCashPayment(order, request, cancellationToken), "card" => await ProcessCardPayment(order, request, cancellationToken), "vnpay" or "momo" => await ProcessOnlinePayment(order, request, method, cancellationToken), // EN: Default — treat "qr" and "transfer" as immediate like card (POS-confirmed) // VI: Mặc định — xử lý "qr" và "transfer" như thẻ (POS xác nhận) _ => await ProcessCardPayment(order, request, cancellationToken), }; } /// /// EN: Process cash payment — instant, calculate change. /// VI: Xử lý thanh toán tiền mặt — ngay lập tức, tính tiền thối. /// private async Task ProcessCashPayment( Order order, PayOrderCommand request, CancellationToken cancellationToken) { var amountTendered = request.AmountTendered ?? order.TotalAmount; if (amountTendered < order.TotalAmount) { return new PayOrderResult( false, order.Status.Name, null, null, null, $"Insufficient cash: tendered {amountTendered}, required {order.TotalAmount}"); } var transactionId = $"CASH-{DateTime.UtcNow:yyyyMMddHHmmss}-{Guid.NewGuid().ToString("N")[..8].ToUpper()}"; var changeAmount = amountTendered - order.TotalAmount; // EN: Mark as paid and processing / VI: Đánh dấu đã thanh toán và đang xử lý order.MarkAsPaid("cash", transactionId, amountTendered); order.MarkAsProcessing(); await _orderRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); _logger.LogInformation( "EN: Cash payment completed for order {OrderId}, change: {Change} / VI: Thanh toán tiền mặt hoàn tất cho order {OrderId}, tiền thối: {Change}", order.Id, changeAmount); // EN: Send real-time payment notification / VI: Gửi thông báo thanh toán real-time await SendPaymentNotificationAsync(order, "cash", cancellationToken); return new PayOrderResult(true, order.Status.Name, null, changeAmount, transactionId, null); } /// /// EN: Process card payment — instant (terminal handles actual charge). /// VI: Xử lý thanh toán thẻ — ngay lập tức (terminal xử lý giao dịch thực). /// private async Task ProcessCardPayment( Order order, PayOrderCommand request, CancellationToken cancellationToken) { var transactionId = $"CARD-{DateTime.UtcNow:yyyyMMddHHmmss}-{Guid.NewGuid().ToString("N")[..8].ToUpper()}"; // EN: Mark as paid and processing / VI: Đánh dấu đã thanh toán và đang xử lý order.MarkAsPaid(request.PaymentMethod.ToLowerInvariant(), transactionId); order.MarkAsProcessing(); await _orderRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); _logger.LogInformation( "EN: Card payment completed for order {OrderId} / VI: Thanh toán thẻ hoàn tất cho order {OrderId}", order.Id); // EN: Send real-time payment notification / VI: Gửi thông báo thanh toán real-time await SendPaymentNotificationAsync(order, request.PaymentMethod.ToLowerInvariant(), cancellationToken); return new PayOrderResult(true, order.Status.Name, null, null, transactionId, null); } /// /// EN: Process online payment (VNPay/Momo) — creates payment via wallet-service, returns redirect URL. /// VI: Xử lý thanh toán trực tuyến (VNPay/Momo) — tạo payment qua wallet-service, trả về URL redirect. /// private async Task ProcessOnlinePayment( Order order, PayOrderCommand request, string gateway, CancellationToken cancellationToken) { try { var paymentResponse = await _walletServiceClient.CreatePaymentAsync( order.Id, order.TotalAmount, gateway, request.ReturnUrl ?? "", request.IpAddress ?? "127.0.0.1", cancellationToken); if (paymentResponse == null || string.IsNullOrEmpty(paymentResponse.PaymentUrl)) { return new PayOrderResult( false, order.Status.Name, null, null, null, "Failed to create online payment / Không thể tạo thanh toán trực tuyến"); } // EN: Mark order as payment pending / VI: Đánh dấu order chờ thanh toán order.MarkAsPaymentPending(gateway, paymentResponse.TransactionId); await _orderRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); _logger.LogInformation( "EN: Online payment initiated for order {OrderId} via {Gateway}, txn: {TxnId} / VI: Thanh toán trực tuyến đã khởi tạo cho order {OrderId} qua {Gateway}, giao dịch: {TxnId}", order.Id, gateway, paymentResponse.TransactionId); return new PayOrderResult( true, order.Status.Name, paymentResponse.PaymentUrl, null, paymentResponse.TransactionId, null); } catch (Exception ex) { _logger.LogError(ex, "EN: Failed to create online payment for order {OrderId} / VI: Không thể tạo thanh toán trực tuyến cho order {OrderId}", order.Id); return new PayOrderResult( false, order.Status.Name, null, null, null, $"Payment gateway error: {ex.Message}"); } } /// /// EN: Send real-time payment notification to POS/KDS clients. /// VI: Gửi thông báo thanh toán real-time đến POS/KDS clients. /// private async Task SendPaymentNotificationAsync( Order order, string paymentMethod, CancellationToken cancellationToken) { try { var payment = new PaymentNotificationDto( OrderId: order.Id, Amount: order.TotalAmount, Method: paymentMethod, Status: "Completed"); await _posNotificationService.NotifyPaymentCompletedAsync( order.ShopId, payment, cancellationToken); await _posNotificationService.NotifyOrderStatusChangedAsync( order.ShopId, order.Id, "Validated", order.Status.Name, cancellationToken); } catch (Exception ex) { // EN: Don't fail the command if notification fails // VI: Không fail command nếu notification thất bại _logger.LogWarning(ex, "EN: Failed to send payment notification for order {OrderId} / " + "VI: Gửi thông báo thanh toán thất bại cho order {OrderId}", order.Id); } } }