赛事支付模块优化
This commit is contained in:
parent
81cdb951c1
commit
b0a8156739
@ -2,6 +2,8 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using YD_Event.Application.Client.Dtos;
|
||||
using YD_Event.Application.Event;
|
||||
using YD_Event.Application.Event.Dtos;
|
||||
using YD_Event.Core.Entities;
|
||||
using YD_Event.Core.Enum;
|
||||
using Easy.Core;
|
||||
@ -61,6 +63,7 @@ public class EventClientController : IDynamicApiController
|
||||
private readonly ILogger<EventClientController> _logger;
|
||||
private readonly OrderNoGeneratorService _orderNoGeneratorService;
|
||||
private readonly YD_Event.Application.Banner.BannerService _bannerService;
|
||||
private readonly UnifiedRegistrationService _unifiedRegistrationService;
|
||||
|
||||
public EventClientController(
|
||||
ISqlSugarRepository<EventInfo> eventRepository,
|
||||
@ -95,7 +98,8 @@ public class EventClientController : IDynamicApiController
|
||||
IOptionsMonitor<WechatTenpayClientOptions> wechatTenpayClientOptionsMonitor,
|
||||
ILogger<EventClientController> logger,
|
||||
OrderNoGeneratorService orderNoGeneratorService,
|
||||
YD_Event.Application.Banner.BannerService bannerService)
|
||||
YD_Event.Application.Banner.BannerService bannerService,
|
||||
UnifiedRegistrationService unifiedRegistrationService)
|
||||
{
|
||||
_eventRepository = eventRepository;
|
||||
_projectRepository = projectRepository;
|
||||
@ -130,6 +134,7 @@ public class EventClientController : IDynamicApiController
|
||||
_logger = logger;
|
||||
_orderNoGeneratorService = orderNoGeneratorService;
|
||||
_bannerService = bannerService;
|
||||
_unifiedRegistrationService = unifiedRegistrationService;
|
||||
}
|
||||
|
||||
private long GetCurrentUserId()
|
||||
@ -452,7 +457,7 @@ public class EventClientController : IDynamicApiController
|
||||
IndividualProjectLimit = limit?.IndividualProjectLimit ?? 0,
|
||||
TeamProjectLimit = limit?.TeamProjectLimit ?? 0,
|
||||
|
||||
Units = units.Select(u => new EventUnitDto
|
||||
Units = units.Select(u => new YD_Event.Application.Client.Dtos.EventUnitDto
|
||||
{
|
||||
UnitType = u.UnitType,
|
||||
UnitName = u.UnitName,
|
||||
@ -608,17 +613,10 @@ public class EventClientController : IDynamicApiController
|
||||
var userId = GetCurrentUserId();
|
||||
|
||||
// 0. 校验报名模式
|
||||
if (input.RegistrationMode == 2) // 假设2为团队
|
||||
if (input.RegistrationMode == 2)
|
||||
throw Oops.Bah("团队报名请前往网页端进行操作");
|
||||
|
||||
var evt = await _eventRepository.GetFirstAsync(e => e.Id == input.EventId);
|
||||
if (evt == null) throw Oops.Bah("赛事不存在");
|
||||
|
||||
// 1. 校验时间
|
||||
if (DateTime.Now < evt.RegistrationStartTime || DateTime.Now > evt.RegistrationEndTime)
|
||||
throw Oops.Bah("不在报名时间内");
|
||||
|
||||
// 2. 校验重复报名 - 如果已有未取消的报名记录,直接返回现有记录ID
|
||||
// 校验重复报名 - 如果已有未取消的报名记录,直接返回现有记录ID
|
||||
var existingRegistration = await _registrationRepository.GetFirstAsync(
|
||||
r => r.EventId == input.EventId && r.CreatedUserId == userId && r.PaymentStatus != 4); // 4=已取消
|
||||
|
||||
@ -628,12 +626,12 @@ public class EventClientController : IDynamicApiController
|
||||
return existingRegistration.Id;
|
||||
}
|
||||
|
||||
// 3. 获取当前用户信息(用于个人报名时自动填充)
|
||||
// 获取当前用户信息(用于个人报名时自动填充)
|
||||
var appUser = await _appUserRepository.GetFirstAsync(u => u.CreatedUserId == userId && !u.DeleteMark);
|
||||
if (appUser == null && input.RegistrationMode == 1)
|
||||
throw Oops.Bah("请先完成身份认证");
|
||||
|
||||
// 3.5 个人报名模式下自动填充联系人信息
|
||||
// 个人报名模式下自动填充联系人信息
|
||||
if (input.RegistrationMode == 1)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input.ContactName))
|
||||
@ -650,269 +648,61 @@ public class EventClientController : IDynamicApiController
|
||||
throw Oops.Bah("联系人电话不能为空,请先完善身份认证信息");
|
||||
}
|
||||
|
||||
// 4. 校验项目有效性 & 限制
|
||||
if (input.Items == null || !input.Items.Any()) throw Oops.Bah("请至少选择一个项目");
|
||||
|
||||
var limit = await _limitRepository.GetFirstAsync(l => l.EventId == input.EventId && !l.DeleteMark);
|
||||
if (limit != null)
|
||||
// 个人报名模式下自动填充参赛人员信息
|
||||
if (input.RegistrationMode == 1 && input.Items != null)
|
||||
{
|
||||
if (input.Items.Count > limit.IndividualProjectLimit)
|
||||
throw Oops.Bah($"每人最多可报{limit.IndividualProjectLimit}个项目");
|
||||
}
|
||||
|
||||
var projectLimits = await _projectLimitRepository.AsQueryable()
|
||||
.Where(pl => pl.EventId == input.EventId && !pl.DeleteMark)
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var item in input.Items)
|
||||
{
|
||||
var projectLimit = projectLimits.FirstOrDefault(pl => pl.ProjectId == item.ProjectId);
|
||||
if (projectLimit != null)
|
||||
foreach (var item in input.Items)
|
||||
{
|
||||
var currentCount = await _registrationItemRepository.AsQueryable()
|
||||
.InnerJoin<EventRegistration>((ri, r) => ri.RegistrationId == r.Id)
|
||||
.Where((ri, r) => ri.ProjectId == item.ProjectId && r.PaymentStatus != 4)
|
||||
.CountAsync();
|
||||
|
||||
if (currentCount >= projectLimit.LimitCount)
|
||||
if (item.Persons == null || !item.Persons.Any())
|
||||
{
|
||||
var eventProject = await _eventProjectRepository.GetFirstAsync(ep => ep.Id == item.ProjectId);
|
||||
var project = await _projectRepository.GetFirstAsync(p => p.Id == eventProject.ProjectId);
|
||||
throw Oops.Bah($"项目[{project?.Name}]已达到报名上限,无法继续报名");
|
||||
if (appUser == null)
|
||||
throw Oops.Bah("请先完成身份认证");
|
||||
|
||||
item.Persons = new List<PersonInput>
|
||||
{
|
||||
new PersonInput
|
||||
{
|
||||
Name = appUser.Name ?? "",
|
||||
Gender = appUser.Gender,
|
||||
IdCardNumber = appUser.IdCardNumber ?? "",
|
||||
BirthDate = appUser.BirthDate ?? DateTime.Now,
|
||||
Mobile = appUser.Mobile
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 个人报名模式下自动填充参赛人员信息
|
||||
if (input.RegistrationMode == 1 && (item.Persons == null || !item.Persons.Any()))
|
||||
{
|
||||
if (appUser == null)
|
||||
throw Oops.Bah("请先完成身份认证");
|
||||
|
||||
item.Persons = new List<PersonInput>
|
||||
{
|
||||
new PersonInput
|
||||
{
|
||||
Name = appUser.Name ?? "",
|
||||
Gender = appUser.Gender,
|
||||
IdCardNumber = appUser.IdCardNumber ?? "",
|
||||
BirthDate = appUser.BirthDate ?? DateTime.Now,
|
||||
Mobile = appUser.Mobile
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 最终验证参赛人员信息
|
||||
if (item.Persons == null || !item.Persons.Any())
|
||||
throw Oops.Bah($"项目未填写参赛人信息");
|
||||
}
|
||||
|
||||
// 5. 创建报名主表
|
||||
var registrationId = _idGenerator.NewLong();
|
||||
var registration = new EventRegistration
|
||||
|
||||
// 转换为统一输入模型
|
||||
var unifiedInput = new UnifiedRegistrationInput
|
||||
{
|
||||
Id = registrationId,
|
||||
EventId = input.EventId,
|
||||
RegistrationMode = input.RegistrationMode,
|
||||
TeamName = input.RegistrationMode == 2 ? input.TeamName : null,
|
||||
ContactName = input.ContactName,
|
||||
ContactPhone = input.ContactPhone,
|
||||
PaymentStatus = 0, // 未支付
|
||||
RegistrationTime = DateTime.Now,
|
||||
TeamName = input.TeamName,
|
||||
ContactName = input.ContactName ?? "",
|
||||
ContactPhone = input.ContactPhone ?? "",
|
||||
CreatedUserId = userId,
|
||||
CreatedTime = DateTime.Now,
|
||||
Status = AvailabilityStatus.Enable,
|
||||
TotalPersonCount = 0 // 稍后计算
|
||||
};
|
||||
|
||||
// 5. 获取费用配置并使用统一的定价服务计算费用
|
||||
var feeConfig = await _feeConfigRepository.GetFirstAsync(f => f.EventId == input.EventId && !f.DeleteMark);
|
||||
|
||||
// 先获取所有需要的 eventProjectGroup 信息和 eventProject 信息
|
||||
var eventProjectIds = input.Items.Select(i => i.ProjectId).Distinct().ToList();
|
||||
var allEventProjectGroups = await _eventProjectGroupRepository.GetListAsync(
|
||||
eg => eventProjectIds.Contains(eg.EventProjectId) && !eg.DeleteMark);
|
||||
var eventProjects = await _eventProjectRepository.AsQueryable()
|
||||
.Where(ep => eventProjectIds.Contains(ep.Id))
|
||||
.ToListAsync();
|
||||
var projectIds = eventProjects.Select(ep => ep.ProjectId).Distinct().ToList();
|
||||
var baseProjects = await _projectRepository.AsQueryable()
|
||||
.Where(p => projectIds.Contains(p.Id))
|
||||
.ToListAsync();
|
||||
|
||||
// 计算总人数
|
||||
int totalPerson = input.Items.Sum(i => i.Persons!.Count);
|
||||
|
||||
// 构建定价服务输入
|
||||
var pricingInput = new PricingInput
|
||||
{
|
||||
ChargeMode = evt.ChargeMode,
|
||||
ParticipantCount = totalPerson,
|
||||
EventPrice = feeConfig?.Price ?? 0m
|
||||
};
|
||||
|
||||
// 添加项目信息(用于单价项目模式)
|
||||
foreach (var item in input.Items)
|
||||
{
|
||||
var ep = eventProjects.FirstOrDefault(p => p.Id == item.ProjectId);
|
||||
var bp = baseProjects.FirstOrDefault(p => p.Id == ep?.ProjectId);
|
||||
|
||||
if (ep != null)
|
||||
IsDraft = false,
|
||||
Items = input.Items?.Select(item => new UnifiedRegistrationItemInput
|
||||
{
|
||||
pricingInput.Projects.Add(new ProjectPricingInfo
|
||||
{
|
||||
ProjectId = ep.Id,
|
||||
ProjectName = bp?.Name ?? "",
|
||||
UnitPrice = ep.Price / 100m
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 使用统一的定价服务计算费用
|
||||
var pricingResult = YD_Event.Core.Domain.Pricing.EventPricingService.Calculate(pricingInput);
|
||||
|
||||
// 6. 创建报名明细 & 参赛人员
|
||||
var regItems = new List<EventRegistrationItem>();
|
||||
var persons = new List<EventRegistrationPerson>();
|
||||
|
||||
foreach (var item in input.Items)
|
||||
{
|
||||
var eventProject = eventProjects.FirstOrDefault(p => p.Id == item.ProjectId);
|
||||
if (eventProject == null) throw Oops.Bah($"项目不存在: {item.ProjectId}");
|
||||
|
||||
var project = baseProjects.FirstOrDefault(p => p.Id == eventProject.ProjectId);
|
||||
if (project == null) throw Oops.Bah($"关联基础项目不存在: {item.ProjectId}");
|
||||
|
||||
// 获取项目类型名称
|
||||
var projectTypeName = project.ProjectType.Description();
|
||||
|
||||
// 获取组别名称 - 优先从前端传的获取,否则从数据库获取默认组别
|
||||
var groupName = item.GroupName;
|
||||
if (string.IsNullOrWhiteSpace(groupName))
|
||||
{
|
||||
var eventProjectGroups = allEventProjectGroups.Where(eg => eg.EventProjectId == item.ProjectId).ToList();
|
||||
if (item.GroupId.HasValue)
|
||||
{
|
||||
// 如果传了 groupId,优先使用
|
||||
var selectedGroup = eventProjectGroups.FirstOrDefault(eg => eg.Id == item.GroupId.Value);
|
||||
groupName = selectedGroup?.GroupName ?? "";
|
||||
}
|
||||
|
||||
// 如果还是没有,使用第一个组别
|
||||
if (string.IsNullOrWhiteSpace(groupName))
|
||||
{
|
||||
var firstGroup = eventProjectGroups.FirstOrDefault();
|
||||
groupName = firstGroup?.GroupName ?? "";
|
||||
}
|
||||
|
||||
// 如果还是没有,设置默认值
|
||||
if (string.IsNullOrWhiteSpace(groupName))
|
||||
{
|
||||
groupName = "默认组别";
|
||||
}
|
||||
}
|
||||
|
||||
// 根据收费模式计算该项目的单价和总价
|
||||
decimal unitPrice = 0;
|
||||
decimal totalPrice = 0;
|
||||
|
||||
switch (evt.ChargeMode)
|
||||
{
|
||||
case ChargeMode.Free:
|
||||
unitPrice = 0;
|
||||
totalPrice = 0;
|
||||
break;
|
||||
|
||||
case ChargeMode.FixedPrice:
|
||||
// 定价(赛事):使用赛事费用配置的单价
|
||||
unitPrice = feeConfig?.Price ?? 0;
|
||||
totalPrice = unitPrice * item.Persons!.Count;
|
||||
break;
|
||||
|
||||
case ChargeMode.UnitPriceEvent:
|
||||
// 单价(赛事):使用赛事单价
|
||||
unitPrice = feeConfig?.Price ?? 0;
|
||||
totalPrice = unitPrice * item.Persons!.Count;
|
||||
break;
|
||||
|
||||
case ChargeMode.UnitPriceProject:
|
||||
// 单价(项目):使用项目单价
|
||||
unitPrice = eventProject.Price / 100m;
|
||||
totalPrice = unitPrice * item.Persons!.Count;
|
||||
break;
|
||||
}
|
||||
|
||||
var regItemId = _idGenerator.NewLong();
|
||||
var regItem = new EventRegistrationItem
|
||||
{
|
||||
Id = regItemId,
|
||||
RegistrationId = registrationId,
|
||||
ProjectId = item.ProjectId,
|
||||
ProjectName = project.Name,
|
||||
ProjectTypeName = projectTypeName,
|
||||
GroupName = groupName,
|
||||
UnitPrice = unitPrice,
|
||||
PersonCount = item.Persons!.Count,
|
||||
TotalPrice = totalPrice,
|
||||
CreatedUserId = userId,
|
||||
CreatedTime = DateTime.Now
|
||||
};
|
||||
regItems.Add(regItem);
|
||||
|
||||
// 添加参赛人员
|
||||
foreach (var pInput in item.Persons)
|
||||
{
|
||||
persons.Add(new EventRegistrationPerson
|
||||
GroupId = item.GroupId,
|
||||
GroupName = item.GroupName,
|
||||
Persons = item.Persons?.Select(p => new UnifiedPersonInput
|
||||
{
|
||||
Id = _idGenerator.NewLong(),
|
||||
RegistrationId = registrationId,
|
||||
RegistrationItemId = regItemId,
|
||||
ProjectId = item.ProjectId,
|
||||
PersonType = PersonType.Athlete, // 运动员
|
||||
Name = pInput.Name,
|
||||
Gender = pInput.Gender,
|
||||
IdCard = pInput.IdCardNumber,
|
||||
BirthDate = pInput.BirthDate,
|
||||
Phone = pInput.Mobile,
|
||||
GroupName = groupName,
|
||||
ProjectName = project.Name,
|
||||
CreatedUserId = userId,
|
||||
CreatedTime = DateTime.Now
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
registration.TotalAmount = pricingResult.TotalAmount;
|
||||
registration.TotalPersonCount = totalPerson;
|
||||
registration.OrderNo = await _orderNoGeneratorService.GenerateOrderNoAsync(); // 使用统一的订单号生成器
|
||||
|
||||
await _registrationRepository.InsertAsync(registration);
|
||||
await _registrationItemRepository.InsertRangeAsync(regItems);
|
||||
await _personRepository.InsertRangeAsync(persons);
|
||||
|
||||
// 创建订单记录
|
||||
var certification = await _merchantCertificationRepository.GetFirstAsync(c =>
|
||||
c.UserId == userId && c.AuditStatus == AuditStatus.Pass && !c.DeleteMark);
|
||||
|
||||
var order = new YD_Event.Core.Entities.Order
|
||||
{
|
||||
Id = _idGenerator.NewLong(),
|
||||
OrderNo = registration.OrderNo,
|
||||
OrderType = 1, // 收入
|
||||
MerchantId = evt.MerchantId,
|
||||
MerchantCertificationId = certification?.Id,
|
||||
EventId = input.EventId,
|
||||
RegistrationId = registrationId,
|
||||
OriginalAmount = pricingResult.TotalAmount,
|
||||
DiscountAmount = 0,
|
||||
ActualAmount = pricingResult.TotalAmount,
|
||||
OrderStatus = OrderStatus.Pending, // 待支付
|
||||
CreatedTime = DateTime.Now,
|
||||
CreatedUserId = userId,
|
||||
Status = AvailabilityStatus.Enable
|
||||
Name = p.Name,
|
||||
Gender = p.Gender,
|
||||
IdCardNumber = p.IdCardNumber,
|
||||
BirthDate = p.BirthDate,
|
||||
Mobile = p.Mobile
|
||||
}).ToList() ?? new List<UnifiedPersonInput>()
|
||||
}).ToList() ?? new List<UnifiedRegistrationItemInput>()
|
||||
};
|
||||
await _orderRepository.InsertAsync(order);
|
||||
|
||||
return registrationId;
|
||||
// 调用统一报名服务
|
||||
var result = await _unifiedRegistrationService.ProcessRegistrationAsync(unifiedInput);
|
||||
|
||||
return result.RegistrationId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -929,68 +719,44 @@ public class EventClientController : IDynamicApiController
|
||||
public async Task<PaymentInfoDto> GetPaymentInfo([FromBody] GetPaymentInfoInput input)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
|
||||
// 验证报名记录归属权
|
||||
var reg = await _registrationRepository.GetFirstAsync(r => r.Id == input.RegistrationId && r.CreatedUserId == userId);
|
||||
if (reg == null) throw Oops.Bah("报名记录不存在");
|
||||
|
||||
var evt = await _eventRepository.GetFirstAsync(e => e.Id == reg.EventId);
|
||||
if (evt == null) throw Oops.Bah("赛事不存在");
|
||||
|
||||
var configOptions = _wechatTenpayClientOptionsMonitor.CurrentValue;
|
||||
var merchant = await _merchantRepository.GetFirstAsync(m => m.Id == evt.MerchantId && !m.DeleteMark);
|
||||
var paymentInfo = new PaymentInfoDto
|
||||
|
||||
// 如果支付方式已经设置为微信支付或免费,直接调用统一支付服务
|
||||
int paymentMethod = reg.PaymentMethod == 0 ? 2 : reg.PaymentMethod;
|
||||
|
||||
// 构建统一支付输入
|
||||
var unifiedInput = new UnifiedPaymentInput
|
||||
{
|
||||
RegistrationId = reg.Id,
|
||||
Amount = reg.TotalAmount,
|
||||
PaymentMethod = reg.PaymentMethod,
|
||||
BankAccountName = merchant?.MerchantName ?? "上海跃动文化科技有限公司",
|
||||
BankAccountNo = merchant?.AccountNumber ?? "456498465198456261",
|
||||
BankName = merchant?.BankName ?? "招商银行"
|
||||
RegistrationId = input.RegistrationId,
|
||||
PaymentMethod = paymentMethod,
|
||||
CouponId = null,
|
||||
OpenId = input.OpenId,
|
||||
PaymentProofUrl = null,
|
||||
OperatorUserId = userId
|
||||
};
|
||||
|
||||
if (reg.PaymentMethod == 2 && reg.TotalAmount > 0)
|
||||
// 调用统一支付服务
|
||||
var unifiedResult = await _unifiedRegistrationService.ProcessPaymentAsync(unifiedInput);
|
||||
|
||||
// 构建返回结果
|
||||
var paymentInfo = new PaymentInfoDto
|
||||
{
|
||||
// 根据赛事信息获取对应的商户支付配置
|
||||
var payConfig = await GetWeChatPayConfigByEventAsync(evt);
|
||||
if (payConfig == null)
|
||||
{
|
||||
_logger.LogError("未找到可用的微信支付配置,赛事ID={EventId},商户ID={MerchantId}", evt.Id, evt.MerchantId);
|
||||
throw Oops.Bah("未找到可用的微信支付配置,请联系赛事管理员");
|
||||
}
|
||||
|
||||
_logger.LogInformation("开始创建微信 JSAPI 支付订单,订单号={OrderNo},OpenId={OpenId},商户ID={MerchantId}",
|
||||
reg.OrderNo, input.OpenId, payConfig.MerchantId);
|
||||
|
||||
var jsapiRequest = new CreateJsApiPayRequest
|
||||
{
|
||||
OutTradeNo = reg.OrderNo,
|
||||
AppId = configOptions.AppId,
|
||||
Description = $"{evt.EventName} - 报名费",
|
||||
NotifyUrl = configOptions.NotifyUrl,
|
||||
TotalAmount = AmountHelper.YuanToFen(reg.TotalAmount),
|
||||
ExpireTime = DateTimeOffset.Now.AddMinutes(30),
|
||||
OpenId = input.OpenId
|
||||
};
|
||||
|
||||
var payResult = await _weChatPayService.CreateJsApiPayAsync(payConfig, jsapiRequest);
|
||||
if (payResult.Success && payResult.JsApiParameters != null)
|
||||
{
|
||||
_logger.LogInformation("微信 JSAPI 支付下单成功,订单号={OrderNo}", reg.OrderNo);
|
||||
paymentInfo.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
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogError("微信 JSAPI 支付下单失败:订单号={OrderNo},错误信息={Message}", reg.OrderNo, payResult.Message);
|
||||
throw Oops.Bah($"微信支付下单失败:{payResult.Message}");
|
||||
}
|
||||
}
|
||||
RegistrationId = unifiedResult.RegistrationId,
|
||||
Amount = unifiedResult.ActualAmount,
|
||||
PaymentMethod = unifiedResult.PaymentStatus == 1 ? 0 : paymentMethod, // 如果已支付,不需要显示支付方式
|
||||
BankAccountName = merchant?.MerchantName,
|
||||
BankAccountNo = merchant?.AccountNumber,
|
||||
BankName = merchant?.BankName,
|
||||
WechatPayParams = unifiedResult.WechatPayParams
|
||||
};
|
||||
|
||||
return paymentInfo;
|
||||
}
|
||||
@ -1034,19 +800,30 @@ public class EventClientController : IDynamicApiController
|
||||
/// </summary>
|
||||
[HttpPost("upload-voucher/{registrationId}")]
|
||||
[DisplayName("上传支付凭证")]
|
||||
public async Task<bool> UploadPaymentVoucher(long registrationId, [FromBody] UploadVoucherInput input)
|
||||
[UnitOfWork]
|
||||
public async Task<bool> UploadPaymentVoucher(long registrationId, [FromBody] YD_Event.Application.Client.Dtos.UploadVoucherInput input)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
|
||||
// 验证报名记录归属权
|
||||
var reg = await _registrationRepository.GetFirstAsync(r => r.Id == registrationId && r.CreatedUserId == userId);
|
||||
if (reg == null) throw Oops.Bah("报名记录不存在");
|
||||
|
||||
reg.PaymentMethod = 1; // 线下
|
||||
reg.PaymentProofUrl = input.VoucherUrl;
|
||||
reg.PaymentStatus = 0; // 待审核
|
||||
reg.UpdatedTime = DateTime.Now;
|
||||
reg.UpdatedUserId = userId;
|
||||
// 构建统一支付输入
|
||||
var unifiedInput = new UnifiedPaymentInput
|
||||
{
|
||||
RegistrationId = registrationId,
|
||||
PaymentMethod = 1, // 线下支付
|
||||
CouponId = null,
|
||||
OpenId = null,
|
||||
PaymentProofUrl = input.VoucherUrl,
|
||||
OperatorUserId = userId
|
||||
};
|
||||
|
||||
// 调用统一支付服务
|
||||
await _unifiedRegistrationService.ProcessPaymentAsync(unifiedInput);
|
||||
|
||||
return await _registrationRepository.UpdateAsync(reg);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -1217,45 +994,7 @@ public class EventClientController : IDynamicApiController
|
||||
public async Task<bool> CancelRegistration(long registrationId)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
var reg = await _registrationRepository.GetFirstAsync(r => r.Id == registrationId && r.CreatedUserId == userId);
|
||||
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, userId, reg.PaymentStatus);
|
||||
|
||||
// 更新报名状态
|
||||
reg.PaymentStatus = 4; // 4=已取消
|
||||
reg.UpdatedTime = DateTime.Now;
|
||||
reg.UpdatedUserId = userId;
|
||||
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 = userId;
|
||||
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;
|
||||
return await _unifiedRegistrationService.CancelRegistrationAsync(registrationId, userId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -1290,6 +1029,18 @@ public class EventClientController : IDynamicApiController
|
||||
}
|
||||
}
|
||||
|
||||
// 构建统一支付输入
|
||||
var unifiedInput = new UnifiedPaymentInput
|
||||
{
|
||||
RegistrationId = input.RegistrationId,
|
||||
PaymentMethod = input.PaymentMethod,
|
||||
CouponId = null,
|
||||
OpenId = input.OpenId,
|
||||
PaymentProofUrl = null,
|
||||
OperatorUserId = userId
|
||||
};
|
||||
|
||||
// 调用统一支付服务(仅设置支付方式,不处理实际支付)
|
||||
reg.PaymentMethod = input.PaymentMethod;
|
||||
reg.UpdatedTime = DateTime.Now;
|
||||
reg.UpdatedUserId = userId;
|
||||
@ -1309,68 +1060,7 @@ public class EventClientController : IDynamicApiController
|
||||
public async Task<bool> SyncPaymentStatus(long registrationId)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
var reg = await _registrationRepository.GetFirstAsync(r => r.Id == registrationId && r.CreatedUserId == userId);
|
||||
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("赛事不存在");
|
||||
|
||||
// 根据赛事信息获取对应的商户支付配置
|
||||
var payConfig = await GetWeChatPayConfigByEventAsync(evt);
|
||||
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")
|
||||
{
|
||||
// 更新支付状态
|
||||
reg.PaymentStatus = 1;
|
||||
reg.PaymentTime = queryResult.SuccessTime?.DateTime;
|
||||
reg.RegistrationRemark = queryResult.TransactionId; // 将第三方交易号存储在备注字段
|
||||
reg.UpdatedTime = DateTime.Now;
|
||||
reg.UpdatedUserId = userId;
|
||||
|
||||
await _registrationRepository.UpdateAsync(reg);
|
||||
_logger.LogInformation($"订单 {reg.OrderNo} 支付状态同步成功,微信交易号:{queryResult.TransactionId}");
|
||||
|
||||
// 创建支付记录
|
||||
var payment = new EventRegistrationPayment
|
||||
{
|
||||
Id = _idGenerator.NewLong(),
|
||||
RegistrationId = reg.Id,
|
||||
TransactionNo = queryResult.TransactionId ?? "",
|
||||
PaymentChannel = 1, // 微信支付
|
||||
PaymentAmount = reg.TotalAmount,
|
||||
PaymentTime = queryResult.SuccessTime?.DateTime ?? DateTime.Now,
|
||||
PaymentStatus = 1, // 已支付
|
||||
RefundStatus = 0, // 未退款
|
||||
PayeeAccount = "", // 收款账户
|
||||
CreatedUserId = userId,
|
||||
CreatedTime = DateTime.Now
|
||||
};
|
||||
await _paymentRepository.InsertAsync(payment);
|
||||
_logger.LogInformation($"支付记录创建成功,报名ID={reg.Id},交易号={queryResult.TransactionId}");
|
||||
|
||||
// 更新 Order 记录状态
|
||||
var order = await _orderRepository.GetFirstAsync(o => o.OrderNo == reg.OrderNo);
|
||||
if (order != null)
|
||||
{
|
||||
order.OrderStatus = OrderStatus.Paid;
|
||||
order.PaymentTime = queryResult.SuccessTime?.DateTime;
|
||||
order.UpdatedTime = DateTime.Now;
|
||||
await _orderRepository.UpdateAsync(order);
|
||||
_logger.LogInformation($"Order 记录 {order.OrderNo} 状态已更新为已支付");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return await _unifiedRegistrationService.QueryAndSyncWeChatPaymentStatusAsync(registrationId, userId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -1653,7 +1343,7 @@ public class EventClientController : IDynamicApiController
|
||||
/// <returns>操作是否成功</returns>
|
||||
[HttpPost("upload-material")]
|
||||
[DisplayName("上传赛事素材")]
|
||||
public async Task<bool> UploadMaterial([FromBody] UploadMaterialInput input)
|
||||
public async Task<bool> UploadMaterial([FromBody] YD_Event.Application.Client.Dtos.UploadMaterialInput input)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
|
||||
|
||||
315
YD_Event.Application/Event/Dtos/UnifiedRegistrationDtos.cs
Normal file
315
YD_Event.Application/Event/Dtos/UnifiedRegistrationDtos.cs
Normal file
@ -0,0 +1,315 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using YD_Event.Core.Enum;
|
||||
|
||||
namespace YD_Event.Application.Event.Dtos;
|
||||
|
||||
/// <summary>
|
||||
/// 统一报名输入DTO
|
||||
/// </summary>
|
||||
public class UnifiedRegistrationInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 赛事ID
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "赛事ID不能为空")]
|
||||
public long EventId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 报名模式 (1-个人 2-团队)
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "报名模式不能为空")]
|
||||
[Range(1, 2, ErrorMessage = "报名模式只能是1(个人)或2(团队)")]
|
||||
public int RegistrationMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 团队名称 (团队报名必填)
|
||||
/// </summary>
|
||||
[MaxLength(100, ErrorMessage = "团队名称不能超过100个字符")]
|
||||
public string? TeamName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 联系人姓名
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "联系人姓名不能为空")]
|
||||
[MaxLength(50, ErrorMessage = "联系人姓名不能超过50个字符")]
|
||||
public string ContactName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 联系人电话
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "联系人电话不能为空")]
|
||||
[RegularExpression(@"^1[3-9]\d{9}$", ErrorMessage = "请输入正确的手机号格式")]
|
||||
public string ContactPhone { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 报名项目列表
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "报名项目列表不能为空")]
|
||||
[MinLength(1, ErrorMessage = "请至少选择一个项目")]
|
||||
public List<UnifiedRegistrationItemInput> Items { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 创建用户ID
|
||||
/// </summary>
|
||||
public long CreatedUserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否为草稿
|
||||
/// </summary>
|
||||
public bool IsDraft { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 统一报名项目输入DTO
|
||||
/// </summary>
|
||||
public class UnifiedRegistrationItemInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 项目ID
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "项目ID不能为空")]
|
||||
public long ProjectId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 组别ID
|
||||
/// </summary>
|
||||
public long? GroupId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 组别名称
|
||||
/// </summary>
|
||||
public string? GroupName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 参赛人员列表
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "参赛人员列表不能为空")]
|
||||
[MinLength(1, ErrorMessage = "请至少填写一个参赛人员")]
|
||||
public List<UnifiedPersonInput> Persons { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 统一参赛人员输入DTO
|
||||
/// </summary>
|
||||
public class UnifiedPersonInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户ID (可选,已有用户时使用)
|
||||
/// </summary>
|
||||
public long? UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 姓名
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "姓名不能为空")]
|
||||
[MaxLength(50, ErrorMessage = "姓名不能超过50个字符")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 身份证号
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "身份证号不能为空")]
|
||||
[RegularExpression(@"^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$", ErrorMessage = "请输入正确的身份证号格式")]
|
||||
public string IdCardNumber { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 性别
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "性别不能为空")]
|
||||
public Gender Gender { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 电话
|
||||
/// </summary>
|
||||
[RegularExpression(@"^1[3-9]\d{9}$", ErrorMessage = "请输入正确的手机号格式")]
|
||||
public string? Mobile { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 出生日期
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "出生日期不能为空")]
|
||||
public DateTime BirthDate { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 统一报名结果输出DTO
|
||||
/// </summary>
|
||||
public class UnifiedRegistrationOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 报名ID
|
||||
/// </summary>
|
||||
public long RegistrationId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 订单号
|
||||
/// </summary>
|
||||
public string OrderNo { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 总金额(优惠前)
|
||||
/// </summary>
|
||||
public decimal OriginalAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 优惠金额
|
||||
/// </summary>
|
||||
public decimal DiscountAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 实际支付金额
|
||||
/// </summary>
|
||||
public decimal ActualAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 支付状态
|
||||
/// </summary>
|
||||
public int PaymentStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否免费
|
||||
/// </summary>
|
||||
public bool IsFree => ActualAmount <= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 统一支付处理输入DTO
|
||||
/// </summary>
|
||||
public class UnifiedPaymentInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 报名ID
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "报名ID不能为空")]
|
||||
public long RegistrationId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 支付方式 (1-线下 2-线上)
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "支付方式不能为空")]
|
||||
[Range(1, 2, ErrorMessage = "支付方式只能是1(线下)或2(线上)")]
|
||||
public int PaymentMethod { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 优惠券ID (可选)
|
||||
/// </summary>
|
||||
public long? CouponId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 微信OpenId (微信支付时必填)
|
||||
/// </summary>
|
||||
public string? OpenId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 支付凭证URL (线下支付时必填)
|
||||
/// </summary>
|
||||
public string? PaymentProofUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 操作用户ID
|
||||
/// </summary>
|
||||
public long OperatorUserId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 统一支付处理输出DTO
|
||||
/// </summary>
|
||||
public class UnifiedPaymentOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 报名ID
|
||||
/// </summary>
|
||||
public long RegistrationId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 订单号
|
||||
/// </summary>
|
||||
public string OrderNo { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 支付状态
|
||||
/// </summary>
|
||||
public int PaymentStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否免费
|
||||
/// </summary>
|
||||
public bool IsFree { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 优惠前金额
|
||||
/// </summary>
|
||||
public decimal OriginalAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 优惠金额
|
||||
/// </summary>
|
||||
public decimal DiscountAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 实际支付金额
|
||||
/// </summary>
|
||||
public decimal ActualAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 优惠券使用规则说明
|
||||
/// </summary>
|
||||
public string? CouponRuleNote { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 微信支付参数 (微信支付时返回)
|
||||
/// </summary>
|
||||
public object? WechatPayParams { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 支付二维码URL (微信Native支付时返回)
|
||||
/// </summary>
|
||||
public string? QrcodeUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 交易类型
|
||||
/// </summary>
|
||||
public string? TradeType { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 报名验证结果DTO
|
||||
/// </summary>
|
||||
public class RegistrationValidationResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否通过验证
|
||||
/// </summary>
|
||||
public bool IsValid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误信息
|
||||
/// </summary>
|
||||
public string? ErrorMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 验证详情
|
||||
/// </summary>
|
||||
public List<ValidationDetail>? Details { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证详情DTO
|
||||
/// </summary>
|
||||
public class ValidationDetail
|
||||
{
|
||||
/// <summary>
|
||||
/// 验证项名称
|
||||
/// </summary>
|
||||
public string ItemName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 是否通过
|
||||
/// </summary>
|
||||
public bool IsPassed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息
|
||||
/// </summary>
|
||||
public string Message { get; set; } = string.Empty;
|
||||
}
|
||||
1283
YD_Event.Application/Event/UnifiedRegistrationService.cs
Normal file
1283
YD_Event.Application/Event/UnifiedRegistrationService.cs
Normal file
File diff suppressed because it is too large
Load Diff
@ -368,7 +368,7 @@ public class UserEventRecordService : IDynamicApiController, ITransient
|
||||
Code = 200, Success = true,
|
||||
Data = new CertificateDTO
|
||||
{
|
||||
CertUrl = cert.CertificateUrl + "?sas_token=mock_10min_token", // 模拟带签名 URL
|
||||
CertUrl = cert.CertificateUrl,
|
||||
CertNumber = cert.CertificateNo,
|
||||
IssueTime = cert.IssuedTime ?? DateTime.MinValue,
|
||||
ExpireTime = null
|
||||
|
||||
@ -65,6 +65,7 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
|
||||
private readonly ISqlSugarRepository<EventRegistrationPayment> _paymentRepository;
|
||||
private readonly ILogger<UserEventRegistrationService> _logger;
|
||||
private readonly OrderNoGeneratorService _orderNoGeneratorService;
|
||||
private readonly UnifiedRegistrationService _unifiedRegistrationService;
|
||||
|
||||
public UserEventRegistrationService(
|
||||
ISqlSugarRepository<EventInfo> eventRepository,
|
||||
@ -97,7 +98,8 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
|
||||
ISqlSugarRepository<YD_Event.Core.Entities.CouponRedemptionCode> redemptionCodeRepository,
|
||||
ISqlSugarRepository<EventRegistrationPayment> paymentRepository,
|
||||
ILogger<UserEventRegistrationService> logger,
|
||||
OrderNoGeneratorService orderNoGeneratorService)
|
||||
OrderNoGeneratorService orderNoGeneratorService,
|
||||
UnifiedRegistrationService unifiedRegistrationService)
|
||||
{
|
||||
_eventRepository = eventRepository;
|
||||
_unitRepository = unitRepository;
|
||||
@ -130,6 +132,7 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
|
||||
_paymentRepository = paymentRepository;
|
||||
_logger = logger;
|
||||
_orderNoGeneratorService = orderNoGeneratorService;
|
||||
_unifiedRegistrationService = unifiedRegistrationService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -405,11 +408,14 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
|
||||
var eventInfo = await _eventRepository.GetByIdAsync(input.EventId);
|
||||
if (eventInfo == null) throw Oops.Bah("赛事不存在");
|
||||
|
||||
// 验证2:一次报名只能选择单个团队内的运动员
|
||||
// 获取团队信息
|
||||
var team = await _db.Queryable<YD_Event.Core.Entities.Team>().FirstAsync(t => t.Id == input.TeamId && !t.DeleteMark);
|
||||
if (team == null) throw Oops.Bah("团队不存在");
|
||||
|
||||
// 验证:一次报名只能选择单个团队内的运动员
|
||||
if (input.MemberIds != null && input.MemberIds.Count > 0)
|
||||
{
|
||||
// 获取所有队员的团队关系
|
||||
var teamMembers = await _db.Queryable<TeamMember>()
|
||||
var teamMembers = await _db.Queryable<YD_Event.Core.Entities.TeamMember>()
|
||||
.Where(tm => input.MemberIds.Contains(tm.UserId))
|
||||
.Where(tm => !tm.DeleteMark)
|
||||
.ToListAsync();
|
||||
@ -419,19 +425,16 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
|
||||
throw Oops.Bah("未找到队员信息");
|
||||
}
|
||||
|
||||
// 按用户ID分组,获取每个用户所属的所有团队
|
||||
var userTeamMap = teamMembers
|
||||
.GroupBy(tm => tm.UserId)
|
||||
.ToDictionary(g => g.Key, g => g.Select(tm => tm.TeamId).ToList());
|
||||
|
||||
// 检查是否所有用户都有团队信息
|
||||
var missingUsers = input.MemberIds.Except(userTeamMap.Keys).ToList();
|
||||
if (missingUsers.Any())
|
||||
{
|
||||
throw Oops.Bah("部分队员未找到团队信息");
|
||||
}
|
||||
|
||||
// 验证所有队员是否都属于指定的 TeamId
|
||||
var allBelongToTeam = userTeamMap.All(ut => ut.Value.Contains(input.TeamId));
|
||||
if (!allBelongToTeam)
|
||||
{
|
||||
@ -439,73 +442,27 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
|
||||
}
|
||||
}
|
||||
|
||||
// 验证3:调用检查报名限制API,确保所有限制都满足
|
||||
var checkInput = new RegistrationLimitCheckInput
|
||||
{
|
||||
EventId = input.EventId,
|
||||
MemberIds = input.MemberIds ?? new List<long>(),
|
||||
GroupIds = input.GroupMembers?.Select(g => g.GroupId).Distinct().ToList() ?? new List<long>()
|
||||
};
|
||||
var checkResult = await CheckRegistrationLimit(checkInput);
|
||||
if (checkResult.HasLimit)
|
||||
{
|
||||
var firstLimit = checkResult.LimitDetails.FirstOrDefault();
|
||||
throw Oops.Bah(firstLimit?.LimitMessage ?? "报名限制检查失败");
|
||||
}
|
||||
|
||||
var registration = await _registrationRepository.GetFirstAsync(x => x.EventId == input.EventId && x.CreatedUserId == userId && !x.DeleteMark);
|
||||
|
||||
if (registration == null)
|
||||
{
|
||||
registration = new EventRegistration
|
||||
{
|
||||
Id = _idGenerator.NewLong(),
|
||||
EventId = input.EventId,
|
||||
CreatedUserId = userId,
|
||||
CreatedTime = DateTime.Now,
|
||||
Status = AvailabilityStatus.Enable,
|
||||
PaymentStatus = input.IsDraft ? 0 : 0, // 0-未支付/待审核
|
||||
ContactName = userName,
|
||||
ContactPhone = userMobile,
|
||||
OrderNo = "",
|
||||
RegistrationMode = 2 // PC端只做团队报名
|
||||
};
|
||||
await _registrationRepository.InsertAsync(registration);
|
||||
}
|
||||
else
|
||||
{
|
||||
registration.PaymentStatus = input.IsDraft ? 0 : 0;
|
||||
registration.ContactName = userName;
|
||||
registration.ContactPhone = userMobile;
|
||||
registration.UpdatedTime = DateTime.Now;
|
||||
registration.UpdatedUserId = userId;
|
||||
await _registrationRepository.UpdateAsync(registration);
|
||||
}
|
||||
|
||||
await _registrationItemRepository.DeleteAsync(x => x.RegistrationId == registration.Id);
|
||||
await _registrationPersonRepository.DeleteAsync(x => x.RegistrationId == registration.Id);
|
||||
|
||||
// 获取成员信息
|
||||
var members = await _userRepository.GetListAsync(x => input.MemberIds.Contains(x.Id));
|
||||
|
||||
// 获取所有组别信息
|
||||
var groupIds = input.GroupMembers?.Select(g => g.GroupId).Distinct().ToList() ?? new List<long>();
|
||||
var eventProjectGroups = await _eventProjectGroupRepository.GetListAsync(x => groupIds.Contains(x.Id));
|
||||
|
||||
// 通过EventProjectGroup获取对应的EventProject
|
||||
var eventProjectIds = eventProjectGroups.Select(x => x.EventProjectId).Distinct().ToList();
|
||||
var eventProjects = await _eventProjectRepository.GetListAsync(x => eventProjectIds.Contains(x.Id));
|
||||
|
||||
var baseProjectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList();
|
||||
var baseProjects = await _projectRepository.GetListAsync(x => baseProjectIds.Contains(x.Id));
|
||||
|
||||
var baseProjectGroupIds = eventProjectGroups.Select(x => x.ProjectGroupId).Distinct().ToList();
|
||||
var baseProjectGroups = await _projectGroupRepository.GetListAsync(x => baseProjectGroupIds.Contains(x.Id));
|
||||
|
||||
// 获取赛事费用配置
|
||||
var feeConfig = await _feeConfigRepository.GetFirstAsync(x => x.EventId == input.EventId && !x.DeleteMark);
|
||||
|
||||
int totalPersonCount = 0;
|
||||
FeeCalculationResult? feeCalculationResult = null;
|
||||
// 构建统一报名输入
|
||||
var unifiedInput = new UnifiedRegistrationInput
|
||||
{
|
||||
EventId = input.EventId,
|
||||
RegistrationMode = 2, // PC端只做团队报名
|
||||
TeamName = team.Name,
|
||||
ContactName = userName,
|
||||
ContactPhone = userMobile,
|
||||
Items = new List<UnifiedRegistrationItemInput>(),
|
||||
CreatedUserId = userId,
|
||||
IsDraft = input.IsDraft
|
||||
};
|
||||
|
||||
// 填充项目和人员信息
|
||||
if (input.GroupMembers != null && input.GroupMembers.Any())
|
||||
{
|
||||
foreach (var groupMember in input.GroupMembers)
|
||||
@ -513,186 +470,30 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
|
||||
var eventProjectGroup = eventProjectGroups.FirstOrDefault(g => g.Id == groupMember.GroupId);
|
||||
if (eventProjectGroup == null) continue;
|
||||
|
||||
var eventProject = eventProjects.FirstOrDefault(p => p.Id == eventProjectGroup.EventProjectId);
|
||||
if (eventProject == null) continue;
|
||||
var groupMembersList = members.Where(m => groupMember.MemberIds.Contains(m.Id)).ToList();
|
||||
if (!groupMembersList.Any()) continue;
|
||||
|
||||
var baseProject = baseProjects.FirstOrDefault(x => x.Id == eventProject.ProjectId);
|
||||
var baseProjectGroup = baseProjectGroups.FirstOrDefault(bg => bg.Id == eventProjectGroup.ProjectGroupId);
|
||||
|
||||
var groupMembers = members.Where(m => groupMember.MemberIds.Contains(m.Id)).ToList();
|
||||
if (!groupMembers.Any()) continue;
|
||||
|
||||
// 根据收费模式设置单价和总价
|
||||
decimal unitPrice = 0;
|
||||
decimal totalPrice = 0;
|
||||
|
||||
switch (feeConfig.ChargeMode)
|
||||
unifiedInput.Items.Add(new UnifiedRegistrationItemInput
|
||||
{
|
||||
case ChargeMode.Free:
|
||||
unitPrice = 0;
|
||||
totalPrice = 0;
|
||||
break;
|
||||
|
||||
case ChargeMode.FixedPrice:
|
||||
// 定价(赛事)模式:使用赛事费用配置的单价
|
||||
unitPrice = feeConfig.Price;
|
||||
totalPrice = unitPrice * groupMembers.Count();
|
||||
break;
|
||||
|
||||
case ChargeMode.UnitPriceEvent:
|
||||
// 单价(赛事)模式:使用赛事单价
|
||||
unitPrice = feeConfig.Price;
|
||||
totalPrice = unitPrice * groupMembers.Count();
|
||||
break;
|
||||
|
||||
case ChargeMode.UnitPriceProject:
|
||||
// 单价(项目)模式:使用项目单价
|
||||
unitPrice = eventProject.Price;
|
||||
totalPrice = unitPrice * groupMembers.Count();
|
||||
break;
|
||||
}
|
||||
|
||||
var item = new EventRegistrationItem
|
||||
{
|
||||
Id = _idGenerator.NewLong(),
|
||||
RegistrationId = registration.Id,
|
||||
ProjectId = eventProject.Id,
|
||||
EventProjectGroupId = eventProjectGroup.Id,
|
||||
ProjectName = baseProject?.Name ?? "",
|
||||
GroupName = baseProjectGroup?.GroupName ?? "",
|
||||
ProjectTypeName = baseProject?.ProjectType.Description() ?? "",
|
||||
UnitPrice = unitPrice,
|
||||
PersonCount = groupMembers.Count(),
|
||||
TotalPrice = totalPrice,
|
||||
CreatedUserId = userId,
|
||||
CreatedTime = DateTime.Now
|
||||
};
|
||||
await _registrationItemRepository.InsertAsync(item);
|
||||
|
||||
totalPersonCount += item.PersonCount;
|
||||
|
||||
foreach (var member in groupMembers)
|
||||
{
|
||||
var person = new EventRegistrationPerson
|
||||
ProjectId = eventProjectGroup.EventProjectId,
|
||||
GroupId = groupMember.GroupId,
|
||||
GroupName = eventProjectGroup.GroupName,
|
||||
Persons = groupMembersList.Select(m => new UnifiedPersonInput
|
||||
{
|
||||
Id = _idGenerator.NewLong(),
|
||||
RegistrationId = registration.Id,
|
||||
RegistrationItemId = item.Id,
|
||||
ProjectId = eventProject.Id,
|
||||
EventProjectGroupId = eventProjectGroup.Id,
|
||||
GroupName = baseProjectGroup?.GroupName ?? "",
|
||||
ProjectName = baseProject?.Name ?? "",
|
||||
Name = member.Name,
|
||||
Gender = member.Gender,
|
||||
IdCard = member.IdCardNumber,
|
||||
Phone = member.Mobile,
|
||||
Age = CalculateAge(member.BirthDate),
|
||||
BirthDate = member.BirthDate ?? DateTime.MinValue,
|
||||
PersonType = PersonType.Athlete,
|
||||
CreatedUserId = userId,
|
||||
CreatedTime = DateTime.Now
|
||||
};
|
||||
await _registrationPersonRepository.InsertAsync(person);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var eventProject in eventProjects)
|
||||
{
|
||||
var baseProject = baseProjects.FirstOrDefault(x => x.Id == eventProject.ProjectId);
|
||||
|
||||
// 根据收费模式设置单价和总价
|
||||
decimal unitPrice = 0;
|
||||
decimal totalPrice = 0;
|
||||
|
||||
if (feeConfig != null)
|
||||
{
|
||||
switch (feeConfig.ChargeMode)
|
||||
{
|
||||
case ChargeMode.Free:
|
||||
unitPrice = 0;
|
||||
totalPrice = 0;
|
||||
break;
|
||||
|
||||
case ChargeMode.FixedPrice:
|
||||
// 定价(赛事)模式:使用赛事费用配置的单价
|
||||
unitPrice = feeConfig.Price;
|
||||
totalPrice = unitPrice * members.Count();
|
||||
break;
|
||||
|
||||
case ChargeMode.UnitPriceEvent:
|
||||
// 单价(赛事)模式:使用赛事单价
|
||||
unitPrice = feeConfig.Price;
|
||||
totalPrice = unitPrice * members.Count();
|
||||
break;
|
||||
|
||||
case ChargeMode.UnitPriceProject:
|
||||
// 单价(项目)模式:使用项目单价
|
||||
unitPrice = eventProject.Price;
|
||||
totalPrice = unitPrice * members.Count();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var item = new EventRegistrationItem
|
||||
{
|
||||
Id = _idGenerator.NewLong(),
|
||||
RegistrationId = registration.Id,
|
||||
ProjectId = eventProject.Id,
|
||||
ProjectName = baseProject?.Name ?? "",
|
||||
ProjectTypeName = baseProject?.ProjectType.Description() ?? "",
|
||||
UnitPrice = unitPrice,
|
||||
PersonCount = members.Count(),
|
||||
TotalPrice = totalPrice,
|
||||
CreatedUserId = userId,
|
||||
CreatedTime = DateTime.Now
|
||||
};
|
||||
await _registrationItemRepository.InsertAsync(item);
|
||||
|
||||
totalPersonCount += item.PersonCount;
|
||||
|
||||
foreach (var member in members)
|
||||
{
|
||||
var person = new EventRegistrationPerson
|
||||
{
|
||||
Id = _idGenerator.NewLong(),
|
||||
RegistrationId = registration.Id,
|
||||
RegistrationItemId = item.Id,
|
||||
ProjectId = eventProject.Id,
|
||||
GroupName = baseProject?.Name ?? "",
|
||||
ProjectName = baseProject?.Name ?? "",
|
||||
Name = member.Name,
|
||||
Gender = member.Gender,
|
||||
IdCard = member.IdCardNumber,
|
||||
Phone = member.Mobile,
|
||||
Age = CalculateAge(member.BirthDate),
|
||||
BirthDate = member.BirthDate ?? DateTime.MinValue,
|
||||
PersonType = PersonType.Athlete,
|
||||
CreatedUserId = userId,
|
||||
CreatedTime = DateTime.Now
|
||||
};
|
||||
await _registrationPersonRepository.InsertAsync(person);
|
||||
}
|
||||
UserId = m.Id,
|
||||
Name = m.Name ?? "",
|
||||
IdCardNumber = m.IdCardNumber ?? "",
|
||||
Gender = m.Gender,
|
||||
Mobile = m.Mobile,
|
||||
BirthDate = m.BirthDate ?? DateTime.MinValue
|
||||
}).ToList()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (totalPersonCount > 0)
|
||||
{
|
||||
var feeInput = new FeeCalculationInput
|
||||
{
|
||||
EventId = input.EventId,
|
||||
EventProjectIds = eventProjectIds,
|
||||
ParticipantCount = totalPersonCount
|
||||
};
|
||||
feeCalculationResult = await CalculateFeeAsync(feeInput);
|
||||
registration.TotalAmount = feeCalculationResult.TotalAmount;
|
||||
}
|
||||
|
||||
registration.TotalPersonCount = totalPersonCount;
|
||||
await _registrationRepository.UpdateAsync(registration);
|
||||
|
||||
return registration.Id;
|
||||
// 调用统一报名服务
|
||||
var result = await _unifiedRegistrationService.ProcessRegistrationAsync(unifiedInput);
|
||||
return result.RegistrationId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -769,155 +570,30 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
|
||||
[UnitOfWork]
|
||||
public async Task<UserPaymentResultOutput> CreateOrder([FromBody] UserCreateOrderInput input)
|
||||
{
|
||||
var registration = await _registrationRepository.GetByIdAsync(input.RegistrationId);
|
||||
if (registration == null) throw Oops.Bah("报名信息不存在");
|
||||
var userId = _authManager.UserId;
|
||||
|
||||
var eventInfo = await _eventRepository.GetByIdAsync(registration.EventId);
|
||||
if (eventInfo == null) throw Oops.Bah("赛事信息不存在");
|
||||
|
||||
var result = new UserPaymentResultOutput();
|
||||
|
||||
// 计算最终支付金额
|
||||
decimal originalAmount = registration.TotalAmount;
|
||||
decimal finalAmount = originalAmount;
|
||||
decimal discountAmount = 0;
|
||||
YD_Event.Core.Entities.Coupon? coupon = null;
|
||||
YD_Event.Core.Entities.UserCoupon? userCoupon = null;
|
||||
|
||||
// 处理优惠券
|
||||
if (input.CouponId.HasValue)
|
||||
// 构建统一支付输入
|
||||
var unifiedInput = new UnifiedPaymentInput
|
||||
{
|
||||
var userId = _authManager.UserId;
|
||||
coupon = await _couponRepository.GetFirstAsync(c =>
|
||||
c.Id == input.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 && // 0-未使用
|
||||
!uc.DeleteMark);
|
||||
|
||||
if (userCoupon != null)
|
||||
{
|
||||
// 计算折扣金额
|
||||
if (coupon.Type == 0) // 折扣
|
||||
{
|
||||
discountAmount = originalAmount * (1 - coupon.Value / 100);
|
||||
}
|
||||
else if (coupon.Type == 1) // 定额
|
||||
{
|
||||
discountAmount = Math.Min(coupon.Value, originalAmount);
|
||||
}
|
||||
|
||||
finalAmount = originalAmount - discountAmount;
|
||||
finalAmount = Math.Max(0, finalAmount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果最终金额为0,直接完成报名,无需支付
|
||||
if (finalAmount <= 0)
|
||||
{
|
||||
// 如果使用了优惠券,标记为已使用
|
||||
if (userCoupon != null)
|
||||
{
|
||||
userCoupon.Status = 1; // 1-已使用
|
||||
userCoupon.UseTime = DateTime.Now;
|
||||
await _userCouponRepository.UpdateAsync(userCoupon);
|
||||
}
|
||||
|
||||
registration.PaymentStatus = 1; // 已支付
|
||||
registration.PaymentTime = DateTime.Now;
|
||||
await _registrationRepository.UpdateAsync(registration);
|
||||
|
||||
result.Status = 1;
|
||||
result.IsFree = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
// 获取当前用户的单位认证ID
|
||||
var certification = await _db.Queryable<MerchantCertification>()
|
||||
.FirstAsync(c => c.UserId == registration.CreatedUserId && c.AuditStatus == AuditStatus.Pass && !c.DeleteMark);
|
||||
|
||||
var orderNo = await _orderNoGeneratorService.GenerateOrderNoAsync();
|
||||
registration.OrderNo = orderNo;
|
||||
registration.PaymentMethod = input.PaymentMethod;
|
||||
registration.PaymentStatus = 0; // 未支付
|
||||
await _registrationRepository.UpdateAsync(registration);
|
||||
|
||||
// 创建内部订单记录
|
||||
var order = new YD_Event.Core.Entities.Order
|
||||
{
|
||||
Id = _idGenerator.NewLong(),
|
||||
OrderNo = orderNo,
|
||||
OrderType = 1, // Income
|
||||
MerchantId = eventInfo.MerchantId,
|
||||
MerchantCertificationId = certification?.Id, // 设置单位认证ID
|
||||
EventId = registration.EventId,
|
||||
RegistrationId = registration.Id,
|
||||
OriginalAmount = originalAmount,
|
||||
ActualAmount = finalAmount,
|
||||
DiscountAmount = discountAmount,
|
||||
RegistrationId = input.RegistrationId,
|
||||
PaymentMethod = input.PaymentMethod,
|
||||
CouponId = input.CouponId,
|
||||
OrderStatus = OrderStatus.Pending,
|
||||
CreatedTime = DateTime.Now,
|
||||
CreatedUserId = registration.CreatedUserId
|
||||
OperatorUserId = userId
|
||||
};
|
||||
await _orderRepository.InsertAsync(order);
|
||||
|
||||
result.OrderNo = orderNo;
|
||||
result.Status = 0;
|
||||
result.IsFree = false;
|
||||
// 调用统一支付处理服务
|
||||
var unifiedResult = await _unifiedRegistrationService.ProcessPaymentAsync(unifiedInput);
|
||||
|
||||
// 如果是微信支付 (假设 1 是微信支付)
|
||||
if (input.PaymentMethod == 1)
|
||||
// 转换结果
|
||||
var result = new UserPaymentResultOutput
|
||||
{
|
||||
WeChatPayConfig? payConfig = null;
|
||||
if (eventInfo.MerchantId.HasValue)
|
||||
{
|
||||
payConfig = await _weChatPayConfigRepository.GetFirstAsync(x =>
|
||||
x.MerchantId == eventInfo.MerchantId.Value &&
|
||||
x.Status == AvailabilityStatus.Enable &&
|
||||
!x.DeleteMark);
|
||||
}
|
||||
|
||||
if (payConfig == null)
|
||||
{
|
||||
throw Oops.Bah("商户微信支付配置不存在");
|
||||
}
|
||||
|
||||
var configOptions = _wechatTenpayClientOptionsMonitor.CurrentValue;
|
||||
var nativeRequest = new CreateNativePayRequest
|
||||
{
|
||||
OutTradeNo = orderNo,
|
||||
AppId = configOptions.AppId,
|
||||
Description = $"{eventInfo.EventName} - 报名费",
|
||||
NotifyUrl = configOptions.NotifyUrl,
|
||||
TotalAmount = AmountHelper.YuanToFen(finalAmount), // 单位为分,使用优惠后的金额
|
||||
ExpireTime = DateTimeOffset.Now.AddMinutes(5)
|
||||
};
|
||||
|
||||
var payResult = await _weChatPayService.CreateNativePayAsync(payConfig, nativeRequest);
|
||||
if (payResult.Success)
|
||||
{
|
||||
result.PayUrl = payResult.QrcodeUrl ?? string.Empty;
|
||||
result.QrcodeUrl = payResult.QrcodeUrl;
|
||||
result.TradeType = payResult.TradeType;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw Oops.Bah($"微信统一下单失败:{payResult.Message}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result.PayUrl = "http://mock-pay-url.com/" + orderNo;
|
||||
result.TradeType = "MOCK";
|
||||
}
|
||||
OrderNo = unifiedResult.OrderNo,
|
||||
Status = unifiedResult.PaymentStatus,
|
||||
IsFree = unifiedResult.IsFree,
|
||||
PayUrl = unifiedResult.QrcodeUrl ?? string.Empty,
|
||||
QrcodeUrl = unifiedResult.QrcodeUrl,
|
||||
TradeType = unifiedResult.TradeType
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -950,8 +626,7 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
|
||||
return new JsonResult(new { code = "FAIL", message = "参数不完整" });
|
||||
}
|
||||
|
||||
var platformPayConfig = await _weChatPayConfigRepository.GetFirstAsync(x =>
|
||||
x.Status == AvailabilityStatus.Enable && !x.DeleteMark);
|
||||
var platformPayConfig = await _unifiedRegistrationService.GetPlatformWeChatPayConfigAsync();
|
||||
if (platformPayConfig == null)
|
||||
{
|
||||
_logger.LogError("未找到平台级别的微信支付配置");
|
||||
@ -981,63 +656,12 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
|
||||
return new JsonResult(new { code = "SUCCESS", message = "成功" });
|
||||
}
|
||||
|
||||
var reg = await _registrationRepository.GetFirstAsync(r => r.OrderNo == notifyData.OutTradeNo);
|
||||
if (reg == null)
|
||||
{
|
||||
_logger.LogWarning($"未找到订单:{notifyData.OutTradeNo}");
|
||||
return new JsonResult(new { code = "SUCCESS", message = "成功" });
|
||||
}
|
||||
|
||||
if (reg.PaymentStatus == 1)
|
||||
{
|
||||
_logger.LogInformation($"订单 {reg.OrderNo} 已处理过支付回调");
|
||||
return new JsonResult(new { code = "SUCCESS", message = "成功" });
|
||||
}
|
||||
|
||||
var evt = await _eventRepository.GetFirstAsync(e => e.Id == reg.EventId);
|
||||
if (evt == null)
|
||||
{
|
||||
_logger.LogError($"未找到赛事信息,订单号:{reg.OrderNo},赛事ID:{reg.EventId}");
|
||||
return new JsonResult(new { code = "FAIL", message = "赛事信息不存在" });
|
||||
}
|
||||
|
||||
_logger.LogInformation("订单支付成功,订单号={OrderNo},赛事ID={EventId},商户ID={MerchantId}",
|
||||
reg.OrderNo, evt.Id, evt.MerchantId);
|
||||
|
||||
reg.PaymentStatus = 1;
|
||||
reg.PaymentTime = notifyData.SuccessTime?.DateTime;
|
||||
reg.RegistrationRemark = notifyData.TransactionId;
|
||||
reg.UpdatedTime = DateTime.Now;
|
||||
|
||||
await _registrationRepository.UpdateAsync(reg);
|
||||
_logger.LogInformation($"订单 {reg.OrderNo} 支付成功,微信交易号:{notifyData.TransactionId}");
|
||||
|
||||
var payment = new EventRegistrationPayment
|
||||
{
|
||||
Id = _idGenerator.NewLong(),
|
||||
RegistrationId = reg.Id,
|
||||
TransactionNo = notifyData.TransactionId ?? "",
|
||||
PaymentChannel = 1,
|
||||
PaymentAmount = reg.TotalAmount,
|
||||
PaymentTime = notifyData.SuccessTime?.DateTime ?? DateTime.Now,
|
||||
PaymentStatus = 1,
|
||||
RefundStatus = 0,
|
||||
PayeeAccount = "",
|
||||
CreatedUserId = reg.CreatedUserId,
|
||||
CreatedTime = DateTime.Now
|
||||
};
|
||||
await _paymentRepository.InsertAsync(payment);
|
||||
_logger.LogInformation($"支付记录创建成功,报名ID={reg.Id},交易号={notifyData.TransactionId}");
|
||||
|
||||
var order = await _orderRepository.GetFirstAsync(o => o.OrderNo == reg.OrderNo);
|
||||
if (order != null)
|
||||
{
|
||||
order.OrderStatus = OrderStatus.Paid;
|
||||
order.PaymentTime = notifyData.SuccessTime?.DateTime;
|
||||
order.UpdatedTime = DateTime.Now;
|
||||
await _orderRepository.UpdateAsync(order);
|
||||
_logger.LogInformation($"Order 记录 {order.OrderNo} 状态已更新为已支付");
|
||||
}
|
||||
// 调用统一支付成功处理方法
|
||||
await _unifiedRegistrationService.HandlePaymentSuccessAsync(
|
||||
notifyData.OutTradeNo,
|
||||
notifyData.TransactionId,
|
||||
notifyData.SuccessTime?.DateTime,
|
||||
0);
|
||||
|
||||
return new JsonResult(new { code = "SUCCESS", message = "成功" });
|
||||
}
|
||||
|
||||
@ -738,7 +738,7 @@
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Banner.Dtos.BannerConfigInput.Mode">
|
||||
<summary>
|
||||
模式(0-手动配置 1-自动配置)
|
||||
模式(0-自动配置 1-手动配置)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:YD_Event.Application.Banner.Dtos.BannerConfigOutput">
|
||||
@ -748,7 +748,7 @@
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Banner.Dtos.BannerConfigOutput.Mode">
|
||||
<summary>
|
||||
模式(0-手动配置 1-自动配置)
|
||||
模式(0-自动配置 1-手动配置)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.BaseService`1.Delete(YD_Event.Core.Shared.KeyDto)">
|
||||
@ -5554,6 +5554,286 @@
|
||||
发放时间
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:YD_Event.Application.Event.Dtos.UnifiedRegistrationInput">
|
||||
<summary>
|
||||
统一报名输入DTO
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedRegistrationInput.EventId">
|
||||
<summary>
|
||||
赛事ID
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedRegistrationInput.RegistrationMode">
|
||||
<summary>
|
||||
报名模式 (1-个人 2-团队)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedRegistrationInput.TeamName">
|
||||
<summary>
|
||||
团队名称 (团队报名必填)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedRegistrationInput.ContactName">
|
||||
<summary>
|
||||
联系人姓名
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedRegistrationInput.ContactPhone">
|
||||
<summary>
|
||||
联系人电话
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedRegistrationInput.Items">
|
||||
<summary>
|
||||
报名项目列表
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedRegistrationInput.CreatedUserId">
|
||||
<summary>
|
||||
创建用户ID
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedRegistrationInput.IsDraft">
|
||||
<summary>
|
||||
是否为草稿
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:YD_Event.Application.Event.Dtos.UnifiedRegistrationItemInput">
|
||||
<summary>
|
||||
统一报名项目输入DTO
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedRegistrationItemInput.ProjectId">
|
||||
<summary>
|
||||
项目ID
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedRegistrationItemInput.GroupId">
|
||||
<summary>
|
||||
组别ID
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedRegistrationItemInput.GroupName">
|
||||
<summary>
|
||||
组别名称
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedRegistrationItemInput.Persons">
|
||||
<summary>
|
||||
参赛人员列表
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:YD_Event.Application.Event.Dtos.UnifiedPersonInput">
|
||||
<summary>
|
||||
统一参赛人员输入DTO
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedPersonInput.UserId">
|
||||
<summary>
|
||||
用户ID (可选,已有用户时使用)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedPersonInput.Name">
|
||||
<summary>
|
||||
姓名
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedPersonInput.IdCardNumber">
|
||||
<summary>
|
||||
身份证号
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedPersonInput.Gender">
|
||||
<summary>
|
||||
性别
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedPersonInput.Mobile">
|
||||
<summary>
|
||||
电话
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedPersonInput.BirthDate">
|
||||
<summary>
|
||||
出生日期
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:YD_Event.Application.Event.Dtos.UnifiedRegistrationOutput">
|
||||
<summary>
|
||||
统一报名结果输出DTO
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedRegistrationOutput.RegistrationId">
|
||||
<summary>
|
||||
报名ID
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedRegistrationOutput.OrderNo">
|
||||
<summary>
|
||||
订单号
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedRegistrationOutput.OriginalAmount">
|
||||
<summary>
|
||||
总金额(优惠前)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedRegistrationOutput.DiscountAmount">
|
||||
<summary>
|
||||
优惠金额
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedRegistrationOutput.ActualAmount">
|
||||
<summary>
|
||||
实际支付金额
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedRegistrationOutput.PaymentStatus">
|
||||
<summary>
|
||||
支付状态
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedRegistrationOutput.IsFree">
|
||||
<summary>
|
||||
是否免费
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:YD_Event.Application.Event.Dtos.UnifiedPaymentInput">
|
||||
<summary>
|
||||
统一支付处理输入DTO
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedPaymentInput.RegistrationId">
|
||||
<summary>
|
||||
报名ID
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedPaymentInput.PaymentMethod">
|
||||
<summary>
|
||||
支付方式 (1-线下 2-线上)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedPaymentInput.CouponId">
|
||||
<summary>
|
||||
优惠券ID (可选)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedPaymentInput.OpenId">
|
||||
<summary>
|
||||
微信OpenId (微信支付时必填)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedPaymentInput.PaymentProofUrl">
|
||||
<summary>
|
||||
支付凭证URL (线下支付时必填)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedPaymentInput.OperatorUserId">
|
||||
<summary>
|
||||
操作用户ID
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:YD_Event.Application.Event.Dtos.UnifiedPaymentOutput">
|
||||
<summary>
|
||||
统一支付处理输出DTO
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedPaymentOutput.RegistrationId">
|
||||
<summary>
|
||||
报名ID
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedPaymentOutput.OrderNo">
|
||||
<summary>
|
||||
订单号
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedPaymentOutput.PaymentStatus">
|
||||
<summary>
|
||||
支付状态
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedPaymentOutput.IsFree">
|
||||
<summary>
|
||||
是否免费
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedPaymentOutput.OriginalAmount">
|
||||
<summary>
|
||||
优惠前金额
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedPaymentOutput.DiscountAmount">
|
||||
<summary>
|
||||
优惠金额
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedPaymentOutput.ActualAmount">
|
||||
<summary>
|
||||
实际支付金额
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedPaymentOutput.CouponRuleNote">
|
||||
<summary>
|
||||
优惠券使用规则说明
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedPaymentOutput.WechatPayParams">
|
||||
<summary>
|
||||
微信支付参数 (微信支付时返回)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedPaymentOutput.QrcodeUrl">
|
||||
<summary>
|
||||
支付二维码URL (微信Native支付时返回)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedPaymentOutput.TradeType">
|
||||
<summary>
|
||||
交易类型
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:YD_Event.Application.Event.Dtos.RegistrationValidationResult">
|
||||
<summary>
|
||||
报名验证结果DTO
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.RegistrationValidationResult.IsValid">
|
||||
<summary>
|
||||
是否通过验证
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.RegistrationValidationResult.ErrorMessage">
|
||||
<summary>
|
||||
错误信息
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.RegistrationValidationResult.Details">
|
||||
<summary>
|
||||
验证详情
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:YD_Event.Application.Event.Dtos.ValidationDetail">
|
||||
<summary>
|
||||
验证详情DTO
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.ValidationDetail.ItemName">
|
||||
<summary>
|
||||
验证项名称
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.ValidationDetail.IsPassed">
|
||||
<summary>
|
||||
是否通过
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.ValidationDetail.Message">
|
||||
<summary>
|
||||
消息
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:YD_Event.Application.Event.Dtos.UserEventQueryInput">
|
||||
<summary>
|
||||
用户端赛事查询输入
|
||||
@ -6956,6 +7236,129 @@
|
||||
<param name="now">当前时间</param>
|
||||
<returns>计算后的赛事状态</returns>
|
||||
</member>
|
||||
<member name="T:YD_Event.Application.Event.UnifiedRegistrationService">
|
||||
<summary>
|
||||
统一报名处理服务
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.ProcessRegistrationAsync(YD_Event.Application.Event.Dtos.UnifiedRegistrationInput)">
|
||||
<summary>
|
||||
统一报名方法A - 处理核心报名业务逻辑
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.ProcessPaymentAsync(YD_Event.Application.Event.Dtos.UnifiedPaymentInput)">
|
||||
<summary>
|
||||
统一支付处理方法 - 处理支付相关逻辑
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.ValidateRegistrationAsync(YD_Event.Application.Event.Dtos.UnifiedRegistrationInput)">
|
||||
<summary>
|
||||
验证报名
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.ValidateProjectSelectionAsync(YD_Event.Application.Event.Dtos.UnifiedRegistrationInput)">
|
||||
<summary>
|
||||
验证项目选择(一阶段:单选机制)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.ValidateTeamAssignmentAsync(YD_Event.Application.Event.Dtos.UnifiedRegistrationInput)">
|
||||
<summary>
|
||||
验证队员分配(二阶段)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.ValidatePersonQualifications(System.Collections.Generic.List{YD_Event.Application.Event.Dtos.UnifiedPersonInput},YD_Event.Core.Entities.EventProjectGroup)">
|
||||
<summary>
|
||||
验证队员资质(年龄和性别)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.ValidateAgeCrossGroup(System.Collections.Generic.List{YD_Event.Application.Event.Dtos.UnifiedPersonInput},YD_Event.Core.Entities.EventProjectGroup)">
|
||||
<summary>
|
||||
验证年龄跨组
|
||||
支持小年龄段向大年龄段跨组报名,需满足两个条件:
|
||||
①团队中至少有一名成员属于目标年龄段;
|
||||
②仅允许跨一个年龄段
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.CreateRegistrationDetailsAsync(YD_Event.Core.Entities.EventRegistration,YD_Event.Application.Event.Dtos.UnifiedRegistrationInput)">
|
||||
<summary>
|
||||
创建报名明细和参赛人员
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.CalculateFinalAmountAsync(System.Decimal,System.Nullable{System.Int64},System.Int64)">
|
||||
<summary>
|
||||
计算最终金额(含优惠券)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.HandleFreePaymentAsync(YD_Event.Core.Entities.EventRegistration,YD_Event.Core.Entities.UserCoupon,System.Int64)">
|
||||
<summary>
|
||||
处理免费支付
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.CreateOrderAsync(YD_Event.Core.Entities.EventRegistration,YD_Event.Core.Entities.EventInfo,System.Decimal,System.Decimal,System.Decimal,System.Nullable{System.Int64},System.Int64)">
|
||||
<summary>
|
||||
创建订单记录
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.HandleOnlinePaymentAsync(YD_Event.Core.Entities.EventRegistration,YD_Event.Core.Entities.EventInfo,System.Decimal,YD_Event.Application.Event.Dtos.UnifiedPaymentInput)">
|
||||
<summary>
|
||||
处理线上支付
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.HandleOfflinePaymentAsync(YD_Event.Core.Entities.EventRegistration,YD_Event.Application.Event.Dtos.UnifiedPaymentInput)">
|
||||
<summary>
|
||||
处理线下支付
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.CheckRegistrationLimitAsync(YD_Event.Application.Event.Dtos.UnifiedRegistrationInput)">
|
||||
<summary>
|
||||
检查报名限制
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.GetGroupName(YD_Event.Application.Event.Dtos.UnifiedRegistrationItemInput,System.Collections.Generic.List{YD_Event.Core.Entities.EventProjectGroup})">
|
||||
<summary>
|
||||
获取组别名称
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.CalculateItemPrice(YD_Event.Core.Entities.EventFeeConfig,YD_Event.Core.Entities.EventProject,System.Int32)">
|
||||
<summary>
|
||||
计算单个项目费用
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.CalculateTotalAmount(YD_Event.Core.Entities.EventFeeConfig,System.Collections.Generic.List{YD_Event.Core.Entities.EventProject},System.Collections.Generic.List{YD_Event.Application.Event.Dtos.UnifiedRegistrationItemInput},System.Int32)">
|
||||
<summary>
|
||||
计算总费用
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.CalculateAge(System.DateTime)">
|
||||
<summary>
|
||||
根据出生日期计算年龄
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.HandlePaymentSuccessAsync(System.String,System.String,System.Nullable{System.DateTime},System.Int64)">
|
||||
<summary>
|
||||
处理支付成功
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.CancelRegistrationAsync(System.Int64,System.Int64)">
|
||||
<summary>
|
||||
取消报名
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.QueryAndSyncWeChatPaymentStatusAsync(System.Int64,System.Int64)">
|
||||
<summary>
|
||||
查询并同步微信支付状态
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.GetPlatformWeChatPayConfigAsync">
|
||||
<summary>
|
||||
获取平台级别的微信支付配置
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.GetWeChatPayConfigByEventAsync(YD_Event.Core.Entities.EventInfo)">
|
||||
<summary>
|
||||
根据赛事获取对应的微信支付配置
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:YD_Event.Application.Event.UserEventRecordService">
|
||||
<summary>
|
||||
用户端参赛记录管理
|
||||
|
||||
@ -726,7 +726,7 @@
|
||||
</member>
|
||||
<member name="P:YD_Event.Core.Entities.BannerConfig.Mode">
|
||||
<summary>
|
||||
模式(0-手动配置 1-自动配置)
|
||||
模式(0-自动配置 1-手动配置)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Core.Entities.BannerConfig.DeleteMark">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user