赛事退款
This commit is contained in:
parent
66d75e1497
commit
f9b5e8e1ee
@ -994,3 +994,26 @@ public class SelectPaymentMethodInput
|
||||
[RegularExpression(@"^[a-zA-Z0-9_-]+$", ErrorMessage = "OpenId格式不正确,只能包含字母、数字、下划线和连字符")]
|
||||
public string? OpenId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消报名退款输入DTO
|
||||
/// </summary>
|
||||
public class CancelRegistrationRefundInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 报名记录ID
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "报名记录ID不能为空")]
|
||||
public long RegistrationId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 退款原因
|
||||
/// </summary>
|
||||
[MaxLength(200, ErrorMessage = "退款原因不能超过200个字符")]
|
||||
public string? RefundReason { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 取消的项目ID列表(可选,用于部分取消)
|
||||
/// </summary>
|
||||
public List<long>? CancelItemIds { get; set; }
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ using Furion.FriendlyException;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
using System.IO;
|
||||
using YD_Event.Core.Entities;
|
||||
|
||||
namespace YD_Event.Application.Client;
|
||||
|
||||
@ -50,6 +51,8 @@ public class EventClientController : IDynamicApiController
|
||||
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Order> _orderRepository;
|
||||
private readonly ISqlSugarRepository<MerchantCertification> _merchantCertificationRepository;
|
||||
private readonly ISqlSugarRepository<EventRegistrationPayment> _paymentRepository;
|
||||
private readonly ISqlSugarRepository<RefundRecord> _refundRecordRepository;
|
||||
private readonly ISqlSugarRepository<RefundConfig> _refundConfigRepository;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly IIdGenerator _idGenerator;
|
||||
private readonly WeChatPayService _weChatPayService;
|
||||
@ -82,6 +85,8 @@ public class EventClientController : IDynamicApiController
|
||||
ISqlSugarRepository<YD_Event.Core.Entities.Order> orderRepository,
|
||||
ISqlSugarRepository<MerchantCertification> merchantCertificationRepository,
|
||||
ISqlSugarRepository<EventRegistrationPayment> paymentRepository,
|
||||
ISqlSugarRepository<RefundRecord> refundRecordRepository,
|
||||
ISqlSugarRepository<RefundConfig> refundConfigRepository,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
IIdGenerator idGenerator,
|
||||
WeChatPayService weChatPayService,
|
||||
@ -113,6 +118,8 @@ public class EventClientController : IDynamicApiController
|
||||
_orderRepository = orderRepository;
|
||||
_merchantCertificationRepository = merchantCertificationRepository;
|
||||
_paymentRepository = paymentRepository;
|
||||
_refundRecordRepository = refundRecordRepository;
|
||||
_refundConfigRepository = refundConfigRepository;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_idGenerator = idGenerator;
|
||||
_weChatPayService = weChatPayService;
|
||||
@ -1624,4 +1631,243 @@ public class EventClientController : IDynamicApiController
|
||||
|
||||
return await _registrationRepository.UpdateAsync(registration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消报名并申请退款
|
||||
/// </summary>
|
||||
[HttpPost("cancel-registration-refund")]
|
||||
[UnitOfWork]
|
||||
[DisplayName("取消报名退款")]
|
||||
public async Task CancelRegistrationRefund([FromBody] CancelRegistrationRefundInput input)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
|
||||
// 验证报名记录
|
||||
var registration = await _registrationRepository.GetByIdAsync(input.RegistrationId);
|
||||
if (registration == null) throw Oops.Bah("报名记录不存在");
|
||||
if (registration.CreatedUserId != userId) throw Oops.Bah("无权操作此报名记录");
|
||||
if (registration.PaymentStatus != 1) throw Oops.Bah("只有已支付的报名才能申请退款");
|
||||
|
||||
// 获取订单信息
|
||||
var order = await _orderRepository.GetFirstAsync(x => x.RegistrationId == input.RegistrationId && x.OrderType == 1);
|
||||
if (order == null) throw Oops.Bah("订单不存在");
|
||||
if (order.OrderStatus != OrderStatus.Paid) throw Oops.Bah("订单未支付,无法退款");
|
||||
|
||||
// 获取赛事信息
|
||||
var eventInfo = await _eventRepository.GetByIdAsync(order.EventId);
|
||||
if (eventInfo == null) throw Oops.Bah("赛事信息不存在");
|
||||
|
||||
// 计算退款金额
|
||||
var refundAmount = await CalculateRefundAmount(order.ActualAmount, eventInfo.MatchStartTime);
|
||||
|
||||
// 处理退款
|
||||
await ProcessRefundAsync(order, registration, refundAmount, input.RefundReason ?? "用户取消报名", userId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算退款金额
|
||||
/// </summary>
|
||||
private async Task<decimal> CalculateRefundAmount(decimal totalAmount, DateTime eventStartTime)
|
||||
{
|
||||
var configs = await _refundConfigRepository.AsQueryable()
|
||||
.Where(x => x.Status == Core.Enum.AvailabilityStatus.Enable && !x.DeleteMark)
|
||||
.OrderBy(x => x.DayStart)
|
||||
.ToListAsync();
|
||||
|
||||
if (!configs.Any()) return totalAmount;
|
||||
|
||||
var daysRemaining = (eventStartTime - DateTime.Now).TotalDays;
|
||||
if (daysRemaining < 0) daysRemaining = 0;
|
||||
|
||||
var matchConfig = configs.FirstOrDefault(c =>
|
||||
daysRemaining >= c.DayStart &&
|
||||
(c.DayEnd == int.MaxValue ? true : daysRemaining < c.DayEnd)
|
||||
);
|
||||
|
||||
if (matchConfig != null)
|
||||
{
|
||||
var deductAmount = totalAmount * (matchConfig.DeductPercentage / 100m);
|
||||
return totalAmount - deductAmount;
|
||||
}
|
||||
|
||||
return totalAmount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理退款核心逻辑
|
||||
/// </summary>
|
||||
private async Task ProcessRefundAsync(
|
||||
YD_Event.Core.Entities.Order order,
|
||||
EventRegistration registration,
|
||||
decimal refundAmount,
|
||||
string refundReason,
|
||||
long userId)
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
|
||||
// 获取微信支付配置
|
||||
var weChatPayConfig = await _weChatPayConfigRepository.GetFirstAsync(x => !x.DeleteMark);
|
||||
|
||||
// 生成退款单号
|
||||
var outRefundNo = "RF" + DateTime.Now.ToString("yyyyMMddHHmmss") + new Random().Next(1000, 9999);
|
||||
|
||||
// 创建退款记录
|
||||
var refundRecord = new RefundRecord
|
||||
{
|
||||
Id = _idGenerator.NewLong(),
|
||||
OutRefundNo = outRefundNo,
|
||||
RegistrationId = registration.Id,
|
||||
OutTradeNo = order.OrderNo,
|
||||
RefundAmount = refundAmount,
|
||||
TotalAmount = order.ActualAmount,
|
||||
RefundReason = refundReason,
|
||||
RefundStatus = 0,
|
||||
ApplyTime = now,
|
||||
WeChatPayConfigId = weChatPayConfig?.Id,
|
||||
CreatedTime = now,
|
||||
CreatedUserId = userId,
|
||||
Status = Core.Enum.AvailabilityStatus.Enable
|
||||
};
|
||||
|
||||
// 获取支付记录中的微信交易号
|
||||
var payment = await _paymentRepository.GetFirstAsync(p => p.RegistrationId == registration.Id);
|
||||
if (payment != null && !string.IsNullOrEmpty(payment.TransactionNo))
|
||||
{
|
||||
refundRecord.TransactionId = payment.TransactionNo;
|
||||
}
|
||||
|
||||
await _refundRecordRepository.InsertAsync(refundRecord);
|
||||
|
||||
// 如果是线上支付,调用微信退款接口
|
||||
if (order.PaymentMethod == 2 && weChatPayConfig != null)
|
||||
{
|
||||
await CreateWeChatRefundAsync(refundRecord, order, weChatPayConfig, refundAmount, registration, userId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 线下支付直接标记退款成功
|
||||
await CompleteRefundAsync(refundRecord, order, registration, userId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调用微信退款接口
|
||||
/// </summary>
|
||||
private async Task CreateWeChatRefundAsync(
|
||||
RefundRecord refundRecord,
|
||||
YD_Event.Core.Entities.Order order,
|
||||
WeChatPayConfig config,
|
||||
decimal refundAmount,
|
||||
EventRegistration registration,
|
||||
long userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = new YD_Event.Core.Infrastructure.WeChat.Dto.CreateRefundRequest
|
||||
{
|
||||
OutTradeNo = order.OrderNo,
|
||||
OutRefundNo = refundRecord.OutRefundNo,
|
||||
RefundAmount = (int)(refundAmount * 100),
|
||||
TotalAmount = (int)(order.ActualAmount * 100),
|
||||
Reason = refundRecord.RefundReason
|
||||
//NotifyUrl = config.RefundNotifyUrl
|
||||
};
|
||||
|
||||
var result = await _weChatPayService.CreateRefundAsync(config, request);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
refundRecord.RefundId = result.RefundId;
|
||||
refundRecord.WeChatResponse = result.WeChatResponse;
|
||||
|
||||
if (result.RefundStatus == "SUCCESS")
|
||||
{
|
||||
await CompleteRefundAsync(refundRecord, order, registration, userId);
|
||||
}
|
||||
else
|
||||
{
|
||||
refundRecord.RefundStatus = 0;
|
||||
await _refundRecordRepository.UpdateAsync(refundRecord);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
refundRecord.RefundStatus = 2;
|
||||
refundRecord.ErrorMessage = result.Message;
|
||||
refundRecord.WeChatResponse = result.WeChatResponse;
|
||||
await _refundRecordRepository.UpdateAsync(refundRecord);
|
||||
throw Oops.Bah($"微信退款申请失败: {result.Message}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
refundRecord.RefundStatus = 2;
|
||||
refundRecord.ErrorMessage = ex.Message;
|
||||
await _refundRecordRepository.UpdateAsync(refundRecord);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 完成退款流程
|
||||
/// </summary>
|
||||
private async Task CompleteRefundAsync(
|
||||
RefundRecord refundRecord,
|
||||
YD_Event.Core.Entities.Order originalOrder,
|
||||
EventRegistration registration,
|
||||
long userId)
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
|
||||
// 更新退款记录状态
|
||||
refundRecord.RefundStatus = 1;
|
||||
refundRecord.SuccessTime = now;
|
||||
await _refundRecordRepository.UpdateAsync(refundRecord);
|
||||
|
||||
// 创建退款订单
|
||||
var refundOrder = new YD_Event.Core.Entities.Order
|
||||
{
|
||||
Id = _idGenerator.NewLong(),
|
||||
OrderNo = refundRecord.OutRefundNo,
|
||||
OrderType = 2,
|
||||
PaymentMethod = originalOrder.PaymentMethod,
|
||||
RegistrationMode = originalOrder.RegistrationMode,
|
||||
MerchantId = originalOrder.MerchantId,
|
||||
MerchantCertificationId = originalOrder.MerchantCertificationId,
|
||||
EventId = originalOrder.EventId,
|
||||
RegistrationId = originalOrder.RegistrationId,
|
||||
RelatedOrderId = originalOrder.Id,
|
||||
OriginalAmount = originalOrder.ActualAmount,
|
||||
ActualAmount = refundRecord.RefundAmount,
|
||||
OrderStatus = OrderStatus.Refunded,
|
||||
PaymentTime = now,
|
||||
Remark = refundRecord.RefundReason,
|
||||
CreatedTime = now,
|
||||
CreatedUserId = userId,
|
||||
Status = Core.Enum.AvailabilityStatus.Enable
|
||||
};
|
||||
await _orderRepository.InsertAsync(refundOrder);
|
||||
|
||||
// 更新原订单状态
|
||||
originalOrder.OrderStatus = OrderStatus.Refunded;
|
||||
originalOrder.UpdatedTime = now;
|
||||
originalOrder.UpdatedUserId = userId;
|
||||
await _orderRepository.UpdateAsync(originalOrder);
|
||||
|
||||
// 更新报名状态
|
||||
registration.PaymentStatus = 3;
|
||||
registration.UpdatedTime = now;
|
||||
registration.UpdatedUserId = userId;
|
||||
await _registrationRepository.UpdateAsync(registration);
|
||||
|
||||
// 更新支付记录
|
||||
var payment = await _paymentRepository.GetFirstAsync(p => p.RegistrationId == originalOrder.RegistrationId);
|
||||
if (payment != null)
|
||||
{
|
||||
payment.RefundStatus = 1;
|
||||
payment.UpdatedTime = now;
|
||||
payment.UpdatedUserId = userId;
|
||||
await _paymentRepository.UpdateAsync(payment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2509,6 +2509,26 @@
|
||||
- 允许的字符:字母、数字、下划线、连字符
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="T:YD_Event.Application.Client.Dtos.CancelRegistrationRefundInput">
|
||||
<summary>
|
||||
取消报名退款输入DTO
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Client.Dtos.CancelRegistrationRefundInput.RegistrationId">
|
||||
<summary>
|
||||
报名记录ID
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Client.Dtos.CancelRegistrationRefundInput.RefundReason">
|
||||
<summary>
|
||||
退款原因
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Client.Dtos.CancelRegistrationRefundInput.CancelItemIds">
|
||||
<summary>
|
||||
取消的项目ID列表(可选,用于部分取消)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:YD_Event.Application.Client.Dtos.MiniProgramLoginInput">
|
||||
<summary>
|
||||
小程序登录输入参数
|
||||
@ -3378,6 +3398,31 @@
|
||||
<param name="input">上传素材输入参数</param>
|
||||
<returns>操作是否成功</returns>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Client.EventClientController.CancelRegistrationRefund(YD_Event.Application.Client.Dtos.CancelRegistrationRefundInput)">
|
||||
<summary>
|
||||
取消报名并申请退款
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Client.EventClientController.CalculateRefundAmount(System.Decimal,System.DateTime)">
|
||||
<summary>
|
||||
计算退款金额
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Client.EventClientController.ProcessRefundAsync(YD_Event.Core.Entities.Order,YD_Event.Core.Entities.EventRegistration,System.Decimal,System.String,System.Int64)">
|
||||
<summary>
|
||||
处理退款核心逻辑
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Client.EventClientController.CreateWeChatRefundAsync(YD_Event.Core.Entities.RefundRecord,YD_Event.Core.Entities.Order,YD_Event.Core.Entities.WeChatPayConfig,System.Decimal,YD_Event.Core.Entities.EventRegistration,System.Int64)">
|
||||
<summary>
|
||||
调用微信退款接口
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Client.EventClientController.CompleteRefundAsync(YD_Event.Core.Entities.RefundRecord,YD_Event.Core.Entities.Order,YD_Event.Core.Entities.EventRegistration,System.Int64)">
|
||||
<summary>
|
||||
完成退款流程
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:YD_Event.Application.Client.MyController">
|
||||
<summary>
|
||||
小程序-个人中心接口
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user