Files
petwash/PetWash.Api/Services/OrderService.cs
GukSang.Jin ac05493177 更新
2026-03-20 16:23:56 +08:00

208 lines
6.6 KiB
C#

using Microsoft.EntityFrameworkCore;
using PetWash.Api.Data;
using PetWash.Api.Models;
namespace PetWash.Api.Services;
public class OrderService
{
private static readonly OrderStatus[] RetryablePaymentStatuses =
[
OrderStatus.Created,
OrderStatus.WaitingPayment,
OrderStatus.Expired,
OrderStatus.PaymentInitFailed
];
private readonly PetWashDbContext _context;
private readonly MqttService _mqttService;
private readonly ILogger<OrderService> _logger;
public OrderService(PetWashDbContext context, MqttService mqttService, ILogger<OrderService> logger)
{
_context = context;
_mqttService = mqttService;
_logger = logger;
}
public async Task<Order> CreateOrderAsync(int packageId)
{
var package = await _context.Packages.FindAsync(packageId);
if (package == null)
throw new ArgumentException("套餐不存在");
var order = new Order
{
PackageId = packageId,
Package = package,
CreatedAt = DateTime.Now,
Status = OrderStatus.Created,
IsPaid = false,
OutTradeNo = string.Empty,
PaymentCodeUrl = string.Empty,
PaymentExpiresAt = null,
PaymentInitError = string.Empty
};
_context.Orders.Add(order);
await _context.SaveChangesAsync();
_logger.LogInformation($"订单创建成功: OrderId={order.Id}, PackageId={packageId}");
return order;
}
public async Task<Order?> GetLatestRetryableOrderAsync(int packageId)
{
var retryCutoff = DateTime.Now.AddMinutes(-30);
return await _context.Orders
.Include(o => o.Package)
.Where(o =>
o.PackageId == packageId &&
!o.IsPaid &&
o.CreatedAt >= retryCutoff &&
RetryablePaymentStatuses.Contains(o.Status))
.OrderByDescending(o => o.Id)
.FirstOrDefaultAsync();
}
public async Task<Order?> MarkPaymentReadyAsync(
int orderId,
string outTradeNo,
string codeUrl,
DateTimeOffset? expiresAt)
{
var order = await _context.Orders.Include(o => o.Package).FirstOrDefaultAsync(o => o.Id == orderId);
if (order == null)
{
return null;
}
order.Status = OrderStatus.WaitingPayment;
order.OutTradeNo = outTradeNo;
order.PaymentCodeUrl = codeUrl;
order.PaymentExpiresAt = expiresAt;
order.PaymentInitError = string.Empty;
await _context.SaveChangesAsync();
_logger.LogInformation(
"订单支付初始化成功: OrderId={OrderId}, OutTradeNo={OutTradeNo}, ExpiresAt={ExpiresAt}",
order.Id,
order.OutTradeNo,
order.PaymentExpiresAt);
return order;
}
public async Task<Order?> MarkPaymentInitializationFailedAsync(int orderId, string errorMessage)
{
var order = await _context.Orders.Include(o => o.Package).FirstOrDefaultAsync(o => o.Id == orderId);
if (order == null)
{
return null;
}
order.Status = OrderStatus.PaymentInitFailed;
order.PaymentCodeUrl = string.Empty;
order.PaymentExpiresAt = null;
order.PaymentInitError = errorMessage;
await _context.SaveChangesAsync();
_logger.LogWarning(
"订单支付初始化失败: OrderId={OrderId}, Error={Error}",
order.Id,
errorMessage);
return order;
}
public async Task<Order?> ConfirmPaymentAsync(int orderId)
{
var order = await _context.Orders.Include(o => o.Package).FirstOrDefaultAsync(o => o.Id == orderId);
if (order == null) return null;
if (order.IsPaid)
{
if (order.Status == OrderStatus.Paid)
{
await PublishOpenDoorCommandAsync(order);
_logger.LogInformation("订单已支付但尚未开门,补发开门指令: OrderId={OrderId}", orderId);
}
_logger.LogInformation("Order payment already confirmed: OrderId={OrderId}", orderId);
return order;
}
order.IsPaid = true;
order.PaidAt = DateTime.Now;
order.Status = OrderStatus.Paid;
order.PaymentInitError = string.Empty;
await _context.SaveChangesAsync();
await PublishOpenDoorCommandAsync(order);
_logger.LogInformation($"订单支付成功,已发送开门指令: OrderId={orderId}");
return order;
}
public async Task<Order?> UpdateOrderStatusAsync(int orderId, OrderStatus status)
{
var order = await _context.Orders.Include(o => o.Package).FirstOrDefaultAsync(o => o.Id == orderId);
if (order == null) return null;
order.Status = status;
if (status == OrderStatus.DoorClosed)
{
order.StartedAt = DateTime.Now;
// 门关闭后,发送开始清洗指令
await _mqttService.PublishAsync("device/command", new
{
command = "start_wash",
orderId = order.Id,
durationMinutes = order.Package?.DurationMinutes ?? 15,
timestamp = DateTime.Now
});
}
else if (status == OrderStatus.Completed)
{
order.CompletedAt = DateTime.Now;
}
else if (status == OrderStatus.Cancelled || status == OrderStatus.Expired)
{
order.PaymentCodeUrl = string.Empty;
order.PaymentExpiresAt = null;
}
await _context.SaveChangesAsync();
_logger.LogInformation($"订单状态更新: OrderId={orderId}, Status={status}");
return order;
}
public async Task<Order?> GetOrderAsync(int orderId)
{
return await _context.Orders.Include(o => o.Package).FirstOrDefaultAsync(o => o.Id == orderId);
}
public async Task EnsureOpenDoorCommandDispatchedAsync(int orderId)
{
var order = await _context.Orders.Include(o => o.Package).FirstOrDefaultAsync(o => o.Id == orderId);
if (order == null || !order.IsPaid || order.Status != OrderStatus.Paid)
{
return;
}
await PublishOpenDoorCommandAsync(order);
_logger.LogInformation("轮询支付状态时补发开门指令: OrderId={OrderId}", orderId);
}
private async Task PublishOpenDoorCommandAsync(Order order)
{
await _mqttService.PublishAsync("device/command", new
{
command = "open_door",
orderId = order.Id,
timestamp = DateTime.Now
});
}
}