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