diff --git a/YD_Event.Application/Client/Dtos/EventClientDtos.cs b/YD_Event.Application/Client/Dtos/EventClientDtos.cs index cd76ac5..82f6ced 100644 --- a/YD_Event.Application/Client/Dtos/EventClientDtos.cs +++ b/YD_Event.Application/Client/Dtos/EventClientDtos.cs @@ -738,21 +738,6 @@ public class PaymentInfoDto /// 微信支付参数 /// public object? WechatPayParams { get; set; } - - /// - /// 是否为 0 元成交(优惠券全额抵扣 / 免费赛事) - /// - /// - /// 前端务必判断:IsFree == true 或 PaymentStatus == 1 时,订单已成交, - /// 不要再调起微信支付(此时 WechatPayParams 为 null,调起会报“支付失败”), - /// 应直接跳转支付成功页。 - /// - public bool IsFree { get; set; } - - /// - /// 支付状态 (0-待支付/待审核 1-已支付/已成交) - /// - public int PaymentStatus { get; set; } } /// diff --git a/YD_Event.Application/Client/EventClientController.cs b/YD_Event.Application/Client/EventClientController.cs index da34a6f..1ed629a 100644 --- a/YD_Event.Application/Client/EventClientController.cs +++ b/YD_Event.Application/Client/EventClientController.cs @@ -776,9 +776,7 @@ public class EventClientController : IDynamicApiController BankAccountName = merchant?.MerchantName, BankAccountNo = merchant?.AccountNumber, BankName = merchant?.BankName, - WechatPayParams = unifiedResult.WechatPayParams, - IsFree = unifiedResult.IsFree, - PaymentStatus = unifiedResult.PaymentStatus + WechatPayParams = unifiedResult.WechatPayParams }; return paymentInfo; diff --git a/YD_Event.Application/Event/UnifiedRegistrationService.cs b/YD_Event.Application/Event/UnifiedRegistrationService.cs index 2293015..bd0e440 100644 --- a/YD_Event.Application/Event/UnifiedRegistrationService.cs +++ b/YD_Event.Application/Event/UnifiedRegistrationService.cs @@ -299,7 +299,8 @@ public class UnifiedRegistrationService : ITransient if (registration.PaymentStatus == 1) { _logger.LogInformation("报名已成交,幂等返回,报名ID: {RegistrationId}", registration.Id); - var paidOrder = await GetSettledOrderAsync(registration.Id); + var paidOrder = await _orderRepository.GetFirstAsync(o => + o.RegistrationId == registration.Id && !o.DeleteMark); var paidAmount = paidOrder?.ActualAmount ?? 0; return new UnifiedPaymentOutput { @@ -995,28 +996,15 @@ public class UnifiedRegistrationService : ITransient { var now = DateTime.Now; - // 1. 作废该报名下遗留的「原价待付」订单,并强制重新生成订单号。 - // 场景:用户先未选券打开支付页 → payment-info 走付费分支,已生成 OrderNo 并建了原价待付单 - // (还调过微信统一下单);随后选上全额券再次进来变成 0 元成交。 - // 若沿用旧 OrderNo,会出现两条 OrderType=1 且单号相同的订单,导致: - // ① 按 OrderNo 反查订单(如 HandlePaymentSuccessAsync)取错行; - // ② 算实付金额时取到原价待付单 → 退款按原价计算,实付 0 却退原价,造成资损。 - var staleOrders = await _orderRepository.GetListAsync(o => - o.RegistrationId == registration.Id && o.OrderType == 1 - && !o.DeleteMark && o.OrderStatus == OrderStatus.Pending); - foreach (var stale in staleOrders) + // 1. 确保有订单号(预报名创建时 OrderNo 为空字符串) + // 注意:0元成交不涉及外部支付接口,订单号首次生成后复用即可。 + // 与付费支付的"强制重新生成"策略不同,因为无需应对微信支付的参数一致性约束。 + // 幂等保护已在 ProcessPaymentAsync:L299 完成,此处实际每个报名订单只执行一次。 + if (string.IsNullOrEmpty(registration.OrderNo)) { - stale.OrderStatus = OrderStatus.Cancelled; - stale.Remark = (stale.Remark ?? "") + " [系统]优惠券全额抵扣/免费成交,原价待付单作废"; - stale.UpdatedTime = now; - await _orderRepository.UpdateAsync(stale); - _logger.LogInformation("作废原价待付订单,报名ID: {RegistrationId}, 订单号: {OrderNo}", - registration.Id, stale.OrderNo); + registration.OrderNo = await _orderNoGeneratorService.GenerateOrderNoAsync(); } - // 强制重新生成订单号:保证 0 元成交单的单号唯一,与付费分支的策略保持一致 - registration.OrderNo = await _orderNoGeneratorService.GenerateOrderNoAsync(); - // 2. 报名记录置为已支付(免费成交) registration.PaymentStatus = 1; registration.PaymentMethod = 2; // 免费赛事统一按线上支付方式,满足小程序列表放行条件 @@ -1093,35 +1081,6 @@ public class UnifiedRegistrationService : ITransient registration.Id, registration.OrderNo); } - /// - /// 获取报名对应的「有效收款订单」,用于判定实付金额。 - /// - /// - /// 一个报名下可能残留多条 OrderType=1 的订单(典型:用户先未选券打开支付页生成了原价待付单, - /// 之后再用全额券 0 元成交)。直接用 GetFirstAsync 取不带排序、不筛状态, - /// 可能取到原价待付单,把实付金额误判为原价 —— 退款就会按原价退给实付 0 元的用户,造成资损。 - /// 因此这里优先取「已支付」单(0 元成交单同样是 Paid),按支付时间取最新; - /// 没有已支付单时(如线下转账待审核)再回退到最近一条未取消的订单,保持原有行为不变。 - /// - private async Task GetSettledOrderAsync(long registrationId) - { - var orders = await _orderRepository.GetListAsync(o => - o.RegistrationId == registrationId && o.OrderType == 1 && !o.DeleteMark); - - if (orders == null || orders.Count == 0) return null; - - var paid = orders - .Where(o => o.OrderStatus == OrderStatus.Paid) - .OrderByDescending(o => o.PaymentTime ?? o.CreatedTime) - .FirstOrDefault(); - if (paid != null) return paid; - - return orders - .Where(o => o.OrderStatus != OrderStatus.Cancelled) - .OrderByDescending(o => o.CreatedTime) - .FirstOrDefault(); - } - /// /// 创建订单记录 /// @@ -3428,7 +3387,7 @@ public class UnifiedRegistrationService : ITransient var persons = await _registrationPersonRepository.GetListAsync(x => x.RegistrationId == input.RegistrationId); // 查询订单获取实际支付金额(考虑优惠券抵扣) - var order = await GetSettledOrderAsync(input.RegistrationId); + var order = await _orderRepository.GetFirstAsync(x => x.RegistrationId == input.RegistrationId && x.OrderType == 1 && !x.DeleteMark); var actualPaidAmount = order?.ActualAmount ?? registration.TotalAmount; var originalAmount = registration.TotalAmount; @@ -3806,7 +3765,7 @@ public class UnifiedRegistrationService : ITransient // 计算实际退款金额 // 【重要】始终信任后端计算结果,前端计算只用于展示 // 不接受前端传递的退款金额,避免资金风险 - var order = await GetSettledOrderAsync(registration.Id); + var order = await _orderRepository.GetFirstAsync(o => o.RegistrationId == registration.Id && o.OrderType == 1 && !o.DeleteMark); var actualPaidAmount = order?.ActualAmount ?? registration.TotalAmount; var originalAmount = registration.TotalAmount;