YD_Event.Admin/YD_Event.Application/Event/UnifiedRegistrationService.cs

2697 lines
114 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using YD_Event.Application.Event.Dtos;
using YD_Event.Application.Order.Dtos;
using YD_Event.Application.Refund;
using YD_Event.Core.Domain;
using YD_Event.Core.Domain.Pricing;
using YD_Event.Core.Domain.Pricing.Models;
using YD_Event.Core.Entities;
using YD_Event.Core.Enum;
using YD_Event.Core.Extensions;
using YD_Event.Core.Infrastructure.WeChat;
using YD_Event.Core.Infrastructure.WeChat.Dto;
using YD_Event.Core.Options;
namespace YD_Event.Application.Event;
/// <summary>
/// 统一报名处理服务
/// </summary>
public class UnifiedRegistrationService : ITransient
{
private readonly ISqlSugarRepository<EventInfo> _eventRepository;
private readonly ISqlSugarRepository<EventUnit> _unitRepository;
private readonly ISqlSugarRepository<EventProject> _eventProjectRepository;
private readonly ISqlSugarRepository<EventProjectGroup> _eventProjectGroupRepository;
private readonly ISqlSugarRepository<ProjectGroup> _projectGroupRepository;
private readonly ISqlSugarRepository<EventProjectRegistrationLimit> _projectLimitRepository;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Project> _projectRepository;
private readonly ISqlSugarRepository<EventRegistration> _registrationRepository;
private readonly ISqlSugarRepository<EventRegistrationItem> _registrationItemRepository;
private readonly ISqlSugarRepository<EventRegistrationPerson> _registrationPersonRepository;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.AppUser> _userRepository;
private readonly ISqlSugarRepository<SysUser> _sysUserRepository;
private readonly ISqlSugarRepository<EventFeeConfig> _feeConfigRepository;
private readonly ISqlSugarRepository<EventRegistrationLimit> _limitRepository;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Order> _orderRepository;
private readonly ISqlSugarRepository<WeChatPayConfig> _weChatPayConfigRepository;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Merchant> _merchantRepository;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Coupon> _couponRepository;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.UserCoupon> _userCouponRepository;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.CouponRedemptionCode> _redemptionCodeRepository;
private readonly ISqlSugarRepository<EventRegistrationPayment> _paymentRepository;
private readonly ISqlSugarRepository<MerchantCertification> _merchantCertificationRepository;
private readonly ISqlSugarClient _db;
private readonly IIdGenerator _idGenerator;
private readonly WeChatPayService _weChatPayService;
private readonly IOptionsMonitor<WechatTenpayClientOptions> _wechatTenpayClientOptionsMonitor;
private readonly ILogger<UnifiedRegistrationService> _logger;
private readonly OrderNoGeneratorService _orderNoGeneratorService;
private readonly RefundConfigService _refundConfigService;
private readonly ISqlSugarRepository<RefundRecord> _refundRecordRepository;
public UnifiedRegistrationService(
ISqlSugarRepository<EventInfo> eventRepository,
ISqlSugarRepository<EventUnit> unitRepository,
ISqlSugarRepository<EventProject> eventProjectRepository,
ISqlSugarRepository<EventProjectGroup> eventProjectGroupRepository,
ISqlSugarRepository<ProjectGroup> projectGroupRepository,
ISqlSugarRepository<EventProjectRegistrationLimit> projectLimitRepository,
ISqlSugarRepository<YD_Event.Core.Entities.Project> projectRepository,
ISqlSugarRepository<EventRegistration> registrationRepository,
ISqlSugarRepository<EventRegistrationItem> registrationItemRepository,
ISqlSugarRepository<EventRegistrationPerson> registrationPersonRepository,
ISqlSugarRepository<YD_Event.Core.Entities.AppUser> userRepository,
ISqlSugarRepository<SysUser> sysUserRepository,
ISqlSugarRepository<EventFeeConfig> feeConfigRepository,
ISqlSugarRepository<EventRegistrationLimit> limitRepository,
ISqlSugarRepository<YD_Event.Core.Entities.Order> orderRepository,
ISqlSugarRepository<WeChatPayConfig> weChatPayConfigRepository,
ISqlSugarRepository<YD_Event.Core.Entities.Merchant> merchantRepository,
ISqlSugarRepository<YD_Event.Core.Entities.Coupon> couponRepository,
ISqlSugarRepository<YD_Event.Core.Entities.UserCoupon> userCouponRepository,
ISqlSugarRepository<YD_Event.Core.Entities.CouponRedemptionCode> redemptionCodeRepository,
ISqlSugarRepository<EventRegistrationPayment> paymentRepository,
ISqlSugarRepository<MerchantCertification> merchantCertificationRepository,
ISqlSugarClient db,
IIdGenerator idGenerator,
WeChatPayService weChatPayService,
IOptionsMonitor<WechatTenpayClientOptions> wechatTenpayClientOptionsMonitor,
ILogger<UnifiedRegistrationService> logger,
OrderNoGeneratorService orderNoGeneratorService,
RefundConfigService refundConfigService,
ISqlSugarRepository<RefundRecord> refundRecordRepository)
{
_eventRepository = eventRepository;
_unitRepository = unitRepository;
_eventProjectRepository = eventProjectRepository;
_eventProjectGroupRepository = eventProjectGroupRepository;
_projectGroupRepository = projectGroupRepository;
_projectLimitRepository = projectLimitRepository;
_projectRepository = projectRepository;
_registrationRepository = registrationRepository;
_registrationItemRepository = registrationItemRepository;
_registrationPersonRepository = registrationPersonRepository;
_userRepository = userRepository;
_sysUserRepository = sysUserRepository;
_feeConfigRepository = feeConfigRepository;
_limitRepository = limitRepository;
_orderRepository = orderRepository;
_weChatPayConfigRepository = weChatPayConfigRepository;
_merchantRepository = merchantRepository;
_couponRepository = couponRepository;
_userCouponRepository = userCouponRepository;
_redemptionCodeRepository = redemptionCodeRepository;
_paymentRepository = paymentRepository;
_merchantCertificationRepository = merchantCertificationRepository;
_db = db;
_idGenerator = idGenerator;
_weChatPayService = weChatPayService;
_wechatTenpayClientOptionsMonitor = wechatTenpayClientOptionsMonitor;
_logger = logger;
_orderNoGeneratorService = orderNoGeneratorService;
_refundConfigService = refundConfigService;
_refundRecordRepository = refundRecordRepository;
}
/// <summary>
/// 统一报名方法A - 处理核心报名业务逻辑
/// </summary>
[UnitOfWork]
public async Task<UnifiedRegistrationOutput> ProcessRegistrationAsync(UnifiedRegistrationInput input)
{
_logger.LogInformation("开始处理统一报名,赛事ID: {EventId}, 模式: {Mode}", input.EventId, input.RegistrationMode);
// 1. 验证报名
var validationResult = await ValidateRegistrationAsync(input);
if (!validationResult.IsValid)
{
throw Oops.Bah(validationResult.ErrorMessage ?? "报名验证失败");
}
// 2. 获取或创建报名记录
var registration = await GetOrCreateRegistrationAsync(input);
// 3. 创建报名明细和参赛人员
var (totalAmount, totalPersonCount) = await CreateRegistrationDetailsAsync(registration, input);
// 4. 更新报名信息
registration.TotalAmount = totalAmount;
registration.TotalPersonCount = totalPersonCount;
await _registrationRepository.UpdateAsync(registration);
_logger.LogInformation("统一报名处理完成,报名ID: {RegistrationId}", registration.Id);
return new UnifiedRegistrationOutput
{
RegistrationId = registration.Id,
OrderNo = registration.OrderNo,
OriginalAmount = totalAmount,
DiscountAmount = 0,
ActualAmount = totalAmount,
PaymentStatus = registration.PaymentStatus
};
}
/// <summary>
/// 获取或创建报名记录
/// </summary>
private async Task<EventRegistration> GetOrCreateRegistrationAsync(UnifiedRegistrationInput input)
{
var existingRegistration = await _registrationRepository.GetFirstAsync(
r => r.EventId == input.EventId && r.CreatedUserId == input.CreatedUserId && r.PaymentStatus != 4);
if (existingRegistration != null)
{
return await UpdateExistingRegistrationAsync(existingRegistration, input);
}
return await CreateNewRegistrationAsync(input);
}
/// <summary>
/// 更新现有报名记录
/// </summary>
private async Task<EventRegistration> UpdateExistingRegistrationAsync(EventRegistration registration, UnifiedRegistrationInput input)
{
registration.RegistrationMode = input.RegistrationMode;
registration.TeamName = input.RegistrationMode == 2 ? input.TeamName : null;
registration.ContactName = input.ContactName;
registration.ContactPhone = input.ContactPhone;
registration.PaymentStatus = 0;
registration.UpdatedTime = DateTime.Now;
registration.UpdatedUserId = input.CreatedUserId;
// 删除旧的明细和人员
await _registrationItemRepository.DeleteAsync(x => x.RegistrationId == registration.Id);
await _registrationPersonRepository.DeleteAsync(x => x.RegistrationId == registration.Id);
return registration;
}
/// <summary>
/// 创建新的报名记录
/// </summary>
private async Task<EventRegistration> CreateNewRegistrationAsync(UnifiedRegistrationInput input)
{
var registration = new EventRegistration
{
Id = _idGenerator.NewLong(),
EventId = input.EventId,
RegistrationMode = input.RegistrationMode,
TeamName = input.RegistrationMode == 2 ? input.TeamName : null,
ContactName = input.ContactName,
ContactPhone = input.ContactPhone,
PaymentStatus = 0,
RegistrationTime = DateTime.Now,
CreatedUserId = input.CreatedUserId,
CreatedTime = DateTime.Now,
Status = AvailabilityStatus.Enable,
OrderNo = string.Empty
};
await _registrationRepository.InsertAsync(registration);
return registration;
}
/// <summary>
/// 统一支付处理方法 - 处理支付相关逻辑
/// </summary>
[UnitOfWork]
public async Task<UnifiedPaymentOutput> ProcessPaymentAsync(UnifiedPaymentInput input)
{
_logger.LogInformation("开始处理统一支付,报名ID: {RegistrationId}, 支付方式: {PaymentMethod}",
input.RegistrationId, input.PaymentMethod);
// 1. 获取报名信息
var registration = await _registrationRepository.GetByIdAsync(input.RegistrationId);
if (registration == null)
{
throw Oops.Bah("报名记录不存在");
}
// 2. 获取赛事信息
var eventInfo = await _eventRepository.GetByIdAsync(registration.EventId);
if (eventInfo == null)
{
throw Oops.Bah("赛事信息不存在");
}
// 3. 检查是否已存在订单(幂等性处理)
var existingOrder = await _orderRepository.GetFirstAsync(o =>
o.RegistrationId == registration.Id && !o.DeleteMark);
decimal originalAmount, discountAmount, finalAmount;
YD_Event.Core.Entities.Coupon? usedCoupon = null;
YD_Event.Core.Entities.UserCoupon? usedUserCoupon = null;
// 如果订单已存在,使用订单中保存的金额,确保参数一致性
if (existingOrder != null)
{
_logger.LogInformation("找到已存在订单,报名ID: {RegistrationId}, 订单号: {OrderNo}",
registration.Id, existingOrder.OrderNo);
originalAmount = existingOrder.OriginalAmount;
discountAmount = existingOrder.DiscountAmount;
finalAmount = existingOrder.ActualAmount;
// 确保报名记录的订单号与订单一致
if (string.IsNullOrEmpty(registration.OrderNo))
{
registration.OrderNo = existingOrder.OrderNo;
await _registrationRepository.UpdateAsync(registration);
}
}
else
{
// 订单不存在,计算优惠后金额
var calculateResult = await CalculateFinalAmountAsync(
registration.TotalAmount, input.CouponId, input.OperatorUserId);
originalAmount = calculateResult.originalAmount;
discountAmount = calculateResult.discountAmount;
finalAmount = calculateResult.finalAmount;
usedCoupon = calculateResult.coupon;
usedUserCoupon = calculateResult.userCoupon;
}
// 4. 构建支付输出对象
var output = new UnifiedPaymentOutput
{
RegistrationId = registration.Id,
OrderNo = registration.OrderNo,
OriginalAmount = originalAmount,
DiscountAmount = discountAmount,
ActualAmount = finalAmount,
PaymentStatus = registration.PaymentStatus,
IsFree = finalAmount <= 0,
CouponRuleNote = usedCoupon != null ? "优惠券使用后不予退还" : null
};
// 5. 处理免费情况
if (finalAmount <= 0)
{
if (registration.PaymentStatus != 1)
{
await HandleFreePaymentAsync(registration, usedUserCoupon, input.OperatorUserId);
}
output.PaymentStatus = 1;
return output;
}
// 6. 生成订单号(仅在不存在时生成)
if (string.IsNullOrEmpty(registration.OrderNo))
{
registration.OrderNo = await _orderNoGeneratorService.GenerateOrderNoAsync();
output.OrderNo = registration.OrderNo;
}
else
{
output.OrderNo = registration.OrderNo;
}
// 处理支付方式转换逻辑
registration.PaymentMethod = GetPaymentMethodByType(input.PaymentMethodType) ?? input.PaymentMethod;
registration.PaymentMethodType = input.PaymentMethodType;
// 设置支付状态
registration.PaymentStatus = registration.PaymentStatus == 1 ? 1 : 0;
await _registrationRepository.UpdateAsync(registration);
// 7. 创建订单记录(仅在不存在时创建)
if (existingOrder == null)
{
await CreateOrderAsync(registration, eventInfo, originalAmount, discountAmount, finalAmount,
input.CouponId, input.OperatorUserId);
}
// 8. 根据支付方式处理
if (input.PaymentMethod == 2) // 线上支付
{
// 检查是否已经有支付记录或已支付状态,避免重复调用微信支付
var existingPayment = await _paymentRepository.GetFirstAsync(p =>
p.RegistrationId == registration.Id && !p.DeleteMark);
if (registration.PaymentStatus == 1 || existingPayment != null)
{
_logger.LogInformation("订单已支付或已有支付记录,跳过微信统一下单,报名ID: {RegistrationId}",
registration.Id);
output.PaymentStatus = registration.PaymentStatus;
return output;
}
var onlinePaymentOutput = await HandleOnlinePaymentAsync(registration, eventInfo, finalAmount, input);
output.WechatPayParams = onlinePaymentOutput.WechatPayParams;
output.QrcodeUrl = onlinePaymentOutput.QrcodeUrl;
output.TradeType = onlinePaymentOutput.TradeType;
}
else // 线下支付
{
var offlinePaymentOutput = await HandleOfflinePaymentAsync(registration, input);
output.PaymentStatus = offlinePaymentOutput.PaymentStatus;
}
return output;
}
/// <summary>
/// 验证报名
/// </summary>
private async Task<RegistrationValidationResult> ValidateRegistrationAsync(UnifiedRegistrationInput input)
{
var result = new RegistrationValidationResult
{
IsValid = true,
Details = new List<ValidationDetail>()
};
// 1. 验证赛事是否存在且可报名
var eventInfo = await _eventRepository.GetByIdAsync(input.EventId);
if (eventInfo == null)
{
result.IsValid = false;
result.ErrorMessage = "赛事不存在";
result.Details.Add(CreateValidationDetail("赛事验证", false, "赛事不存在"));
return result;
}
// 1.5 验证报名相关配置参数
var configValidationResult = await ValidateRegistrationConfigAsync(eventInfo);
if (!configValidationResult.IsValid)
{
result.IsValid = false;
result.ErrorMessage = configValidationResult.ErrorMessage;
result.Details.AddRange(configValidationResult.Details);
return result;
}
result.Details.AddRange(configValidationResult.Details);
// 2. 验证用户单位认证状态:仅在满足以下特定条件时执行单位认证状态验证
// 条件1:报名类型为个人报名(非团队/机构报名)
// 条件2:用户通过小程序渠道进行报名
// 当同时满足上述两个条件时,执行以下单位认证验证逻辑
var isIndividualRegistration = input.RegistrationMode == 1; // 1-个人报名
var isMiniAppChannel = input.RegistrationChannel == 1; // 1-小程序渠道
if (isIndividualRegistration && isMiniAppChannel)
{
var certValidationResult = await ValidateUserCertificationAsync(input.CreatedUserId);
if (!certValidationResult.IsValid)
{
result.IsValid = false;
result.ErrorMessage = certValidationResult.ErrorMessage;
result.Details.AddRange(certValidationResult.Details);
return result;
}
result.Details.AddRange(certValidationResult.Details);
}
// 3. 验证报名时间
var now = DateTime.Now;
if (now < eventInfo.RegistrationStartTime || now > eventInfo.RegistrationEndTime)
{
result.IsValid = false;
result.ErrorMessage = "不在报名时间内";
result.Details.Add(CreateValidationDetail("时间验证", false, "不在报名时间内"));
return result;
}
// 4. 验证项目选择(一阶段:单选机制,每个项目只能选择一个组别)
var projectSelectionResult = await ValidateProjectSelectionAsync(input);
if (!projectSelectionResult.IsValid)
{
result.IsValid = false;
result.ErrorMessage = projectSelectionResult.ErrorMessage;
result.Details.AddRange(projectSelectionResult.Details);
return result;
}
result.Details.AddRange(projectSelectionResult.Details);
// 5. 验证队员分配(二阶段:人数校验、资质验证、年龄跨组等)
var teamAssignmentResult = await ValidateTeamAssignmentAsync(input);
if (!teamAssignmentResult.IsValid)
{
result.IsValid = false;
result.ErrorMessage = teamAssignmentResult.ErrorMessage;
result.Details.AddRange(teamAssignmentResult.Details);
return result;
}
result.Details.AddRange(teamAssignmentResult.Details);
// 6. 验证报名限制
var limitCheckResult = await CheckRegistrationLimitAsync(input);
if (limitCheckResult.HasLimit)
{
result.IsValid = false;
var firstLimit = limitCheckResult.LimitDetails.FirstOrDefault();
result.ErrorMessage = firstLimit?.LimitMessage ?? "报名限制检查失败";
result.Details.AddRange(limitCheckResult.LimitDetails.Select(l => CreateValidationDetail(l.LimitType, false, l.LimitMessage)));
return result;
}
result.Details.Add(CreateValidationDetail("整体验证", true, "验证通过"));
return result;
}
/// <summary>
/// 验证项目选择(一阶段:单选机制)
/// </summary>
private async Task<RegistrationValidationResult> ValidateProjectSelectionAsync(UnifiedRegistrationInput input)
{
var result = new RegistrationValidationResult
{
IsValid = true,
Details = new List<ValidationDetail>()
};
// 1. 检查是否有重复选择的项目(每个项目只能选择一个组别)
var duplicateProjects = input.Items
.GroupBy(i => i.ProjectId)
.Where(g => g.Count() > 1)
.Select(g => g.Key)
.ToList();
if (duplicateProjects.Any())
{
var eventProjects = await _eventProjectRepository.GetListAsync(x => duplicateProjects.Contains(x.Id));
var projectNames = eventProjects.Select(ep => $"{ep.ProjectId}").ToList();
result.IsValid = false;
result.ErrorMessage = $"以下项目被重复选择,请确保每个项目只选择一个组别:{string.Join(", ", projectNames)}";
result.Details.Add(CreateValidationDetail("项目选择", false, result.ErrorMessage));
return result;
}
result.Details.Add(CreateValidationDetail("项目单选机制", true, "每个项目仅选择一个组别,符合要求"));
return result;
}
/// <summary>
/// 验证队员分配(二阶段)
/// </summary>
private async Task<RegistrationValidationResult> ValidateTeamAssignmentAsync(UnifiedRegistrationInput input)
{
var result = new RegistrationValidationResult
{
IsValid = true,
Details = new List<ValidationDetail>()
};
var eventProjectIds = input.Items.Select(i => i.ProjectId).Distinct().ToList();
var groupIds = input.Items.Where(i => i.GroupId.HasValue).Select(i => i.GroupId.Value).Distinct().ToList();
var eventProjects = await _eventProjectRepository.GetListAsync(x => eventProjectIds.Contains(x.Id));
var projectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList();
var baseProjects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id));
var eventProjectGroups = await _eventProjectGroupRepository.GetListAsync(x =>
eventProjectIds.Contains(x.EventProjectId) || groupIds.Contains(x.Id));
foreach (var item in input.Items)
{
var eventProject = eventProjects.FirstOrDefault(ep => ep.Id == item.ProjectId);
if (eventProject == null)
{
result.IsValid = false;
result.ErrorMessage = $"项目不存在: {item.ProjectId}";
result.Details.Add(CreateValidationDetail("项目验证", false, result.ErrorMessage));
return result;
}
var baseProject = baseProjects.FirstOrDefault(p => p.Id == eventProject.ProjectId);
var isTeamProject = baseProject?.IsTeamProject() ?? false;
if (!item.GroupId.HasValue)
{
result.IsValid = false;
result.ErrorMessage = $"项目组别不能为空";
result.Details.Add(CreateValidationDetail("组别验证", false, result.ErrorMessage));
return result;
}
var eventProjectGroup = eventProjectGroups.FirstOrDefault(epg => epg.Id == item.GroupId.Value);
if (eventProjectGroup == null)
{
result.IsValid = false;
result.ErrorMessage = $"项目组别不存在: {item.GroupId.Value}";
result.Details.Add(CreateValidationDetail("组别验证", false, result.ErrorMessage));
return result;
}
var teamsToValidate = GetTeamsFromItem(item);
if (!teamsToValidate.Any())
{
result.IsValid = false;
result.ErrorMessage = $"请为项目添加至少一个团队和队员";
result.Details.Add(CreateValidationDetail("团队验证", false, result.ErrorMessage));
return result;
}
foreach (var team in teamsToValidate)
{
if (team.Persons == null || !team.Persons.Any())
{
result.IsValid = false;
result.ErrorMessage = $"团队{team.TeamIndex}请至少添加一个队员";
result.Details.Add(CreateValidationDetail("团队验证", false, result.ErrorMessage));
return result;
}
var qualificationResult = ValidatePersonQualifications(team.Persons, eventProjectGroup, isTeamProject);
if (!qualificationResult.IsValid)
{
result.IsValid = false;
result.ErrorMessage = qualificationResult.ErrorMessage;
result.Details.AddRange(qualificationResult.Details);
return result;
}
result.Details.AddRange(qualificationResult.Details);
}
}
return result;
}
/// <summary>
/// 从报名项中获取团队列表(支持新旧数据结构)
/// </summary>
private static List<UnifiedTeamInput> GetTeamsFromItem(UnifiedRegistrationItemInput item)
{
if (item.Teams != null && item.Teams.Any())
{
return item.Teams;
}
if (item.Persons != null && item.Persons.Any())
{
return new List<UnifiedTeamInput>
{
new()
{
TeamIndex = 1,
Persons = item.Persons
}
};
}
return new List<UnifiedTeamInput>();
}
/// <summary>
/// 从报名项中获取所有人员列表(支持新旧数据结构)
/// </summary>
private static List<UnifiedPersonInput> GetAllPersonsFromItem(UnifiedRegistrationItemInput item)
{
var persons = new List<UnifiedPersonInput>();
var teams = GetTeamsFromItem(item);
foreach (var team in teams)
{
if (team.Persons != null)
{
persons.AddRange(team.Persons);
}
}
return persons;
}
/// <summary>
/// 验证队员资质(年龄和性别)
/// </summary>
public RegistrationValidationResult ValidatePersonQualifications(List<UnifiedPersonInput> persons, EventProjectGroup group, bool isTeamProject)
{
var result = new RegistrationValidationResult
{
IsValid = true,
Details = new List<ValidationDetail>()
};
// 首先验证性别,发现问题立即返回
var genderError = ValidatePersonGenders(persons, group);
if (genderError != null)
{
return genderError;
}
// 验证年龄限制和跨组规则
var ageError = ValidatePersonAges(persons, group, isTeamProject, result);
if (ageError != null)
{
return ageError;
}
result.Details.Add(CreateValidationDetail("队员资质", true, "所有队员的性别符合组别要求,年龄符合规则"));
return result;
}
/// <summary>
/// 验证队员性别
/// </summary>
private static RegistrationValidationResult? ValidatePersonGenders(List<UnifiedPersonInput> persons, EventProjectGroup group)
{
if (group.Gender == Gender.All)
{
return null;
}
foreach (var person in persons)
{
if (person.Gender != group.Gender)
{
var errorMessage = $"队员{person.Name}的性别不符合组别要求。要求:{group.Gender.Description()},实际:{person.Gender.Description()}";
return new RegistrationValidationResult
{
IsValid = false,
ErrorMessage = errorMessage,
Details = new List<ValidationDetail> { CreateValidationDetail("性别验证", false, errorMessage) }
};
}
}
return null;
}
/// <summary>
/// 验证队员年龄
/// </summary>
private static RegistrationValidationResult? ValidatePersonAges(List<UnifiedPersonInput> persons, EventProjectGroup group, bool isTeamProject, RegistrationValidationResult result)
{
// 检查是否有超过最大年龄限制的队员
var aboveAgePerson = persons.FirstOrDefault(p => p.BirthDate > group.BirthDateEnd);
if (aboveAgePerson != null)
{
result.IsValid = false;
result.ErrorMessage = $"队员{aboveAgePerson.Name}的年龄超过组别最大限制。要求出生日期不晚于:{group.BirthDateEnd:yyyy-MM-dd},实际:{aboveAgePerson.BirthDate:yyyy-MM-dd}";
result.Details.Add(CreateValidationDetail("年龄验证", false, result.ErrorMessage));
return result;
}
// 统计年龄状态
var inTargetCount = persons.Count(p => p.BirthDate >= group.BirthDateStart && p.BirthDate <= group.BirthDateEnd);
var belowTargetCount = persons.Count(p => p.BirthDate < group.BirthDateStart);
bool hasBelow = belowTargetCount > 0;
bool allBelow = belowTargetCount == persons.Count;
// 团队项目跨组验证
if (isTeamProject && hasBelow && inTargetCount == 0)
{
string errorMessage = allBelow
? "跨组报名时,团队中所有成员年龄均小于目标年龄段,团队中至少需要有一名成员属于目标年龄段"
: "跨组报名时,团队中至少需要有一名成员属于目标年龄段";
string itemName = allBelow
? "年龄跨组验证(全部小于目标年龄)"
: "年龄跨组验证";
result.IsValid = false;
result.ErrorMessage = errorMessage;
result.Details.Add(CreateValidationDetail(itemName, false, errorMessage));
return result;
}
// 添加验证通过的详情
result.Details.Add(hasBelow
? CreateValidationDetail("年龄跨组", true, "符合年龄跨组报名要求")
: CreateValidationDetail("年龄验证", true, "所有队员均在目标年龄段内"));
return null;
}
/// <summary>
/// 创建报名明细和参赛人员
/// </summary>
private async Task<(decimal totalAmount, int totalPersonCount)> CreateRegistrationDetailsAsync(
EventRegistration registration, UnifiedRegistrationInput input)
{
// 获取相关数据
var eventProjectIds = input.Items.Select(i => i.ProjectId).Distinct().ToList();
var groupIds = input.Items.Where(i => i.GroupId.HasValue).Select(i => i.GroupId.Value).Distinct().ToList();
var eventProjects = await _eventProjectRepository.GetListAsync(x => eventProjectIds.Contains(x.Id));
var projectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList();
var baseProjects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id));
var eventProjectGroups = await _eventProjectGroupRepository.GetListAsync(x =>
eventProjectIds.Contains(x.EventProjectId) || groupIds.Contains(x.Id));
var feeConfig = await _feeConfigRepository.GetFirstAsync(x => x.EventId == input.EventId && !x.DeleteMark);
var regItems = new List<EventRegistrationItem>();
var persons = new List<EventRegistrationPerson>();
int totalPersonCount = 0;
foreach (var item in input.Items)
{
var eventProject = eventProjects.FirstOrDefault(p => p.Id == item.ProjectId);
if (eventProject == null) continue;
var baseProject = baseProjects.FirstOrDefault(p => p.Id == eventProject.ProjectId);
var groupName = GetGroupName(item, eventProjectGroups);
var teamsToProcess = GetTeamsFromItem(item);
foreach (var team in teamsToProcess)
{
var (unitPrice, totalPrice) = CalculateItemPrice(feeConfig, eventProject, team.Persons.Count);
var regItemId = _idGenerator.NewLong();
var regItem = new EventRegistrationItem
{
Id = regItemId,
RegistrationId = registration.Id,
ProjectId = item.ProjectId,
EventProjectGroupId = item.GroupId,
ProjectName = baseProject?.Name ?? "",
GroupName = groupName,
ProjectTypeName = baseProject?.ProjectType.Description() ?? "",
UnitPrice = unitPrice,
PersonCount = team.Persons.Count,
TotalPrice = totalPrice,
CreatedUserId = input.CreatedUserId,
CreatedTime = DateTime.Now
};
regItems.Add(regItem);
totalPersonCount += team.Persons.Count;
foreach (var pInput in team.Persons)
{
persons.Add(new EventRegistrationPerson
{
Id = _idGenerator.NewLong(),
RegistrationId = registration.Id,
RegistrationItemId = regItemId,
ProjectId = item.ProjectId,
EventProjectGroupId = item.GroupId,
GroupName = groupName,
ProjectName = baseProject?.Name ?? "",
Name = pInput.Name,
Gender = pInput.Gender,
IdCard = pInput.IdCardNumber,
BirthDate = pInput.BirthDate,
Phone = pInput.Mobile,
Age = CalculateAge(pInput.BirthDate),
PersonType = PersonType.Athlete,
UserId = pInput.UserId ?? 0,
CreatedUserId = input.CreatedUserId,
CreatedTime = DateTime.Now
});
}
}
}
await _registrationItemRepository.InsertRangeAsync(regItems);
await _registrationPersonRepository.InsertRangeAsync(persons);
var totalAmount = CalculateTotalAmount(feeConfig, eventProjects, input.Items, totalPersonCount);
return (totalAmount, totalPersonCount);
}
/// <summary>
/// 计算最终金额(含优惠券)
/// </summary>
private async Task<(decimal originalAmount, decimal discountAmount, decimal finalAmount, YD_Event.Core.Entities.Coupon? coupon, YD_Event.Core.Entities.UserCoupon? userCoupon)>
CalculateFinalAmountAsync(decimal originalAmount, long? couponId, long userId)
{
decimal discountAmount = 0;
decimal finalAmount = originalAmount;
YD_Event.Core.Entities.Coupon? coupon = null;
YD_Event.Core.Entities.UserCoupon? userCoupon = null;
if (couponId.HasValue)
{
coupon = await _couponRepository.GetFirstAsync(c =>
c.Id == couponId.Value && c.Status == AvailabilityStatus.Enable && !c.DeleteMark);
if (coupon != null)
{
userCoupon = await _userCouponRepository.GetFirstAsync(uc =>
uc.CouponId == coupon.Id && uc.UserId == userId && uc.Status == 0 && !uc.DeleteMark);
if (userCoupon != null)
{
(discountAmount, finalAmount) = CalculateCouponDiscount(coupon, originalAmount);
}
}
}
return (originalAmount, discountAmount, finalAmount, coupon, userCoupon);
}
/// <summary>
/// 计算优惠券折扣
/// </summary>
private static (decimal discountAmount, decimal finalAmount) CalculateCouponDiscount(
YD_Event.Core.Entities.Coupon coupon, decimal originalAmount)
{
decimal discountAmount = 0;
if (coupon.Type == 0) // 折扣
{
discountAmount = originalAmount * (1 - coupon.Value / 100);
}
else if (coupon.Type == 1) // 定额
{
discountAmount = Math.Min(coupon.Value, originalAmount);
}
var finalAmount = Math.Max(0, originalAmount - discountAmount);
return (discountAmount, finalAmount);
}
/// <summary>
/// 根据支付方式类型获取对应的支付方式
/// </summary>
private static int? GetPaymentMethodByType(PaymentMethodType type)
{
return type switch
{
PaymentMethodType.WeChatPay or PaymentMethodType.AliPay => 2, // 线上支付
PaymentMethodType.BankTransfer => 1, // 线下支付
_ => null
};
}
/// <summary>
/// 处理免费支付
/// </summary>
private async Task HandleFreePaymentAsync(EventRegistration registration, YD_Event.Core.Entities.UserCoupon? userCoupon, long operatorUserId)
{
if (userCoupon != null)
{
userCoupon.Status = 1;
userCoupon.UseTime = DateTime.Now;
await _userCouponRepository.UpdateAsync(userCoupon);
}
registration.PaymentStatus = 1;
registration.PaymentTime = DateTime.Now;
registration.UpdatedTime = DateTime.Now;
registration.UpdatedUserId = operatorUserId;
await _registrationRepository.UpdateAsync(registration);
}
/// <summary>
/// 创建订单记录
/// </summary>
private async Task<YD_Event.Core.Entities.Order> CreateOrderAsync(EventRegistration registration, EventInfo eventInfo,
decimal originalAmount, decimal discountAmount, decimal finalAmount, long? couponId, long operatorUserId)
{
var certification = await _merchantCertificationRepository.GetFirstAsync(c =>
c.UserId == registration.CreatedUserId && c.AuditStatus == AuditStatus.Pass && !c.DeleteMark);
var order = new YD_Event.Core.Entities.Order
{
Id = _idGenerator.NewLong(),
OrderNo = registration.OrderNo,
OrderType = 1,
MerchantId = eventInfo.MerchantId,
MerchantCertificationId = certification?.Id,
EventId = registration.EventId,
RegistrationId = registration.Id,
OriginalAmount = originalAmount,
ActualAmount = finalAmount,
DiscountAmount = discountAmount,
CouponId = couponId,
OrderStatus = OrderStatus.Pending,
CreatedTime = DateTime.Now,
CreatedUserId = operatorUserId,
Status = AvailabilityStatus.Enable
};
await _orderRepository.InsertAsync(order);
return order;
}
/// <summary>
/// 处理线上支付
/// </summary>
private async Task<UnifiedPaymentOutput> HandleOnlinePaymentAsync(EventRegistration registration,
EventInfo eventInfo, decimal finalAmount, UnifiedPaymentInput input)
{
var payConfig = await GetWeChatPayConfigAsync(eventInfo);
if (payConfig == null)
{
throw Oops.Bah("商户微信支付配置不存在");
}
var output = new UnifiedPaymentOutput
{
RegistrationId = registration.Id,
OrderNo = registration.OrderNo,
PaymentStatus = 0,
IsFree = false
};
return !string.IsNullOrEmpty(input.OpenId)
? await HandleJsApiPaymentAsync(registration, eventInfo, finalAmount, input, payConfig, output)
: await HandleNativePaymentAsync(registration, eventInfo, finalAmount, payConfig, output);
}
/// <summary>
/// 获取微信支付配置
/// </summary>
private async Task<WeChatPayConfig?> GetWeChatPayConfigAsync(EventInfo eventInfo)
{
if (eventInfo.MerchantId.HasValue)
{
return await _weChatPayConfigRepository.GetFirstAsync(x =>
x.MerchantId == eventInfo.MerchantId.Value && x.Status == AvailabilityStatus.Enable && !x.DeleteMark);
}
return null;
}
/// <summary>
/// 处理 JSAPI 支付
/// </summary>
private async Task<UnifiedPaymentOutput> HandleJsApiPaymentAsync(
EventRegistration registration, EventInfo eventInfo, decimal finalAmount,
UnifiedPaymentInput input, WeChatPayConfig payConfig, UnifiedPaymentOutput output)
{
var configOptions = _wechatTenpayClientOptionsMonitor.CurrentValue;
// 1. 先查询微信侧是否已有预支付单,避免重复下单导致参数不一致
var queryResult = await _weChatPayService.QueryOrderAsync(payConfig, registration.OrderNo);
if (queryResult.Success)
{
_logger.LogInformation("查询到微信侧已有预支付单,订单号:{OrderNo},交易状态:{TradeState}",
registration.OrderNo, queryResult.TradeState);
// 如果订单已经成功支付,直接返回
if (queryResult.TradeState == "SUCCESS")
{
_logger.LogInformation("订单已在微信侧支付成功,更新本地状态,订单号:{OrderNo}", registration.OrderNo);
await HandlePaymentSuccessAsync(
registration.OrderNo,
queryResult.TransactionId,
queryResult.SuccessTime?.DateTime,
input.OperatorUserId);
output.PaymentStatus = 1;
return output;
}
// 如果订单已存在但未支付,我们需要确认是否可以重试
// 微信支付允许使用相同订单号重试,但参数必须完全一致
// 为了确保参数一致性,我们使用查询到的信息来验证
if (queryResult.TradeState == "NOTPAY" || queryResult.TradeState == "CLOSED")
{
_logger.LogInformation("订单状态为 {TradeState},将重新调用统一下单,订单号:{OrderNo}",
queryResult.TradeState, registration.OrderNo);
}
}
// 2. 构建统一下单请求
var jsapiRequest = new CreateJsApiPayRequest
{
OutTradeNo = registration.OrderNo,
AppId = configOptions.AppId,
Description = $"{eventInfo.EventName} - 报名费",
NotifyUrl = configOptions.NotifyUrl,
TotalAmount = AmountHelper.YuanToFen(finalAmount),
// 固定过期时间计算方式,确保重试时参数一致
// 从订单创建时间开始计算30分钟,而不是当前时间
ExpireTime = registration.CreatedTime.AddMinutes(30),
OpenId = input.OpenId
};
// 3. 调用微信统一下单
var payResult = await _weChatPayService.CreateJsApiPayAsync(payConfig, jsapiRequest);
if (!payResult.Success || payResult.JsApiParameters == null)
{
_logger.LogError("微信JSAPI支付下单失败,订单号:{OrderNo},错误:{Error}", registration.OrderNo, payResult.Message);
throw Oops.Bah($"微信统一下单失败:{payResult.Message}");
}
output.WechatPayParams = new
{
appId = payResult.JsApiParameters.AppId,
timeStamp = payResult.JsApiParameters.TimeStamp,
nonceStr = payResult.JsApiParameters.NonceStr,
package = payResult.JsApiParameters.Package,
signType = payResult.JsApiParameters.SignType,
paySign = payResult.JsApiParameters.PaySign
};
output.TradeType = payResult.TradeType;
_logger.LogInformation("微信JSAPI支付下单成功,订单号:{OrderNo}", registration.OrderNo);
return output;
}
/// <summary>
/// 处理 Native 支付
/// </summary>
private async Task<UnifiedPaymentOutput> HandleNativePaymentAsync(
EventRegistration registration, EventInfo eventInfo, decimal finalAmount,
WeChatPayConfig payConfig, UnifiedPaymentOutput output)
{
var configOptions = _wechatTenpayClientOptionsMonitor.CurrentValue;
// 1. 先查询微信侧是否已有预支付单,避免重复下单导致参数不一致
var queryResult = await _weChatPayService.QueryOrderAsync(payConfig, registration.OrderNo);
if (queryResult.Success)
{
_logger.LogInformation("查询到微信侧已有预支付单,订单号:{OrderNo},交易状态:{TradeState}",
registration.OrderNo, queryResult.TradeState);
// 如果订单已经成功支付,直接返回
if (queryResult.TradeState == "SUCCESS")
{
_logger.LogInformation("订单已在微信侧支付成功,更新本地状态,订单号:{OrderNo}", registration.OrderNo);
await HandlePaymentSuccessAsync(
registration.OrderNo,
queryResult.TransactionId,
queryResult.SuccessTime?.DateTime,
registration.CreatedUserId);
output.PaymentStatus = 1;
return output;
}
// 如果订单已存在但未支付,我们需要确认是否可以重试
if (queryResult.TradeState == "NOTPAY" || queryResult.TradeState == "CLOSED")
{
_logger.LogInformation("订单状态为 {TradeState},将重新调用统一下单,订单号:{OrderNo}",
queryResult.TradeState, registration.OrderNo);
}
}
// 2. 构建统一下单请求
var nativeRequest = new CreateNativePayRequest
{
OutTradeNo = registration.OrderNo,
AppId = configOptions.AppId,
Description = $"{eventInfo.EventName} - 报名费",
NotifyUrl = configOptions.NotifyUrl,
TotalAmount = AmountHelper.YuanToFen(finalAmount),
// 固定过期时间计算方式,确保重试时参数一致
ExpireTime = registration.CreatedTime.AddMinutes(5)
};
// 3. 调用微信统一下单
var payResult = await _weChatPayService.CreateNativePayAsync(payConfig, nativeRequest);
if (!payResult.Success)
{
_logger.LogError("微信Native支付下单失败,订单号:{OrderNo},错误:{Error}", registration.OrderNo, payResult.Message);
throw Oops.Bah($"微信统一下单失败:{payResult.Message}");
}
output.QrcodeUrl = payResult.QrcodeUrl;
output.TradeType = payResult.TradeType;
_logger.LogInformation("微信Native支付下单成功,订单号:{OrderNo}", registration.OrderNo);
return output;
}
/// <summary>
/// 处理线下支付
/// </summary>
private async Task<UnifiedPaymentOutput> HandleOfflinePaymentAsync(EventRegistration registration, UnifiedPaymentInput input)
{
registration.PaymentProofUrl = input.PaymentProofUrl;
registration.PaymentStatus = 0; // 待审核
registration.UpdatedTime = DateTime.Now;
registration.UpdatedUserId = input.OperatorUserId;
await _registrationRepository.UpdateAsync(registration);
_logger.LogInformation("线下支付提交审核,报名ID:{RegistrationId},凭证URL:{ProofUrl}",
registration.Id, input.PaymentProofUrl);
return new UnifiedPaymentOutput
{
RegistrationId = registration.Id,
OrderNo = registration.OrderNo,
PaymentStatus = 0, // 待审核
IsFree = false
};
}
/// <summary>
/// 检查报名限制
/// </summary>
public async Task<RegistrationLimitCheckOutput> CheckRegistrationLimitAsync(UnifiedRegistrationInput input)
{
var result = new RegistrationLimitCheckOutput
{
HasLimit = false,
LimitDetails = new List<RegistrationLimitDetail>()
};
_logger.LogInformation("开始检查报名限制,赛事ID: {EventId}", input.EventId);
var limit = await _limitRepository.GetFirstAsync(x => x.EventId == input.EventId && !x.DeleteMark);
var projectLimits = await _projectLimitRepository.GetListAsync(x => x.EventId == input.EventId && !x.DeleteMark);
var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == input.EventId);
var projects = await _projectRepository.GetListAsync(x => eventProjects.Select(ep => ep.ProjectId).Contains(x.Id) && !x.DeleteMark);
var eventProjectGroups = await _eventProjectGroupRepository.GetListAsync(x => x.EventId == input.EventId && !x.DeleteMark);
// 先获取所有报名信息,供后续检查使用
// 只统计已支付的报名记录(PaymentStatus == 1),避免未支付/已取消的报名占用名额
var eventRegistrations = await _registrationRepository.GetListAsync(x =>
x.EventId == input.EventId && x.PaymentStatus == 1 && !x.DeleteMark);
var eventRegistrationIds = eventRegistrations.Select(r => r.Id).ToList();
var registrationPersons = await _registrationPersonRepository.GetListAsync(x =>
eventRegistrationIds.Contains(x.RegistrationId) && !x.DeleteMark);
var allRegistrationItems = await _registrationItemRepository.GetListAsync(x =>
eventRegistrationIds.Contains(x.RegistrationId) && !x.DeleteMark);
_logger.LogInformation("获取到赛事报名记录: {RegistrationCount},报名人员: {PersonCount},报名项目: {ItemCount}",
eventRegistrations.Count, registrationPersons.Count, allRegistrationItems.Count);
// ==================== 报名限制检查流程 ====================
// 检查顺序说明:
// 1. 先检查团队组别容量限制 - 检查是否超过组别限报的团队数量
// 2. 再检查成员项目限报 - 检查队员是否超过个人/团队项目的限报数量
// 3. 然后检查人员项目组别重复 - 检查同一人员是否在同一项目的多个组别中重复报名
// 4. 再检查人员是否已报名 - 检查人员是否已在其他报名记录中报过同一项目组别
// 5. 最后检查项目完成人数 - 确保人数与项目的完成人数完全一致
// 检查1:团队组别容量限制
// 验证是否有组别超过了限报的团队数量(例如:某组别限报2队,但本次报了3队)
CheckTeamGroupCapacityLimitAsync(input, result, projectLimits, eventProjects, projects, eventProjectGroups);
// 检查2:成员项目限报
// 验证队员是否超过了个人项目或团队项目的限报数量(例如:某队员已报2个个人项目,限报2个,又报第3个)
await CheckMemberProjectLimitAsync(input, result, limit, eventProjects, projects, registrationPersons, allRegistrationItems);
// 检查3:人员项目组别重复
// 验证同一人员是否在本次报名中重复选择了同一项目的多个组别(例如:同一队员在同一项目的男子组和女子组都报了名)
CheckPersonProjectDuplicateAsync(input, result, eventProjects, projects, eventProjectGroups);
// 检查4:人员是否已在其他报名中报名
// 验证人员是否已在其他已支付的报名记录中报过同一项目组别(避免重复占用名额)
CheckPersonAlreadyRegisteredAsync(input, result, eventProjects, projects, eventProjectGroups, registrationPersons, allRegistrationItems, eventRegistrationIds);
// 检查5:项目完成人数验证
// 验证用户选择的参与人数必须与项目的"完成人数"完全一致(例如:项目要求完成3人,但只选了2人或选了4人)
CheckProjectCompletionAsync(input, result, eventProjects, projects, eventProjectGroups);
_logger.LogInformation("报名限制检查完成,是否存在限制: {HasLimit}, 限制详情数: {LimitCount}",
result.HasLimit, result.LimitDetails.Count);
return result;
}
/// <summary>
/// 创建报名限制详情
/// </summary>
private static RegistrationLimitDetail CreateLimitDetail(long memberId, string memberName, string limitType, string limitMessage)
{
return new RegistrationLimitDetail
{
MemberId = memberId,
MemberName = memberName,
LimitType = limitType,
LimitMessage = limitMessage
};
}
/// <summary>
/// 创建验证详情
/// </summary>
private static ValidationDetail CreateValidationDetail(string itemName, bool isPassed, string message)
{
return new ValidationDetail
{
ItemName = itemName,
IsPassed = isPassed,
Message = message
};
}
/// <summary>
/// 检查团队组别容量限制(项目组别限报设置)
/// </summary>
private void CheckTeamGroupCapacityLimitAsync(
UnifiedRegistrationInput input,
RegistrationLimitCheckOutput result,
List<EventProjectRegistrationLimit> projectLimits,
List<EventProject> eventProjects,
List<YD_Event.Core.Entities.Project> projects,
List<EventProjectGroup> eventProjectGroups)
{
foreach (var item in input.Items)
{
if (!item.GroupId.HasValue) continue;
// 获取组别信息
var eventProjectGroup = eventProjectGroups.FirstOrDefault(epg => epg.Id == item.GroupId.Value);
if (eventProjectGroup == null) continue;
// 获取赛事项目信息
var eventProject = eventProjects.FirstOrDefault(ep => ep.Id == eventProjectGroup.EventProjectId);
if (eventProject == null) continue;
// 获取基础项目信息
var project = projects.FirstOrDefault(p => p.Id == eventProject.ProjectId);
if (project == null) continue;
// 判断是否为团队项目
var isTeamProject = project.IsTeamProject();
// 获取项目限报配置
var projectLimit = projectLimits.FirstOrDefault(pl => pl.ProjectId == eventProject.Id);
if (projectLimit == null) continue;
if (isTeamProject)
{
// ==================== 团队项目验证 ====================
// 检查该组别申报的团队数量是否超过限报配置
var teams = GetTeamsFromItem(item);
var submittedTeamCount = teams.Count;
if (submittedTeamCount > projectLimit.LimitCount)
{
_logger.LogWarning("团队项目组别容量超限,项目: {ProjectName},组别: {GroupName},限报: {LimitCount}队,提交: {SubmittedCount}队",
project.Name, eventProjectGroup.GroupName, projectLimit.LimitCount, submittedTeamCount);
result.HasLimit = true;
result.LimitDetails.Add(CreateLimitDetail(
0,
eventProjectGroup.GroupName ?? "未知组别",
"ProjectGroupCapacity",
$"{project.Name}项目已达到报名限制"));
}
}
else
{
// ==================== 个人项目验证 ====================
// 检查该组别申报的人员数量是否超过限报配置
var persons = GetAllPersonsFromItem(item);
var submittedPersonCount = persons.Count;
if (submittedPersonCount > projectLimit.LimitCount)
{
_logger.LogWarning("个人项目组别容量超限,项目: {ProjectName},组别: {GroupName},限报: {LimitCount}人,提交: {SubmittedCount}人",
project.Name, eventProjectGroup.GroupName, projectLimit.LimitCount, submittedPersonCount);
result.HasLimit = true;
result.LimitDetails.Add(CreateLimitDetail(
0,
eventProjectGroup.GroupName ?? "未知组别",
"ProjectGroupCapacity",
$"{project.Name}项目已达到报名限制"));
}
}
}
}
/// <summary>
/// 检查成员项目限报
/// </summary>
private async Task CheckMemberProjectLimitAsync(
UnifiedRegistrationInput input,
RegistrationLimitCheckOutput result,
EventRegistrationLimit? limit,
List<EventProject> eventProjects,
List<YD_Event.Core.Entities.Project> projects,
List<EventRegistrationPerson> registrationPersons,
List<EventRegistrationItem> allRegistrationItems)
{
var memberIdsList = new List<long>();
foreach (var item in input.Items)
{
var persons = GetAllPersonsFromItem(item);
memberIdsList.AddRange(persons.Where(p => p.UserId.HasValue).Select(p => p.UserId.Value));
}
var memberIds = memberIdsList.Distinct().ToList();
if (limit == null || !memberIds.Any())
{
return;
}
var members = await _userRepository.GetListAsync(x => memberIds.Contains(x.Id) && !x.DeleteMark);
_logger.LogInformation("检查报名限制的成员数量: {MemberCount}", members.Count);
foreach (var member in members)
{
_logger.LogDebug("检查成员报名限制,成员ID: {MemberId}, 姓名: {MemberName}", member.Id, member.Name);
var (individualProjectCount, teamProjectCount) = CalculateMemberProjectCounts(
member.Id, eventProjects, projects, registrationPersons, allRegistrationItems, input);
_logger.LogDebug("成员 {MemberName} 个人项目报名数: {IndividualCount}, 团队项目报名数: {TeamCount}",
member.Name, individualProjectCount, teamProjectCount);
CheckIndividualProjectLimit(result, limit, member, individualProjectCount);
CheckTeamProjectLimit(result, limit, member, teamProjectCount);
}
}
/// <summary>
/// 计算成员项目报名数量(包含本次提交的项目)
/// </summary>
private (int individualCount, int teamCount) CalculateMemberProjectCounts(
long memberId,
List<EventProject> eventProjects,
List<YD_Event.Core.Entities.Project> projects,
List<EventRegistrationPerson> registrationPersons,
List<EventRegistrationItem> allRegistrationItems,
UnifiedRegistrationInput input)
{
var matchedPersons = registrationPersons.Where(x => x.UserId == memberId).ToList();
var personRegistrationIds = matchedPersons.Select(x => x.RegistrationId).Distinct().ToList();
var personRegistrationItems = allRegistrationItems.Where(ri => personRegistrationIds.Contains(ri.RegistrationId)).ToList();
int individualProjectCount = 0;
int teamProjectCount = 0;
// 统计已存在的报名项目
foreach (var item in personRegistrationItems)
{
var eventProject = eventProjects.FirstOrDefault(ep => ep.Id == item.ProjectId);
if (eventProject == null) continue;
var baseProject = projects.FirstOrDefault(p => p.Id == eventProject.ProjectId);
if (baseProject == null) continue;
if (baseProject.IsIndividualProject())
{
individualProjectCount++;
}
else if (baseProject.IsTeamProject())
{
teamProjectCount++;
}
}
// 统计本次提交的项目,确保不重复统计
var existingProjectIds = new HashSet<long>(personRegistrationItems.Select(x => x.ProjectId));
foreach (var item in input.Items)
{
// 检查该成员是否在本次提交的项目中
var allPersonsInItem = GetAllPersonsFromItem(item);
if (!allPersonsInItem.Any(p => p.UserId.HasValue && p.UserId.Value == memberId))
{
continue;
}
// 检查是否已经统计过该项目
if (existingProjectIds.Contains(item.ProjectId))
{
continue;
}
var eventProject = eventProjects.FirstOrDefault(ep => ep.Id == item.ProjectId);
if (eventProject == null) continue;
var baseProject = projects.FirstOrDefault(p => p.Id == eventProject.ProjectId);
if (baseProject == null) continue;
if (baseProject.IsIndividualProject())
{
individualProjectCount++;
}
else if (baseProject.IsTeamProject())
{
teamProjectCount++;
}
// 标记该项目已统计
existingProjectIds.Add(item.ProjectId);
}
return (individualProjectCount, teamProjectCount);
}
/// <summary>
/// 检查个人项目限报
/// </summary>
private void CheckIndividualProjectLimit(
RegistrationLimitCheckOutput result,
EventRegistrationLimit limit,
YD_Event.Core.Entities.AppUser member,
int individualProjectCount)
{
if (individualProjectCount <= limit.IndividualProjectLimit)
{
return;
}
_logger.LogWarning("个人项目限报超限,成员: {MemberName}, 已报名: {Count}, 限制: {Limit}",
member.Name, individualProjectCount, limit.IndividualProjectLimit);
result.HasLimit = true;
result.LimitDetails.Add(CreateLimitDetail(
member.Id,
member.Name ?? member.NickName ?? "未知",
"IndividualProjectLimit",
$"{member.Name}队员已达到报名限制"));
}
/// <summary>
/// 检查团队项目限报
/// </summary>
private void CheckTeamProjectLimit(
RegistrationLimitCheckOutput result,
EventRegistrationLimit limit,
YD_Event.Core.Entities.AppUser member,
int teamProjectCount)
{
if (teamProjectCount <= limit.TeamProjectLimit)
{
return;
}
_logger.LogWarning("团队项目限报超限,成员: {MemberName}, 已报名: {Count}, 限制: {Limit}",
member.Name, teamProjectCount, limit.TeamProjectLimit);
result.HasLimit = true;
result.LimitDetails.Add(CreateLimitDetail(
member.Id,
member.Name ?? member.NickName ?? "未知",
"TeamProjectLimit",
$"{member.Name}队员已达到报名限制"));
}
/// <summary>
/// 检查人员项目组别重复
/// </summary>
private void CheckPersonProjectDuplicateAsync(
UnifiedRegistrationInput input,
RegistrationLimitCheckOutput result,
List<EventProject> eventProjects,
List<YD_Event.Core.Entities.Project> projects,
List<EventProjectGroup> eventProjectGroups)
{
var personProjectGroupMap = new Dictionary<string, List<(long GroupId, string GroupName, string ProjectName)>>();
foreach (var item in input.Items)
{
if (!item.GroupId.HasValue) continue;
var eventProjectGroup = eventProjectGroups.FirstOrDefault(epg => epg.Id == item.GroupId.Value);
if (eventProjectGroup == null) continue;
var eventProject = eventProjects.FirstOrDefault(ep => ep.Id == eventProjectGroup.EventProjectId);
if (eventProject == null) continue;
var project = projects.FirstOrDefault(p => p.Id == eventProject.ProjectId);
var projectName = project?.Name ?? "未知项目";
var allPersonsInItem = GetAllPersonsFromItem(item);
foreach (var person in allPersonsInItem)
{
var key = person.UserId.HasValue ? person.UserId.Value.ToString() : person.IdCardNumber;
if (string.IsNullOrEmpty(key)) continue;
if (!personProjectGroupMap.ContainsKey(key))
{
personProjectGroupMap[key] = new List<(long GroupId, string GroupName, string ProjectName)>();
}
personProjectGroupMap[key].Add((item.GroupId.Value, eventProjectGroup.GroupName ?? "未知组别", projectName));
}
}
foreach (var kvp in personProjectGroupMap)
{
var key = kvp.Key;
var groupInfos = kvp.Value;
var groupDuplicates = groupInfos
.GroupBy(g => g.GroupId)
.Where(g => g.Count() > 1)
.ToList();
if (!groupDuplicates.Any()) continue;
foreach (var duplicate in groupDuplicates)
{
var groupInfo = duplicate.First();
var person = input.Items.SelectMany(i => i.Persons).FirstOrDefault(p =>
(p.UserId.HasValue && p.UserId.Value.ToString() == key) ||
(!p.UserId.HasValue && p.IdCardNumber == key));
var personName = person?.Name ?? "未知人员";
_logger.LogWarning("人员项目组别重复,人员: {PersonName}, 项目: {ProjectName}, 组别: {GroupName}",
personName, groupInfo.ProjectName, groupInfo.GroupName);
result.HasLimit = true;
result.LimitDetails.Add(CreateLimitDetail(
0,
personName,
"PersonProjectDuplicate",
$"人员项目重复检测:人员\"{personName}\"在项目\"{groupInfo.ProjectName}\"的组别\"{groupInfo.GroupName}\"中重复出现,同一人员只能在一个团队中报名同一项目组别"));
}
}
}
/// <summary>
/// 检查人员是否已在其他报名中报过同一项目组别
/// </summary>
private void CheckPersonAlreadyRegisteredAsync(
UnifiedRegistrationInput input,
RegistrationLimitCheckOutput result,
List<EventProject> eventProjects,
List<YD_Event.Core.Entities.Project> projects,
List<EventProjectGroup> eventProjectGroups,
List<EventRegistrationPerson> registrationPersons,
List<EventRegistrationItem> allRegistrationItems,
List<long> eventRegistrationIds)
{
if (!eventRegistrationIds.Any())
{
return;
}
foreach (var item in input.Items)
{
if (!item.GroupId.HasValue) continue;
var eventProjectGroup = eventProjectGroups.FirstOrDefault(epg => epg.Id == item.GroupId.Value);
if (eventProjectGroup == null) continue;
var eventProject = eventProjects.FirstOrDefault(ep => ep.Id == eventProjectGroup.EventProjectId);
if (eventProject == null) continue;
var project = projects.FirstOrDefault(p => p.Id == eventProject.ProjectId);
var projectName = project?.Name ?? "未知项目";
var existingItems = allRegistrationItems.Where(ri => ri.EventProjectGroupId == item.GroupId.Value).ToList();
if (!existingItems.Any()) continue;
var existingItemIds = existingItems.Select(ri => ri.Id).ToList();
var existingPersons = registrationPersons.Where(rp => existingItemIds.Contains(rp.RegistrationItemId)).ToList();
var allPersonsInItem = GetAllPersonsFromItem(item);
foreach (var person in allPersonsInItem)
{
if (!person.UserId.HasValue) continue;
var existingPerson = existingPersons.FirstOrDefault(ep => ep.UserId == person.UserId.Value);
if (existingPerson == null) continue;
_logger.LogWarning("人员已报名该项目组别,人员: {PersonName}, 项目: {ProjectName}, 组别: {GroupName}",
person.Name, projectName, eventProjectGroup.GroupName);
result.HasLimit = true;
result.LimitDetails.Add(CreateLimitDetail(
0,
person.Name,
"PersonAlreadyRegistered",
$"人员已报名检测:人员\"{person.Name}\"已在项目\"{projectName}\"的组别\"{eventProjectGroup.GroupName}\"中报名过,不能重复报名,避免占用多个名额"));
}
}
}
/// <summary>
/// 检查项目完成人数
/// </summary>
private void CheckProjectCompletionAsync(
UnifiedRegistrationInput input,
RegistrationLimitCheckOutput result,
List<EventProject> eventProjects,
List<YD_Event.Core.Entities.Project> projects,
List<EventProjectGroup> eventProjectGroups)
{
foreach (var item in input.Items)
{
if (!item.GroupId.HasValue) continue;
// 获取组别信息
var eventProjectGroup = eventProjectGroups.FirstOrDefault(epg => epg.Id == item.GroupId.Value);
if (eventProjectGroup == null) continue;
// 获取赛事项目信息
var eventProject = eventProjects.FirstOrDefault(ep => ep.Id == eventProjectGroup.EventProjectId);
if (eventProject == null) continue;
// 获取基础项目信息
var project = projects.FirstOrDefault(p => p.Id == eventProject.ProjectId);
if (project == null) continue;
// 判断是否为团队项目
var isTeamProject = project.IsTeamProject();
// 计算分配的人数(去重)
var assignedPersons = GetAllPersonsFromItem(item);
var totalAssignedMembers = assignedPersons
.Where(p => p.UserId.HasValue)
.Select(p => p.UserId.Value)
.Distinct()
.Count();
// ==================== 项目组别条件判断逻辑 ====================
// 当且仅当某个项目组别中已选择至少一名人员时,才对该项目组别执行后续条件判断流程
if (totalAssignedMembers == 0)
{
// 若项目组别未选择任何人员,直接忽略该项目组别,不进行任何条件判断
continue;
}
// ==================== 报名限制验证规则 ====================
// 个人项目不进行完成人数限制,仅适用项目报名限制规则
if (!isTeamProject)
{
continue;
}
// ==================== 项目完成人数验证 ====================
// 只对团队项目进行完成人数验证
if (project.CompletionCount > 0 && totalAssignedMembers != project.CompletionCount)
{
string errorMessage;
if (totalAssignedMembers < project.CompletionCount)
{
errorMessage = $"{project.Name}参赛人数错误(少{project.CompletionCount - totalAssignedMembers}人)";
}
else
{
errorMessage = $"{project.Name}参赛人数错误(多{totalAssignedMembers - project.CompletionCount}人)";
}
result.HasLimit = true;
result.LimitDetails.Add(CreateLimitDetail(
0,
eventProjectGroup.GroupName ?? "未知组别",
"ProjectCompletion",
errorMessage));
}
}
}
/// <summary>
/// 获取组别名称
/// </summary>
private static string GetGroupName(UnifiedRegistrationItemInput item, List<EventProjectGroup> eventProjectGroups)
{
if (!string.IsNullOrEmpty(item.GroupName))
{
return item.GroupName;
}
if (item.GroupId.HasValue)
{
var selectedGroup = eventProjectGroups.FirstOrDefault(eg => eg.Id == item.GroupId.Value);
if (selectedGroup != null)
{
return selectedGroup.GroupName ?? "";
}
}
var firstGroup = eventProjectGroups.FirstOrDefault(eg => eg.EventProjectId == item.ProjectId);
return firstGroup?.GroupName ?? "";
}
/// <summary>
/// 计算单个项目费用
/// </summary>
private (decimal unitPrice, decimal totalPrice) CalculateItemPrice(EventFeeConfig? feeConfig, EventProject eventProject, int personCount)
{
if (feeConfig == null)
{
return (0, 0);
}
decimal unitPrice = feeConfig.ChargeMode switch
{
ChargeMode.Free => 0,
ChargeMode.FixedPrice or ChargeMode.UnitPriceEvent => feeConfig.Price,
ChargeMode.UnitPriceProject => eventProject.Price,
_ => 0
};
var totalPrice = unitPrice * personCount;
return (unitPrice, totalPrice);
}
/// <summary>
/// 计算总费用
/// </summary>
private decimal CalculateTotalAmount(EventFeeConfig? feeConfig, List<EventProject> eventProjects,
List<UnifiedRegistrationItemInput> items, int totalPersonCount)
{
if (feeConfig == null)
{
return 0;
}
var pricingInput = new PricingInput
{
ChargeMode = feeConfig.ChargeMode,
ParticipantCount = totalPersonCount,
EventPrice = feeConfig.Price
};
var eventProjectsDict = eventProjects.ToDictionary(p => p.Id);
foreach (var item in items)
{
if (eventProjectsDict.TryGetValue(item.ProjectId, out var ep))
{
pricingInput.Projects.Add(new ProjectPricingInfo
{
ProjectId = ep.Id,
UnitPrice = ep.Price
});
}
}
var pricingResult = EventPricingService.Calculate(pricingInput);
return pricingResult.TotalAmount;
}
/// <summary>
/// 计算报名金额(对外公开方法)
/// </summary>
/// <param name="eventId">赛事ID</param>
/// <param name="items">报名项目列表(包含人员数量信息)</param>
/// <returns>计算结果</returns>
public async Task<(decimal totalAmount, ChargeMode chargeMode, List<(long projectId, string projectName, decimal unitPrice, int personCount, decimal subtotal)> itemDetails)> CalculateRegistrationAmountAsync(
long eventId, List<CalculateAmountInputItem> items)
{
var evt = await _eventRepository.GetFirstAsync(e => e.Id == eventId && !e.DeleteMark);
if (evt == null) throw Oops.Bah("赛事不存在");
var result = new List<(long projectId, string projectName, decimal unitPrice, int personCount, decimal subtotal)>();
if (evt.ChargeMode == ChargeMode.Free)
{
return (0, ChargeMode.Free, result);
}
var feeConfig = await _feeConfigRepository.GetFirstAsync(f => f.EventId == eventId && !f.DeleteMark);
var eventProjectIds = items.Select(i => i.ProjectId).Distinct().ToList();
var eventProjects = await _eventProjectRepository.GetListAsync(x => eventProjectIds.Contains(x.Id));
var projectIds = eventProjects.Select(ep => ep.ProjectId).Distinct().ToList();
var baseProjects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id));
int totalParticipantCount = items.Sum(i => i.PersonCount);
var unifiedItems = items.Select(i => new UnifiedRegistrationItemInput
{
ProjectId = i.ProjectId,
Persons = new List<UnifiedPersonInput>(new UnifiedPersonInput[i.PersonCount])
}).ToList();
var totalAmount = CalculateTotalAmount(feeConfig, eventProjects, unifiedItems, totalParticipantCount);
var eventProjectsDict = eventProjects.ToDictionary(p => p.Id);
var baseProjectsDict = baseProjects.ToDictionary(p => p.Id);
foreach (var item in items)
{
if (!eventProjectsDict.TryGetValue(item.ProjectId, out var ep)) continue;
var projectName = baseProjectsDict.TryGetValue(ep.ProjectId, out var bp) ? bp.Name ?? "" : "";
var (unitPrice, subtotal) = CalculateItemPrice(feeConfig, ep, item.PersonCount);
result.Add((item.ProjectId, projectName, unitPrice, item.PersonCount, subtotal));
}
return (totalAmount, evt.ChargeMode, result);
}
/// <summary>
/// 计算金额输入项(简化版)
/// </summary>
public class CalculateAmountInputItem
{
public long ProjectId { get; set; }
public int PersonCount { get; set; }
}
/// <summary>
/// 根据出生日期计算年龄
/// </summary>
private static int CalculateAge(DateTime birthDate)
{
var today = DateTime.Today;
var age = today.Year - birthDate.Year;
if (birthDate.Date > today.AddYears(-age)) age--;
return age;
}
/// <summary>
/// 处理支付成功
/// </summary>
public async Task HandlePaymentSuccessAsync(string orderNo, string transactionId, DateTime? successTime, long operatorUserId)
{
var reg = await _registrationRepository.GetFirstAsync(r => r.OrderNo == orderNo);
if (reg == null)
{
_logger.LogWarning($"未找到订单:{orderNo}");
return;
}
if (reg.PaymentStatus == 1)
{
_logger.LogInformation($"订单 {reg.OrderNo} 已处理过支付");
return;
}
var evt = await _eventRepository.GetFirstAsync(e => e.Id == reg.EventId);
if (evt == null)
{
_logger.LogError($"未找到赛事信息,订单号:{reg.OrderNo},赛事ID:{reg.EventId}");
return;
}
_logger.LogInformation("订单支付成功,订单号={OrderNo},赛事ID={EventId},商户ID={MerchantId}",
reg.OrderNo, evt.Id, evt.MerchantId);
reg.PaymentStatus = 1;
reg.PaymentTime = successTime;
reg.RegistrationRemark = transactionId;
reg.UpdatedTime = DateTime.Now;
reg.UpdatedUserId = operatorUserId;
await _registrationRepository.UpdateAsync(reg);
_logger.LogInformation($"订单 {reg.OrderNo} 支付成功,微信交易号:{transactionId}");
var payment = new EventRegistrationPayment
{
Id = _idGenerator.NewLong(),
RegistrationId = reg.Id,
TransactionNo = transactionId ?? "",
PaymentChannel = 1,
PaymentAmount = reg.TotalAmount,
PaymentTime = successTime ?? DateTime.Now,
PaymentStatus = 1,
RefundStatus = 0,
PayeeAccount = "",
CreatedUserId = reg.CreatedUserId,
CreatedTime = DateTime.Now
};
await _paymentRepository.InsertAsync(payment);
_logger.LogInformation($"支付记录创建成功,报名ID={reg.Id},交易号={transactionId}");
var order = await _orderRepository.GetFirstAsync(o => o.OrderNo == reg.OrderNo);
if (order != null)
{
order.OrderStatus = OrderStatus.Paid;
order.PaymentTime = successTime;
order.UpdatedTime = DateTime.Now;
await _orderRepository.UpdateAsync(order);
_logger.LogInformation($"Order 记录 {order.OrderNo} 状态已更新为已支付");
// 支付成功后,标记优惠券为已使用
if (order.CouponId.HasValue)
{
var userCoupon = await _userCouponRepository.GetFirstAsync(uc =>
uc.CouponId == order.CouponId.Value &&
uc.UserId == order.CreatedUserId &&
uc.Status == 0 &&
!uc.DeleteMark);
if (userCoupon != null)
{
userCoupon.Status = 1;
userCoupon.UseTime = successTime ?? DateTime.Now;
userCoupon.OrderId = order.Id;
await _userCouponRepository.UpdateAsync(userCoupon);
// 更新优惠券的已使用数量
var coupon = await _couponRepository.GetByIdAsync(order.CouponId.Value);
if (coupon != null)
{
coupon.UsedQuantity++;
await _couponRepository.UpdateAsync(coupon);
}
_logger.LogInformation($"优惠券已标记为已使用,用户优惠券ID={userCoupon.Id},订单ID={order.Id}");
}
}
}
}
/// <summary>
/// 取消报名
/// </summary>
public async Task<bool> CancelRegistrationAsync(long registrationId, long operatorUserId)
{
var reg = await _registrationRepository.GetFirstAsync(r => r.Id == registrationId && r.CreatedUserId == operatorUserId);
if (reg == null) throw Oops.Bah("报名记录不存在");
if (reg.PaymentStatus == 1) throw Oops.Bah("已支付订单请联系客服退款");
if (reg.PaymentStatus == 4) throw Oops.Bah("订单已取消,无需重复操作");
_logger.LogInformation("开始取消报名:报名ID={RegistrationId}, 操作人={UserId}, 当前状态={PaymentStatus}",
registrationId, operatorUserId, reg.PaymentStatus);
reg.PaymentStatus = 4;
reg.UpdatedTime = DateTime.Now;
reg.UpdatedUserId = operatorUserId;
await _registrationRepository.UpdateAsync(reg);
if (!string.IsNullOrEmpty(reg.OrderNo))
{
var order = await _orderRepository.GetFirstAsync(o => o.OrderNo == reg.OrderNo);
if (order != null)
{
order.OrderStatus = OrderStatus.Cancelled;
order.UpdatedTime = DateTime.Now;
order.UpdatedUserId = operatorUserId;
await _orderRepository.UpdateAsync(order);
_logger.LogInformation("订单状态已同步更新:订单号={OrderNo}, 状态={OrderStatus}",
reg.OrderNo, OrderStatus.Cancelled);
}
else
{
_logger.LogWarning("未找到对应的订单记录:报名ID={RegistrationId}, 订单号={OrderNo}",
registrationId, reg.OrderNo);
}
}
_logger.LogInformation("取消报名成功:报名ID={RegistrationId}", registrationId);
return true;
}
/// <summary>
/// 查询并同步微信支付状态
/// </summary>
public async Task<bool> QueryAndSyncWeChatPaymentStatusAsync(long registrationId, long operatorUserId)
{
var reg = await _registrationRepository.GetFirstAsync(r => r.Id == registrationId && r.CreatedUserId == operatorUserId);
if (reg == null) throw Oops.Bah("报名记录不存在");
if (reg.PaymentStatus == 1) return true;
var evt = await _eventRepository.GetFirstAsync(e => e.Id == reg.EventId);
if (evt == null) throw Oops.Bah("赛事不存在");
WeChatPayConfig? payConfig = null;
if (evt.MerchantId.HasValue)
{
payConfig = await _weChatPayConfigRepository.GetFirstAsync(x =>
x.MerchantId == evt.MerchantId.Value &&
x.Status == AvailabilityStatus.Enable &&
!x.DeleteMark);
}
if (payConfig == null)
{
payConfig = await _weChatPayConfigRepository.GetFirstAsync(x =>
x.Status == AvailabilityStatus.Enable && !x.DeleteMark);
}
if (payConfig == null)
{
_logger.LogError("未找到可用的微信支付配置,赛事ID={EventId},商户ID={MerchantId}", evt.Id, evt.MerchantId);
throw Oops.Bah("未找到可用的微信支付配置,请联系赛事管理员");
}
var queryResult = await _weChatPayService.QueryOrderAsync(payConfig, reg.OrderNo);
if (queryResult.Success && queryResult.TradeState == "SUCCESS")
{
await HandlePaymentSuccessAsync(reg.OrderNo, queryResult.TransactionId, queryResult.SuccessTime?.DateTime, operatorUserId);
return true;
}
return false;
}
/// <summary>
/// 获取平台级别的微信支付配置
/// </summary>
public async Task<WeChatPayConfig?> GetPlatformWeChatPayConfigAsync()
{
return await _weChatPayConfigRepository.GetFirstAsync(x =>
x.Status == AvailabilityStatus.Enable && !x.DeleteMark);
}
/// <summary>
/// 根据赛事获取对应的微信支付配置
/// </summary>
public async Task<WeChatPayConfig?> GetWeChatPayConfigByEventAsync(EventInfo evt)
{
WeChatPayConfig? payConfig = null;
if (evt.MerchantId.HasValue)
{
payConfig = await _weChatPayConfigRepository.GetFirstAsync(x =>
x.MerchantId == evt.MerchantId.Value &&
x.Status == AvailabilityStatus.Enable &&
!x.DeleteMark);
}
if (payConfig == null)
{
payConfig = await GetPlatformWeChatPayConfigAsync();
}
return payConfig;
}
/// <summary>
/// 统一优惠券验证和使用方法
/// </summary>
[UnitOfWork]
public async Task<ValidateCouponOutput> ValidateAndUseCouponAsync(ValidateCouponInput input, long userId)
{
var result = new ValidateCouponOutput
{
IsValid = false,
InvalidReason = ""
};
// 查找兑换码
var redemptionCode = await _redemptionCodeRepository.GetFirstAsync(rc =>
rc.Code == input.CouponCode &&
rc.Status == 0 &&
!rc.DeleteMark);
if (redemptionCode == null)
{
result.InvalidReason = "兑换码不存在或已使用";
return result;
}
// 查找优惠券
var coupon = await _couponRepository.GetFirstAsync(c =>
c.Id == redemptionCode.CouponId &&
c.Status == AvailabilityStatus.Enable &&
!c.DeleteMark);
if (coupon == null)
{
result.InvalidReason = "优惠券不存在或已失效";
return result;
}
// 检查优惠券是否已过期
if (coupon.EndTime.HasValue && coupon.EndTime.Value < DateTime.Now)
{
result.InvalidReason = "优惠券已过期";
return result;
}
// 检查优惠券是否已开始
if (coupon.StartTime.HasValue && coupon.StartTime.Value > DateTime.Now)
{
result.InvalidReason = "优惠券尚未生效";
return result;
}
// 检查库存
if (coupon.TotalQuantity > 0 && coupon.ClaimedQuantity >= coupon.TotalQuantity)
{
result.InvalidReason = "优惠券已被领完";
return result;
}
// 检查优惠券是否适用于该赛事
if (!string.IsNullOrEmpty(coupon.EventIds))
{
var eventIds = coupon.EventIds.Split(',').Select(long.Parse).ToList();
if (!eventIds.Contains(input.EventId))
{
result.InvalidReason = "该优惠券不适用于当前赛事";
return result;
}
}
// 检查当前用户是否拥有该优惠券且未使用,或者可以直接领取
var userCoupon = await _userCouponRepository.GetFirstAsync(uc =>
uc.CouponId == coupon.Id &&
uc.UserId == userId &&
uc.Status == 0 &&
!uc.DeleteMark);
// 如果用户没有该优惠券,检查是否可以直接领取(假设可以直接通过兑换码领取)
if (userCoupon == null)
{
// 创建用户优惠券记录
userCoupon = new YD_Event.Core.Entities.UserCoupon
{
Id = _idGenerator.NewLong(),
UserId = userId,
CouponId = coupon.Id,
ClaimTime = DateTime.Now,
Status = 0,
RedemptionCodeId = redemptionCode.Id,
RedemptionCode = redemptionCode.Code,
CreatedUserId = userId,
CreatedTime = DateTime.Now,
DeleteMark = false
};
await _userCouponRepository.InsertAsync(userCoupon);
// 更新兑换码状态和关联
redemptionCode.Status = 1;
redemptionCode.UsedTime = DateTime.Now;
redemptionCode.UserCouponId = userCoupon.Id;
await _redemptionCodeRepository.UpdateAsync(redemptionCode);
// 更新优惠券已领取数量
coupon.ClaimedQuantity += 1;
await _couponRepository.UpdateAsync(coupon);
}
// 计算优惠金额
var (discountAmount, finalAmount) = CalculateCouponDiscount(coupon, input.OriginalAmount);
result.IsValid = true;
result.CouponId = coupon.Id;
result.CouponName = coupon.Name ?? "优惠券";
result.DiscountType = coupon.Type;
result.DiscountValue = coupon.Value;
result.DiscountAmount = discountAmount;
result.FinalAmount = finalAmount;
return result;
}
/// <summary>
/// 验证报名相关配置参数
/// </summary>
private async Task<RegistrationValidationResult> ValidateRegistrationConfigAsync(EventInfo eventInfo)
{
var result = new RegistrationValidationResult
{
IsValid = true,
Details = new List<ValidationDetail>()
};
// 1. 验证费用配置是否存在(除非是免费赛事)
if (eventInfo.ChargeMode != ChargeMode.Free)
{
var feeConfig = await _feeConfigRepository.GetFirstAsync(x => x.EventId == eventInfo.Id && !x.DeleteMark);
if (feeConfig == null)
{
result.IsValid = false;
result.ErrorMessage = "赛事费用配置不存在,请联系管理员";
result.Details.Add(CreateValidationDetail("费用配置验证", false, "赛事费用配置不存在"));
return result;
}
// 验证费用配置值是否有效
if (eventInfo.ChargeMode == ChargeMode.FixedPrice && feeConfig.Price <= 0)
{
result.IsValid = false;
result.ErrorMessage = "赛事费用配置不正确,请联系管理员";
result.Details.Add(CreateValidationDetail("费用配置验证", false, "定价模式费用配置不正确"));
return result;
}
result.Details.Add(CreateValidationDetail("费用配置验证", true, "费用配置验证通过"));
}
else
{
result.Details.Add(CreateValidationDetail("费用配置验证", true, "免费赛事,无需费用配置"));
}
// 2. 验证赛事项目是否存在
var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == eventInfo.Id);
if (!eventProjects.Any())
{
result.IsValid = false;
result.ErrorMessage = "赛事未配置任何项目,请联系管理员";
result.Details.Add(CreateValidationDetail("项目配置验证", false, "赛事项目配置不存在"));
return result;
}
result.Details.Add(CreateValidationDetail("项目配置验证", true, $"赛事配置了{eventProjects.Count}个项目"));
// 3. 验证商户配置是否存在(如果需要支付)
if (eventInfo.ChargeMode != ChargeMode.Free)
{
if (eventInfo.MerchantId.HasValue)
{
var merchant = await _merchantRepository.GetFirstAsync(x => x.Id == eventInfo.MerchantId.Value && !x.DeleteMark);
if (merchant == null)
{
result.IsValid = false;
result.ErrorMessage = "赛事关联的商户不存在,请联系管理员";
result.Details.Add(CreateValidationDetail("商户配置验证", false, "赛事关联的商户不存在"));
return result;
}
result.Details.Add(CreateValidationDetail("商户配置验证", true, "商户配置验证通过"));
}
else
{
// 如果赛事没有关联商户,检查是否有平台级别的支付配置
var platformPayConfig = await _weChatPayConfigRepository.GetFirstAsync(x => x.Status == AvailabilityStatus.Enable && !x.DeleteMark);
if (platformPayConfig == null)
{
result.IsValid = false;
result.ErrorMessage = "支付配置不存在,请联系管理员";
result.Details.Add(CreateValidationDetail("支付配置验证", false, "平台支付配置不存在"));
return result;
}
result.Details.Add(CreateValidationDetail("支付配置验证", true, "平台支付配置验证通过"));
}
}
result.Details.Add(CreateValidationDetail("配置验证", true, "所有报名相关配置验证通过"));
return result;
}
/// <summary>
/// 验证用户单位认证状态
/// </summary>
private async Task<RegistrationValidationResult> ValidateUserCertificationAsync(long userId)
{
var result = new RegistrationValidationResult
{
IsValid = true,
Details = new List<ValidationDetail>()
};
// 验证用户是否通过了单位认证
var certification = await _userRepository.GetFirstAsync(c =>
c.Id == userId &&
c.AuditStatus == AuditStatus.Pass &&
!c.DeleteMark);
if (certification == null)
{
result.IsValid = false;
result.ErrorMessage = "您尚未通过单位认证,请先完成单位认证";
result.Details.Add(CreateValidationDetail("单位认证验证", false, "用户未通过单位认证"));
return result;
}
result.Details.Add(CreateValidationDetail("单位认证验证", true, "用户单位认证验证通过"));
return result;
}
#region 统一退款相关方法
/// <summary>
/// 计算退款金额(统一方法)
/// </summary>
/// <param name="totalAmount">总金额</param>
/// <param name="eventStartTime">赛事开始时间</param>
/// <returns>应退金额</returns>
[NonAction]
public async Task<decimal> CalculateRefundAmountAsync(decimal totalAmount, DateTime eventStartTime)
{
var refundAmount = await _refundConfigService.CalculateRefundAmount(totalAmount, eventStartTime);
_logger.LogInformation("统一退款金额计算:总金额={TotalAmount}, 赛事开始时间={EventStartTime}, 计算后退款金额={RefundAmount}",
totalAmount, eventStartTime, refundAmount);
return refundAmount;
}
/// <summary>
/// 统一取消报名预览方法
/// </summary>
/// <param name="registrationId">报名ID</param>
/// <param name="operatorUserId">操作人用户ID</param>
/// <returns>取消预览输出</returns>
[NonAction]
public async Task<CancelPreviewOutput> GetCancelPreviewAsync(long registrationId, long operatorUserId)
{
var registration = await _registrationRepository.GetFirstAsync(x => x.Id == registrationId && x.CreatedUserId == operatorUserId);
if (registration == null) throw Oops.Bah("报名记录不存在");
var eventInfo = await _db.Queryable<EventInfo>().InSingleAsync(registration.EventId);
var items = await _db.Queryable<EventRegistrationItem>().Where(x => x.RegistrationId == registrationId).ToListAsync();
var persons = await _registrationPersonRepository.GetListAsync(x => x.RegistrationId == registrationId);
var refundableAmount = await CalculateRefundAmountAsync(registration.TotalAmount, eventInfo.MatchStartTime);
_logger.LogInformation("统一取消报名预览:报名ID={RegistrationId}, 赛事名称={EventName}, 实际支付={ActualAmount}, 可退款={RefundAmount}",
registrationId, eventInfo?.EventName, registration.TotalAmount, refundableAmount);
var output = new CancelPreviewOutput
{
RegistrationId = registration.Id,
EventName = eventInfo?.EventName ?? "",
TeamName = registration.TeamName ?? "",
ChargeMode = registration.TotalAmount > 0 ? 1 : 0,
ActualPaidAmount = registration.TotalAmount,
RefundableAmount = refundableAmount
};
foreach (var item in items)
{
output.Projects.Add(new CancelPreviewProjectDto
{
ProjectId = item.ProjectId,
ProjectName = item.ProjectName ?? "",
ProjectType = item.ProjectTypeName.Contains("团队")? "团队" : (item.ProjectTypeName.Contains("个人") ? "个人" : string.Empty),
PersonCount = item.PersonCount,
UnitPrice = item.UnitPrice
});
}
foreach (var person in persons)
{
output.Members.Add(new CancelPreviewMemberDto
{
MemberId = person.Id,
Name = person.Name ?? "",
IsLeader = person.PersonType == PersonType.Leader
});
}
return output;
}
/// <summary>
/// 统一取消报名执行方法
/// </summary>
/// <param name="input">取消执行输入</param>
/// <param name="operatorUserId">操作人用户ID</param>
/// <returns>取消执行输出</returns>
[UnitOfWork]
[NonAction]
public async Task<CancelExecuteOutput> ExecuteCancelRegistrationAsync(CancelExecuteInput input, long operatorUserId)
{
var registration = await _registrationRepository.GetFirstAsync(x => x.Id == input.RegistrationId && x.CreatedUserId == operatorUserId);
if (registration == null) throw Oops.Bah("报名记录不存在");
if (registration.PaymentStatus == 4) throw Oops.Bah("订单已取消");
if (registration.PaymentStatus != 1) throw Oops.Bah("未支付的订单无法退款");
var now = DateTime.Now;
var eventInfo = await _eventRepository.GetFirstAsync(e => e.Id == registration.EventId);
var baseRefundAmount = await CalculateRefundAmountAsync(registration.TotalAmount, eventInfo.MatchStartTime);
_logger.LogInformation("统一退款金额计算:报名ID={RegistrationId}, 总金额={TotalAmount}, 按规则计算后金额={BaseRefundAmount}",
registration.Id, registration.TotalAmount, baseRefundAmount);
decimal actualRefundAmount = baseRefundAmount;
bool isPartialCancel = input.CancelType == 1 && input.PersonIds != null && input.PersonIds.Any();
if (isPartialCancel)
{
var persons = await _registrationPersonRepository.GetListAsync(x => x.RegistrationId == registration.Id);
var totalPersons = persons.Count;
if (totalPersons == 0)
{
throw Oops.Bah("报名记录中没有队员信息");
}
var selectedPersons = persons.Where(p => input.PersonIds.Contains(p.Id)).ToList();
if (selectedPersons.Count != input.PersonIds.Count)
{
var invalidIds = input.PersonIds.Except(selectedPersons.Select(p => p.Id)).ToList();
throw Oops.Bah($"选择的人员ID无效:{string.Join(", ", invalidIds)}");
}
var perPersonRefund = baseRefundAmount / totalPersons;
actualRefundAmount = perPersonRefund * selectedPersons.Count;
_logger.LogInformation("统一部分退款:报名ID={RegistrationId}, 总人数={TotalPersons}, 选择人数={SelectedCount}, 单人退款金额={PerPersonRefund}, 实际退款金额={ActualRefundAmount}",
registration.Id, totalPersons, selectedPersons.Count, perPersonRefund, actualRefundAmount);
await _registrationPersonRepository.DeleteAsync(x => input.PersonIds.Contains(x.Id));
_logger.LogInformation("已删除队员:报名ID={RegistrationId}, 队员ID列表={PersonIds}",
registration.Id, string.Join(", ", input.PersonIds));
var remainingPersonsCount = await _registrationPersonRepository.CountAsync(x => x.RegistrationId == registration.Id);
registration.TotalPersonCount = remainingPersonsCount;
if (remainingPersonsCount > 0)
{
registration.UpdatedUserId = operatorUserId;
registration.UpdatedTime = now;
registration.RegistrationRemark = input.CancelReason;
await _registrationRepository.UpdateAsync(registration);
_logger.LogInformation("统一部分退款完成,仍有剩余人员:报名ID={RegistrationId}, 剩余人数={RemainingCount}",
registration.Id, remainingPersonsCount);
}
else
{
registration.PaymentStatus = 3;
registration.UpdatedUserId = operatorUserId;
registration.UpdatedTime = now;
registration.RegistrationRemark = input.CancelReason;
await _registrationRepository.UpdateAsync(registration);
_logger.LogInformation("统一所有人员已退款,更新报名状态为已退款:报名ID={RegistrationId}", registration.Id);
}
}
else
{
_logger.LogInformation("统一全额退款:报名ID={RegistrationId}, 退款金额={ActualRefundAmount}",
registration.Id, actualRefundAmount);
registration.PaymentStatus = 3;
registration.UpdatedUserId = operatorUserId;
registration.UpdatedTime = now;
registration.RegistrationRemark = input.CancelReason;
await _registrationRepository.UpdateAsync(registration);
}
var output = new CancelExecuteOutput
{
Success = true,
Message = isPartialCancel ? "部分取消报名成功" : "取消报名成功",
CancelTime = now,
RefundAmount = actualRefundAmount
};
if (registration.TotalAmount > 0 && !string.IsNullOrEmpty(registration.OrderNo) && actualRefundAmount > 0)
{
var merchant = await _merchantRepository.GetFirstAsync(x => x.Id == eventInfo.MerchantId);
if (merchant != null)
{
var wechatConfig = await _weChatPayConfigRepository.GetFirstAsync(x => x.MerchantId == merchant.Id);
if (wechatConfig != null)
{
var outRefundNo = await _orderNoGeneratorService.GenerateRefundOrderNoAsync();
output.RefundNo = outRefundNo;
var refundRequest = new Core.Infrastructure.WeChat.Dto.CreateRefundRequest
{
OutTradeNo = registration.OrderNo,
OutRefundNo = outRefundNo,
RefundAmount = AmountHelper.YuanToFen(actualRefundAmount),
TotalAmount = AmountHelper.YuanToFen(registration.TotalAmount),
Reason = input.CancelReason
};
_logger.LogInformation("开始执行统一退款:报名ID={RegistrationId}, 订单号={OrderNo}, 退款单号={RefundNo}, 退款金额={Amount}, 退款类型={CancelType}",
registration.Id, registration.OrderNo, outRefundNo, actualRefundAmount, isPartialCancel ? "部分退款" : "全额退款");
var refundResult = await _weChatPayService.CreateRefundAsync(wechatConfig, refundRequest);
if (!refundResult.Success)
{
_logger.LogError("统一退款失败:报名ID={RegistrationId}, 错误信息={ErrorMessage}",
registration.Id, refundResult.Message);
output.Success = false;
output.Message = refundResult.Message;
output.RefundAmount = 0;
output.RefundNo = null;
return output;
}
await CreateRefundRecordAsync(registration, eventInfo, wechatConfig, outRefundNo,
refundResult.RefundId, actualRefundAmount, registration.TotalAmount, input.CancelReason, operatorUserId);
_logger.LogInformation("统一退款成功:报名ID={RegistrationId}, 退款单号={RefundNo}, 退款金额={Amount}",
registration.Id, outRefundNo, actualRefundAmount);
}
}
}
if (!isPartialCancel || registration.PaymentStatus == 3)
{
await UpdateOrderStatusAsync(registration.OrderNo, OrderStatus.Refunded, operatorUserId);
}
_logger.LogInformation("统一取消报名完成:报名ID={RegistrationId}, 操作人={UserId}, 退款金额={RefundAmount}, 成功={Success}",
registration.Id, operatorUserId, actualRefundAmount, output.Success);
return output;
}
/// <summary>
/// 创建退款记录
/// </summary>
private async Task CreateRefundRecordAsync(EventRegistration registration, EventInfo eventInfo,
WeChatPayConfig wechatConfig, string outRefundNo, string? refundId,
decimal refundAmount, decimal totalAmount, string? reason, long operatorUserId)
{
var refundRecord = new RefundRecord
{
Id = _idGenerator.NewLong(),
OutRefundNo = outRefundNo,
RefundId = refundId,
RegistrationId = registration.Id,
OutTradeNo = registration.OrderNo,
TransactionId = registration.PaymentProofUrl,
RefundAmount = refundAmount,
TotalAmount = totalAmount,
RefundReason = reason,
RefundStatus = 1,
ApplyTime = DateTime.Now,
SuccessTime = DateTime.Now,
MerchantId = eventInfo.MerchantId,
WeChatPayConfigId = wechatConfig.Id,
Status = AvailabilityStatus.Enable,
CreatedUserId = operatorUserId,
CreatedTime = DateTime.Now
};
await _refundRecordRepository.InsertAsync(refundRecord);
_logger.LogInformation("退款记录创建成功:退款记录ID={RefundRecordId}, 退款单号={OutRefundNo}",
refundRecord.Id, outRefundNo);
}
/// <summary>
/// 更新订单状态
/// </summary>
private async Task UpdateOrderStatusAsync(string orderNo, OrderStatus orderStatus, long operatorUserId)
{
if (!string.IsNullOrEmpty(orderNo))
{
var order = await _orderRepository.GetFirstAsync(o => o.OrderNo == orderNo);
if (order != null)
{
order.OrderStatus = orderStatus;
order.UpdatedTime = DateTime.Now;
order.UpdatedUserId = operatorUserId;
await _orderRepository.UpdateAsync(order);
_logger.LogInformation("订单状态已同步更新:订单号={OrderNo}, 状态={OrderStatus}",
orderNo, orderStatus);
}
else
{
_logger.LogWarning("未找到对应的订单记录:订单号={OrderNo}", orderNo);
}
}
}
/// <summary>
/// 统一后台管理退款方法
/// </summary>
/// <param name="input">退款订单输入</param>
/// <param name="operatorUserId">操作人用户ID</param>
[UnitOfWork]
[NonAction]
public async Task ExecuteAdminRefundAsync(RefundOrderInput input, long operatorUserId)
{
var order = await _orderRepository.GetByIdAsync(input.Id);
if (order == null) throw Oops.Bah("订单不存在");
if (order.OrderStatus != OrderStatus.Paid) throw Oops.Bah("订单未支付,无法退款");
if (!AmountHelper.IsValidPositiveAmount(input.RefundAmount))
{
throw Oops.Bah("退款金额必须大于0且最多保留两位小数");
}
if (input.RefundAmount > order.ActualAmount)
{
_logger.LogWarning("统一后台超额退款尝试:订单ID={OrderId}, 实付金额={ActualAmount}, 申请退款金额={RefundAmount}",
order.Id, order.ActualAmount, input.RefundAmount);
throw Oops.Bah($"退款金额({input.RefundAmount})不能超过实付金额({order.ActualAmount})");
}
var currentUserId = operatorUserId;
var now = DateTime.Now;
bool isPartialCancel = input.CancelType == 1 && input.PersonIds != null && input.PersonIds.Any();
if (isPartialCancel)
{
var persons = await _registrationPersonRepository.GetListAsync(x => x.RegistrationId == order.RegistrationId);
var totalPersons = persons.Count;
if (totalPersons == 0)
{
throw Oops.Bah("报名记录中没有队员信息");
}
var selectedPersons = persons.Where(p => input.PersonIds.Contains(p.Id)).ToList();
if (selectedPersons.Count != input.PersonIds.Count)
{
var invalidIds = input.PersonIds.Except(selectedPersons.Select(p => p.Id)).ToList();
throw Oops.Bah($"选择的人员ID无效:{string.Join(", ", invalidIds)}");
}
_logger.LogInformation("统一后台部分退款:订单ID={OrderId}, 总人数={TotalPersons}, 选择人数={SelectedCount}, 退款金额={RefundAmount}",
order.Id, totalPersons, selectedPersons.Count, input.RefundAmount);
await _registrationPersonRepository.DeleteAsync(x => input.PersonIds.Contains(x.Id));
_logger.LogInformation("已删除队员:订单ID={OrderId}, 队员ID列表={PersonIds}",
order.Id, string.Join(", ", input.PersonIds));
var remainingPersonsCount = await _registrationPersonRepository.CountAsync(x => x.RegistrationId == order.RegistrationId);
var reg = await _registrationRepository.GetByIdAsync(order.RegistrationId);
if (reg != null)
{
reg.TotalPersonCount = remainingPersonsCount;
reg.UpdatedTime = now;
reg.UpdatedUserId = currentUserId;
await _registrationRepository.UpdateAsync(reg);
}
if (remainingPersonsCount > 0)
{
var refundOrder = new YD_Event.Core.Entities.Order
{
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 = input.RefundAmount,
OrderStatus = OrderStatus.Refunded,
PaymentTime = now,
Remark = input.Reason,
CreatedTime = now,
CreatedUserId = currentUserId
};
await _orderRepository.InsertAsync(refundOrder);
_logger.LogInformation("统一后台部分退款成功:订单ID={OrderId}, 退款订单号={RefundOrderNo}, 退款金额={RefundAmount}, 剩余人数={RemainingCount}, 操作人={UserId}",
order.Id, refundOrder.OrderNo, input.RefundAmount, remainingPersonsCount, currentUserId);
return;
}
}
if (!isPartialCancel)
{
var existingRefund = await _orderRepository.IsAnyAsync(o => o.RelatedOrderId == order.Id && o.OrderType == 2);
if (existingRefund)
{
throw Oops.Bah("该订单已存在退款记录");
}
}
var fullRefundOrder = new YD_Event.Core.Entities.Order
{
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 = input.RefundAmount,
OrderStatus = OrderStatus.Refunded,
PaymentTime = now,
Remark = input.Reason,
CreatedTime = now,
CreatedUserId = currentUserId
};
await _orderRepository.InsertAsync(fullRefundOrder);
_logger.LogInformation("统一后台退款成功:订单ID={OrderId}, 退款订单号={RefundOrderNo}, 退款金额={RefundAmount}, 退款类型={RefundType}, 操作人={UserId}",
order.Id, fullRefundOrder.OrderNo, input.RefundAmount, isPartialCancel ? "部分退款" : "全额退款", currentUserId);
order.OrderStatus = OrderStatus.Refunded;
order.UpdatedTime = now;
order.UpdatedUserId = currentUserId;
await _orderRepository.UpdateAsync(order);
var registration = await _registrationRepository.GetByIdAsync(order.RegistrationId);
if (registration != null)
{
registration.PaymentStatus = 3;
registration.UpdatedTime = now;
registration.UpdatedUserId = currentUserId;
await _registrationRepository.UpdateAsync(registration);
}
var payment = await _paymentRepository.GetFirstAsync(p => p.RegistrationId == order.RegistrationId);
if (payment != null)
{
payment.RefundStatus = 1;
payment.UpdatedTime = now;
payment.UpdatedUserId = currentUserId;
await _paymentRepository.UpdateAsync(payment);
}
}
#endregion
}