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