using Microsoft.AspNetCore.Mvc; using PetWash.Api.Models; using PetWash.Api.Services; namespace PetWash.Api.Controllers; [ApiController] [Route("api/[controller]")] public class OrdersController : ControllerBase { private readonly OrderService _orderService; private readonly WeChatPayService _weChatPayService; public OrdersController(OrderService orderService, WeChatPayService weChatPayService) { _orderService = orderService; _weChatPayService = weChatPayService; } [HttpPost] public async Task CreateOrder([FromBody] CreateOrderRequest request) { Order? order = null; try { order = await _orderService.GetLatestRetryableOrderAsync(request.PackageId); if (order is not null && HasActivePayment(order)) { return Ok(ToCreateOrderResponse(order)); } order ??= await _orderService.CreateOrderAsync(request.PackageId); var payment = await _weChatPayService.CreateNativePayAsync(order, HttpContext.RequestAborted); order = await _orderService.MarkPaymentReadyAsync( order.Id, payment.OutTradeNo, payment.CodeUrl, payment.ExpiresAt) ?? order; return Ok(ToCreateOrderResponse(order)); } catch (ArgumentException ex) { return BadRequest(ex.Message); } catch (InvalidOperationException ex) { if (order != null) { await _orderService.MarkPaymentInitializationFailedAsync(order.Id, ex.Message); } return StatusCode(StatusCodes.Status502BadGateway, ex.Message); } } [HttpPost("{id}/payment-qrcode")] public async Task CreatePaymentQrCode(int id) { var order = await _orderService.GetOrderAsync(id); if (order == null) { return NotFound(); } if (HasActivePayment(order)) { return Ok(ToCreateOrderResponse(order)); } try { var payment = await _weChatPayService.CreateNativePayAsync(order, HttpContext.RequestAborted); order = await _orderService.MarkPaymentReadyAsync( order.Id, payment.OutTradeNo, payment.CodeUrl, payment.ExpiresAt) ?? order; return Ok(ToCreateOrderResponse(order)); } catch (InvalidOperationException ex) { await _orderService.MarkPaymentInitializationFailedAsync(order.Id, ex.Message); return StatusCode(StatusCodes.Status502BadGateway, ex.Message); } } [HttpPost("{id}/payment")] public async Task ConfirmPayment(int id) { var order = await _orderService.ConfirmPaymentAsync(id); if (order == null) return NotFound(); return Ok(order); } [HttpGet("{id}/payment-status")] public async Task GetPaymentStatus(int id, [FromQuery] string outTradeNo) { if (string.IsNullOrWhiteSpace(outTradeNo)) { return BadRequest("outTradeNo is required."); } var order = await _orderService.GetOrderAsync(id); if (order == null) { return NotFound(); } if (order.IsPaid) { if (order.Status == OrderStatus.Paid) { await _orderService.EnsureOpenDoorCommandDispatchedAsync(id); order = await _orderService.GetOrderAsync(id) ?? order; } return Ok(new PaymentStatusResponse { Order = order, IsPaid = true, TradeState = "SUCCESS", OutTradeNo = outTradeNo, Message = "Order already confirmed." }); } try { var payment = await _weChatPayService.QueryOrderByOutTradeNoAsync(outTradeNo, HttpContext.RequestAborted); if (string.Equals(payment.TradeState, "SUCCESS", StringComparison.OrdinalIgnoreCase)) { order = await _orderService.ConfirmPaymentAsync(id) ?? order; } return Ok(new PaymentStatusResponse { Order = order, IsPaid = order.IsPaid, TradeState = payment.TradeState, OutTradeNo = payment.OutTradeNo, TransactionId = payment.TransactionId, Message = order.IsPaid ? "Payment confirmed." : "Waiting for payment." }); } catch (InvalidOperationException ex) { return StatusCode(StatusCodes.Status502BadGateway, ex.Message); } } [HttpGet("{id}")] public async Task GetOrder(int id) { var order = await _orderService.GetOrderAsync(id); if (order == null) return NotFound(); return Ok(order); } [HttpPut("{id}/status")] public async Task UpdateStatus(int id, [FromBody] UpdateStatusRequest request) { var order = await _orderService.UpdateOrderStatusAsync(id, request.Status); if (order == null) return NotFound(); return Ok(order); } private static bool HasActivePayment(Order? order) { return order is { IsPaid: false, Status: OrderStatus.WaitingPayment } && !string.IsNullOrWhiteSpace(order.PaymentCodeUrl) && !string.IsNullOrWhiteSpace(order.OutTradeNo) && order.PaymentExpiresAt is not null && order.PaymentExpiresAt > DateTimeOffset.UtcNow; } private static CreateOrderResponse ToCreateOrderResponse(Order order) { return new CreateOrderResponse { Order = order, CodeUrl = order.PaymentCodeUrl, OutTradeNo = order.OutTradeNo, ExpiresAt = order.PaymentExpiresAt }; } } public record CreateOrderRequest(int PackageId); public record UpdateStatusRequest(OrderStatus Status);