From 2768c4ceefd9c6189b33e0ce28e4409db3446e6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A9=AC=E6=A2=93=E5=BD=AD?= Date: Thu, 6 Aug 2026 16:49:46 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BC=98=E6=83=A0=E5=88=B8?= =?UTF-8?q?=E8=B6=B3=E9=A2=9D=E6=8A=B5=E6=89=A3=E6=8A=A5=E5=90=8D=E8=B4=B9?= =?UTF-8?q?=E5=90=8E=E5=87=BA=E7=8E=B0=E7=9A=84=E6=B5=81=E7=A8=8B=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Event/UnifiedRegistrationService.cs | 132 ++++++++++++++++-- 1 file changed, 118 insertions(+), 14 deletions(-) diff --git a/YD_Event.Application/Event/UnifiedRegistrationService.cs b/YD_Event.Application/Event/UnifiedRegistrationService.cs index 306f28c..cdc8fc2 100644 --- a/YD_Event.Application/Event/UnifiedRegistrationService.cs +++ b/YD_Event.Application/Event/UnifiedRegistrationService.cs @@ -156,11 +156,17 @@ public class UnifiedRegistrationService : ITransient registration.TotalPersonCount = totalPersonCount; await _registrationRepository.UpdateAsync(registration); - // 5. 如果是免费赛事,直接完成支付 + // 5. 如果是免费赛事,直接完成支付(无优惠券,原价即0) if (totalAmount <= 0) { _logger.LogInformation("免费赛事,直接完成支付,报名ID: {RegistrationId}", registration.Id); - await HandleFreePaymentAsync(registration, null, input.CreatedUserId); + var eventInfo = await _eventRepository.GetByIdAsync(registration.EventId); + if (eventInfo == null) + { + throw Oops.Bah("赛事信息不存在"); + } + await HandleFreePaymentAsync(registration, eventInfo, totalAmount, 0, + null, null, null, input.CreatedUserId); } _logger.LogInformation("统一报名处理完成,报名ID: {RegistrationId}", registration.Id); @@ -286,6 +292,28 @@ public class UnifiedRegistrationService : ITransient throw Oops.Bah("赛事信息不存在"); } + // 2.1 幂等短路:已成交(含 0 元券全额抵扣)直接返回。 + // 关键:必须在重新计算金额之前短路。0 元成交时优惠券已被核销(Status=1), + // 若重入再走 CalculateFinalAmountAsync,会因查不到可用券而把 finalAmount 回退为原价, + // 从而落入付费分支:覆盖 PaymentStatus(1→0)、重复建单、重复调起微信支付 → 重复扣款/列表不显示。 + if (registration.PaymentStatus == 1) + { + _logger.LogInformation("报名已成交,幂等返回,报名ID: {RegistrationId}", registration.Id); + var paidOrder = await _orderRepository.GetFirstAsync(o => + o.RegistrationId == registration.Id && !o.DeleteMark); + var paidAmount = paidOrder?.ActualAmount ?? 0; + return new UnifiedPaymentOutput + { + RegistrationId = registration.Id, + OrderNo = registration.OrderNo, + OriginalAmount = paidOrder?.OriginalAmount ?? registration.TotalAmount, + DiscountAmount = paidOrder?.DiscountAmount ?? 0, + ActualAmount = paidAmount, + PaymentStatus = 1, + IsFree = paidAmount <= 0 + }; + } + // 3. 计算优惠后金额(每次都重新计算,确保金额正确) var calculateResult = await CalculateFinalAmountAsync( registration.TotalAmount, input.CouponId, input.OperatorUserId); @@ -308,13 +336,15 @@ public class UnifiedRegistrationService : ITransient CouponRuleNote = usedCoupon != null ? "优惠券使用后不予退还" : null }; - // 6. 处理免费情况 + // 6. 处理免费情况(0 元券全额抵扣等):与付费成交对齐——生成订单+支付流水并完成核销,避免状态孤岛 if (finalAmount <= 0) { if (registration.PaymentStatus != 1) { - await HandleFreePaymentAsync(registration, usedUserCoupon, input.OperatorUserId); + await HandleFreePaymentAsync(registration, eventInfo, originalAmount, discountAmount, + input.CouponId, usedCoupon, usedUserCoupon, input.OperatorUserId); } + output.OrderNo = registration.OrderNo; output.PaymentStatus = 1; return output; } @@ -954,24 +984,98 @@ public class UnifiedRegistrationService : ITransient } /// - /// 处理免费支付 + /// 处理免费支付(0 元券全额抵扣 / 免费赛事成交) + /// 与线上支付回调 对齐: + /// 生成订单(已支付)+支付流水(金额0),并完整核销优惠券,避免“付了钱但查不到”的状态孤岛。 /// - private async Task HandleFreePaymentAsync(EventRegistration registration, YD_Event.Core.Entities.UserCoupon? userCoupon, long operatorUserId) + private async Task HandleFreePaymentAsync( + EventRegistration registration, EventInfo eventInfo, + decimal originalAmount, decimal discountAmount, + long? couponId, YD_Event.Core.Entities.Coupon? coupon, + YD_Event.Core.Entities.UserCoupon? userCoupon, long operatorUserId) { + var now = DateTime.Now; + + // 1. 确保有订单号(预报名创建时 OrderNo 为空字符串) + if (string.IsNullOrEmpty(registration.OrderNo)) + { + registration.OrderNo = await _orderNoGeneratorService.GenerateOrderNoAsync(); + } + + // 2. 报名记录置为已支付(免费成交) + registration.PaymentStatus = 1; + registration.PaymentMethod = 2; // 免费赛事统一按线上支付方式,满足小程序列表放行条件 + registration.PaymentMethodType = PaymentMethodType.WeChatPay; + registration.PaymentTime = now; + registration.UpdatedTime = now; + registration.UpdatedUserId = operatorUserId; + await _registrationRepository.UpdateAsync(registration); + + // 3. 生成订单并标记为已支付(实付 0),消除状态孤岛 + var order = await CreateOrderAsync(registration, eventInfo, originalAmount, discountAmount, + 0, couponId, operatorUserId); + order.OrderStatus = OrderStatus.Paid; + order.PaymentTime = now; + order.UpdatedTime = now; + await _orderRepository.UpdateAsync(order); + + // 4. 写支付流水(金额 0),与微信回调路径保持一致 + var payment = new EventRegistrationPayment + { + Id = _idGenerator.NewLong(), + RegistrationId = registration.Id, + TransactionNo = "", + PaymentChannel = 1, + PaymentAmount = 0, + PaymentTime = now, + PaymentStatus = 1, + RefundStatus = 0, + PayeeAccount = "", + CreatedUserId = registration.CreatedUserId, + CreatedTime = now + }; + await _paymentRepository.InsertAsync(payment); + + // 5. 完整核销优惠券(补齐 OrderId / UsedQuantity / 兑换码,与银行转账、微信回调路径一致) if (userCoupon != null) { userCoupon.Status = 1; - userCoupon.UseTime = DateTime.Now; + userCoupon.UseTime = now; + userCoupon.OrderId = order.Id; + userCoupon.UpdatedTime = now; + userCoupon.UpdatedUserId = operatorUserId; await _userCouponRepository.UpdateAsync(userCoupon); + + // 兑换码标记已使用 + if (userCoupon.RedemptionCodeId.HasValue) + { + var redemptionCode = await _redemptionCodeRepository.GetFirstAsync(rc => + rc.Id == userCoupon.RedemptionCodeId.Value && rc.Status == 0 && !rc.DeleteMark); + if (redemptionCode != null) + { + redemptionCode.Status = 1; + redemptionCode.UsedTime = now; + redemptionCode.UpdatedTime = now; + redemptionCode.UpdatedUserId = operatorUserId; + await _redemptionCodeRepository.UpdateAsync(redemptionCode); + } + } + + // 优惠券已使用数量 +1 + var couponEntity = coupon ?? (couponId.HasValue + ? await _couponRepository.GetByIdAsync(couponId.Value) + : null); + if (couponEntity != null) + { + couponEntity.UsedQuantity++; + couponEntity.UpdatedTime = now; + couponEntity.UpdatedUserId = operatorUserId; + await _couponRepository.UpdateAsync(couponEntity); + } } - registration.PaymentStatus = 1; - registration.PaymentMethod = 2; // 免费赛事设置为线上支付方式 - registration.PaymentMethodType = PaymentMethodType.WeChatPay; // 默认微信支付 - registration.PaymentTime = DateTime.Now; - registration.UpdatedTime = DateTime.Now; - registration.UpdatedUserId = operatorUserId; - await _registrationRepository.UpdateAsync(registration); + _logger.LogInformation("免费成交处理完成(0元/券全额抵扣),报名ID: {RegistrationId}, 订单号: {OrderNo}", + registration.Id, registration.OrderNo); } ///