// 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);
}
}
}