Revert "实付金额0退款流程优化"

This reverts commit 8d812bf3df2862cc461d29db87399fc7250d54f4.
This commit is contained in:
马梓彭 2026-08-07 10:41:09 +08:00
parent 03a3d175b9
commit 26750d9bf1

View File

@ -997,9 +997,6 @@ public class UnifiedRegistrationService : ITransient
var now = DateTime.Now; var now = DateTime.Now;
// 1. 确保有订单号(预报名创建时 OrderNo 为空字符串) // 1. 确保有订单号(预报名创建时 OrderNo 为空字符串)
// 注意:0元成交不涉及外部支付接口,订单号首次生成后复用即可。
// 与付费支付的"强制重新生成"策略不同,因为无需应对微信支付的参数一致性约束。
// 幂等保护已在 ProcessPaymentAsync:L299 完成,此处实际每个报名订单只执行一次。
if (string.IsNullOrEmpty(registration.OrderNo)) if (string.IsNullOrEmpty(registration.OrderNo))
{ {
registration.OrderNo = await _orderNoGeneratorService.GenerateOrderNoAsync(); registration.OrderNo = await _orderNoGeneratorService.GenerateOrderNoAsync();
@ -3768,32 +3765,16 @@ public class UnifiedRegistrationService : ITransient
var order = await _orderRepository.GetFirstAsync(o => o.RegistrationId == registration.Id && o.OrderType == 1 && !o.DeleteMark); var order = await _orderRepository.GetFirstAsync(o => o.RegistrationId == registration.Id && o.OrderType == 1 && !o.DeleteMark);
var actualPaidAmount = order?.ActualAmount ?? registration.TotalAmount; var actualPaidAmount = order?.ActualAmount ?? registration.TotalAmount;
var originalAmount = registration.TotalAmount; var originalAmount = registration.TotalAmount;
decimal originalRefundAmount; // 先计算按原价应该退多少
decimal actualRefundAmount; decimal originalRefundAmount = await CalculateActualRefundAmountAsync(input, registration, eventInfo, items, persons);
// 0 元成交(优惠券全额抵扣 / 免费赛事):实付为 0,无款可退,直接短路。 // 按比例计算实际退款金额
// 不走按比例折算,避免 originalAmount>0 而 actualPaidAmount 取值异常时算出正的退款额造成资损。 decimal actualRefundAmount = originalAmount > 0 ? originalRefundAmount * (actualPaidAmount / originalAmount) : 0;
// 注意:优惠券已核销,按"使用后不予退还"规则不做回退。 actualRefundAmount = RoundUpToTwoDecimalPlaces(actualRefundAmount);
if (actualPaidAmount <= 0) actualRefundAmount = Math.Min(actualRefundAmount, actualPaidAmount);
{
originalRefundAmount = 0; _logger.LogInformation("统一退款金额计算:报名ID={RegistrationId}, 原价={OriginalAmount}, 实付={ActualPaidAmount}, 按原价应退={OriginalRefundAmount}, 实际退款={ActualRefundAmount}",
actualRefundAmount = 0;
_logger.LogInformation("0元成交无需退款:报名ID={RegistrationId}, 原价={OriginalAmount}, 实付=0",
registration.Id, originalAmount);
}
else
{
// 先计算按原价应该退多少
originalRefundAmount = await CalculateActualRefundAmountAsync(input, registration, eventInfo, items, persons);
// 按比例计算实际退款金额
actualRefundAmount = originalAmount > 0 ? originalRefundAmount * (actualPaidAmount / originalAmount) : 0;
actualRefundAmount = RoundUpToTwoDecimalPlaces(actualRefundAmount);
actualRefundAmount = Math.Max(0, Math.Min(actualRefundAmount, actualPaidAmount));
}
_logger.LogInformation("统一退款金额计算:报名ID={RegistrationId}, 原价={OriginalAmount}, 实付={ActualPaidAmount}, 按原价应退={OriginalRefundAmount}, 实际退款={ActualRefundAmount}",
registration.Id, originalAmount, actualPaidAmount, originalRefundAmount, actualRefundAmount); registration.Id, originalAmount, actualPaidAmount, originalRefundAmount, actualRefundAmount);
bool isPartialCancel = input.CancelType == 1; bool isPartialCancel = input.CancelType == 1;
@ -3837,56 +3818,49 @@ public class UnifiedRegistrationService : ITransient
long? refundOrderId = null; long? refundOrderId = null;
string? refundOrderNo = null; string? refundOrderNo = null;
// 第一步:先创建退款订单 // 第一步:先创建退款订单(如果有退款金额的话)
// 只要是"有金额"的报名单(原价 > 0),取消时一律建退款订单(OrderType=2),即使实退为 0。 if (registration.TotalAmount > 0 && !string.IsNullOrEmpty(registration.OrderNo) && actualRefundAmount > 0)
// 原因:OrderType==2 是订单列表/详情展示退款轨迹的唯一依据,且软删除的人员、项目要挂到
// refundOrderId 上才能追溯。券全额抵扣(实付 0、实退 0)若不建单,取消后无据可查 —— 与
// 0 元成交不建订单是同一类状态孤岛。是否真正打款由第二步按 actualRefundAmount > 0 判断,与建单解耦。
// 复用上面已按 OrderType==1 && !DeleteMark 过滤好的 order,避免按 OrderNo 反查捞到退款单。
if (registration.TotalAmount > 0 && order != null)
{ {
var newRefundOrder = new Core.Entities.Order var originalOrder = await _orderRepository.GetFirstAsync(o => o.OrderNo == registration.OrderNo);
if (originalOrder != null)
{ {
Id = _idGenerator.NewLong(), var newRefundOrder = new Core.Entities.Order
OrderNo = await _orderNoGeneratorService.GenerateRefundOrderNoAsync(), {
OrderType = 2, Id = _idGenerator.NewLong(),
MerchantId = order.MerchantId, OrderNo = await _orderNoGeneratorService.GenerateRefundOrderNoAsync(),
MerchantCertificationId = order.MerchantCertificationId, OrderType = 2,
EventId = order.EventId, MerchantId = originalOrder.MerchantId,
RegistrationId = order.RegistrationId, MerchantCertificationId = originalOrder.MerchantCertificationId,
RelatedOrderId = order.Id, EventId = originalOrder.EventId,
OriginalAmount = order.ActualAmount, RegistrationId = originalOrder.RegistrationId,
ActualAmount = actualRefundAmount, RelatedOrderId = originalOrder.Id,
DiscountAmount = 0, OriginalAmount = originalOrder.ActualAmount,
// 实退 0 记为"已取消",与真实退款区分,避免对账时把 0 元单误读为已退款 ActualAmount = actualRefundAmount,
OrderStatus = actualRefundAmount > 0 ? OrderStatus.Refunded : OrderStatus.Cancelled, DiscountAmount = 0,
PaymentTime = now, OrderStatus = OrderStatus.Refunded,
Remark = input.CancelReason, PaymentTime = now,
CreatedTime = now, Remark = input.CancelReason,
CreatedUserId = operatorUserId, CreatedTime = now,
Status = AvailabilityStatus.Enable CreatedUserId = operatorUserId,
}; Status = AvailabilityStatus.Enable
};
// 线下支付时保存收款人信息
if (registration.PaymentMethod == 1) // 线下支付时保存收款人信息
{ if (registration.PaymentMethod == 1)
newRefundOrder.PayeeName = input.PayeeName; {
newRefundOrder.PayeeBankName = input.PayeeBankName; newRefundOrder.PayeeName = input.PayeeName;
newRefundOrder.PayeeBankAccount = input.PayeeBankAccount; newRefundOrder.PayeeBankName = input.PayeeBankName;
newRefundOrder.PayeeBankAccount = input.PayeeBankAccount;
}
await _orderRepository.InsertAsync(newRefundOrder);
refundOrderId = newRefundOrder.Id;
refundOrderNo = newRefundOrder.OrderNo;
output.RefundNo = newRefundOrder.OrderNo;
_logger.LogInformation("退款订单创建成功:报名ID={RegistrationId}, 原订单号={OriginalOrderNo}, 退款订单号={RefundOrderNo}, 退款金额={Amount}",
registration.Id, originalOrder.OrderNo, newRefundOrder.OrderNo, actualRefundAmount);
} }
await _orderRepository.InsertAsync(newRefundOrder);
refundOrderId = newRefundOrder.Id;
refundOrderNo = newRefundOrder.OrderNo;
output.RefundNo = newRefundOrder.OrderNo;
_logger.LogInformation("退款订单创建成功:报名ID={RegistrationId}, 原订单号={OriginalOrderNo}, 退款订单号={RefundOrderNo}, 退款金额={Amount}, 订单状态={OrderStatus}",
registration.Id, order.OrderNo, newRefundOrder.OrderNo, actualRefundAmount, newRefundOrder.OrderStatus);
}
else if (registration.TotalAmount > 0)
{
_logger.LogWarning("未找到原订单,无法创建退款订单:报名ID={RegistrationId}, 订单号={OrderNo}",
registration.Id, registration.OrderNo);
} }
if (registration.PaymentMethod == 1) if (registration.PaymentMethod == 1)
@ -4046,21 +4020,22 @@ public class UnifiedRegistrationService : ITransient
} }
} }
// 第二步:执行实际打款并创建退款流水记录 // 第二步:创建退款记录和执行微信退款(如果是线上支付)
// 退款单据在第一步已经落库(含实退 0 的留痕单),这里只负责真正的资金动作: if (!string.IsNullOrEmpty(refundOrderNo))
// actualRefundAmount == 0 时不得调用微信退款接口(金额 0 会被微信拒绝),也无需退款流水。
if (!string.IsNullOrEmpty(refundOrderNo) && actualRefundAmount > 0)
{ {
if (registration.PaymentMethod == 1) if (registration.PaymentMethod == 1)
{ {
// 线下支付:创建退款记录 // 线下支付:创建退款记录
_logger.LogInformation("线下支付创建退款记录:报名ID={RegistrationId}, 退款订单号={RefundOrderNo}, 退款金额={RefundAmount}", _logger.LogInformation("线下支付创建退款记录:报名ID={RegistrationId}, 退款订单号={RefundOrderNo}, 退款金额={RefundAmount}",
registration.Id, refundOrderNo, actualRefundAmount); registration.Id, refundOrderNo, actualRefundAmount);
// 创建线下支付退款记录 if (registration.TotalAmount > 0 && !string.IsNullOrEmpty(registration.OrderNo) && actualRefundAmount > 0)
await CreateRefundRecordAsync(registration, eventInfo, null, refundOrderNo, {
null, actualRefundAmount, actualPaidAmount, input.CancelReason, operatorUserId, // 创建线下支付退款记录
input.PayeeName, input.PayeeBankName, input.PayeeBankAccount); await CreateRefundRecordAsync(registration, eventInfo, null, refundOrderNo,
null, actualRefundAmount, actualPaidAmount, input.CancelReason, operatorUserId,
input.PayeeName, input.PayeeBankName, input.PayeeBankAccount);
}
} }
else else
{ {
@ -4098,18 +4073,12 @@ public class UnifiedRegistrationService : ITransient
await CreateRefundRecordAsync(registration, eventInfo, wechatConfig, refundOrderNo, await CreateRefundRecordAsync(registration, eventInfo, wechatConfig, refundOrderNo,
refundResult.RefundId, actualRefundAmount, actualPaidAmount, input.CancelReason, operatorUserId); refundResult.RefundId, actualRefundAmount, actualPaidAmount, input.CancelReason, operatorUserId);
_logger.LogInformation("统一退款成功:报名ID={RegistrationId}, 退款单号={RefundNo}, 退款金额={Amount}", _logger.LogInformation("统一退款成功:报名ID={RegistrationId}, 退款单号={RefundNo}, 退款金额={Amount}",
registration.Id, refundOrderNo, actualRefundAmount); registration.Id, refundOrderNo, actualRefundAmount);
} }
} }
} }
} }
else if (!string.IsNullOrEmpty(refundOrderNo))
{
// 实退为 0(优惠券全额抵扣、或按规则本次不退款):只保留退款单据留痕,不动资金
_logger.LogInformation("退款金额为0,仅生成退款单据不调用支付渠道:报名ID={RegistrationId}, 退款订单号={RefundOrderNo}, 实付={ActualPaidAmount}",
registration.Id, refundOrderNo, actualPaidAmount);
}
// 不修改原订单状态,只创建退款订单记录 // 不修改原订单状态,只创建退款订单记录
// if (!isPartialCancel || registration.PaymentStatus == 3 || registration.PaymentStatus == 4) // if (!isPartialCancel || registration.PaymentStatus == 3 || registration.PaymentStatus == 4)