小程序赛事报名
This commit is contained in:
parent
d3c3a847b0
commit
0927390ff1
@ -641,10 +641,20 @@ public class PaymentInfoDto
|
||||
public long RegistrationId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 金额
|
||||
/// 实际金额
|
||||
/// </summary>
|
||||
public decimal Amount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 原始金额
|
||||
/// </summary>
|
||||
public decimal OriginalAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 优惠金额
|
||||
/// </summary>
|
||||
public decimal DiscountAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 支付方式 (1-线下 2-线上)
|
||||
/// </summary>
|
||||
@ -959,6 +969,11 @@ public class GetPaymentInfoInput
|
||||
[MaxLength(50, ErrorMessage = "OpenId长度不能超过50个字符")]
|
||||
[RegularExpression(@"^[a-zA-Z0-9_-]+$", ErrorMessage = "OpenId格式不正确,只能包含字母、数字、下划线和连字符")]
|
||||
public string OpenId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 优惠券ID
|
||||
/// </summary>
|
||||
public long? CouponId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -499,105 +499,28 @@ public class EventClientController : IDynamicApiController
|
||||
[DisplayName("计算报名金额")]
|
||||
public async Task<CalculateAmountOutput> CalculateRegistrationAmount([FromBody] CalculateAmountInput input)
|
||||
{
|
||||
var evt = await _eventRepository.GetFirstAsync(e => e.Id == input.EventId && !e.DeleteMark);
|
||||
if (evt == null) throw Oops.Bah("赛事不存在");
|
||||
var inputItems = input.Items.Select(i => new UnifiedRegistrationService.CalculateAmountInputItem
|
||||
{
|
||||
ProjectId = i.ProjectId,
|
||||
PersonCount = i.PersonCount
|
||||
}).ToList();
|
||||
|
||||
var (totalAmount, chargeMode, itemDetails) = await _unifiedRegistrationService.CalculateRegistrationAmountAsync(input.EventId, inputItems);
|
||||
|
||||
var result = new CalculateAmountOutput
|
||||
{
|
||||
ChargeMode = evt.ChargeMode,
|
||||
TotalAmount = 0,
|
||||
ItemDetails = new List<CalculateAmountItemOutput>()
|
||||
ChargeMode = chargeMode,
|
||||
TotalAmount = totalAmount,
|
||||
ItemDetails = itemDetails.Select(d => new CalculateAmountItemOutput
|
||||
{
|
||||
ProjectId = d.projectId,
|
||||
ProjectName = d.projectName,
|
||||
UnitPrice = d.unitPrice,
|
||||
PersonCount = d.personCount,
|
||||
Subtotal = d.subtotal
|
||||
}).ToList()
|
||||
};
|
||||
|
||||
if (evt.ChargeMode == ChargeMode.Free)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// 获取赛事费用配置
|
||||
var feeConfig = await _feeConfigRepository.GetFirstAsync(f => f.EventId == input.EventId && !f.DeleteMark);
|
||||
|
||||
// 获取项目信息
|
||||
var eventProjectIds = input.Items.Select(i => i.ProjectId).Distinct().ToList();
|
||||
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 totalParticipantCount = input.Items.Sum(i => i.PersonCount);
|
||||
|
||||
// 构建定价服务输入
|
||||
var pricingInput = new PricingInput
|
||||
{
|
||||
ChargeMode = evt.ChargeMode,
|
||||
ParticipantCount = totalParticipantCount,
|
||||
EventPrice = feeConfig?.Price ?? 0m
|
||||
};
|
||||
|
||||
// 添加项目信息(用于单价项目模式)
|
||||
foreach (var item in input.Items)
|
||||
{
|
||||
var ep = eventProjects.FirstOrDefault(p => p.Id == item.ProjectId);
|
||||
if (ep == null) throw Oops.Bah($"项目不存在: {item.ProjectId}");
|
||||
|
||||
var bp = baseProjects.FirstOrDefault(p => p.Id == ep.ProjectId);
|
||||
|
||||
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);
|
||||
result.TotalAmount = pricingResult.TotalAmount;
|
||||
|
||||
// 构建明细输出
|
||||
foreach (var item in input.Items)
|
||||
{
|
||||
var ep = eventProjects.FirstOrDefault(p => p.Id == item.ProjectId);
|
||||
var bp = baseProjects.FirstOrDefault(p => p.Id == ep?.ProjectId);
|
||||
decimal unitPrice = 0;
|
||||
decimal subtotal = 0;
|
||||
|
||||
switch (evt.ChargeMode)
|
||||
{
|
||||
case ChargeMode.FixedPrice:
|
||||
// 定价(赛事):每个项目显示0,但实际只收一次固定费用
|
||||
unitPrice = 0;
|
||||
subtotal = 0;
|
||||
break;
|
||||
|
||||
case ChargeMode.UnitPriceEvent:
|
||||
// 单价(赛事):按赛事单价计算
|
||||
unitPrice = feeConfig?.Price ?? 0;
|
||||
subtotal = unitPrice * item.PersonCount;
|
||||
break;
|
||||
|
||||
case ChargeMode.UnitPriceProject:
|
||||
// 单价(项目):按项目单价计算
|
||||
unitPrice = ep?.Price / 100m ?? 0;
|
||||
subtotal = unitPrice * item.PersonCount;
|
||||
break;
|
||||
}
|
||||
|
||||
result.ItemDetails.Add(new CalculateAmountItemOutput
|
||||
{
|
||||
ProjectId = item.ProjectId,
|
||||
ProjectName = bp?.Name ?? "",
|
||||
UnitPrice = unitPrice,
|
||||
PersonCount = item.PersonCount,
|
||||
Subtotal = subtotal
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -628,8 +551,8 @@ public class EventClientController : IDynamicApiController
|
||||
}
|
||||
|
||||
// 获取当前用户信息(用于个人报名时自动填充)
|
||||
var appUser = await _appUserRepository.GetFirstAsync(u => u.CreatedUserId == userId && !u.DeleteMark);
|
||||
if (appUser == null && input.RegistrationMode == 1)
|
||||
var appUser = await _appUserRepository.GetFirstAsync(u => u.Id == userId && !u.DeleteMark);
|
||||
if (appUser == null && input.RegistrationMode == 1 && appUser.AuditStatus != AuditStatus.Pass)
|
||||
throw Oops.Bah("请先完成身份认证");
|
||||
|
||||
// 个人报名模式下自动填充联系人信息
|
||||
@ -684,6 +607,7 @@ public class EventClientController : IDynamicApiController
|
||||
ContactPhone = input.ContactPhone ?? "",
|
||||
CreatedUserId = userId,
|
||||
IsDraft = false,
|
||||
RegistrationChannel = 1, // 1-小程序渠道
|
||||
Items = input.Items?.Select(item => new UnifiedRegistrationItemInput
|
||||
{
|
||||
ProjectId = item.ProjectId,
|
||||
@ -725,6 +649,17 @@ public class EventClientController : IDynamicApiController
|
||||
return result.RegistrationId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证优惠券
|
||||
/// </summary>
|
||||
[HttpPost("coupon/validate")]
|
||||
[DisplayName("验证优惠券")]
|
||||
public async Task<ValidateCouponOutput> ValidateCoupon([FromBody] ValidateCouponInput input)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
return await _unifiedRegistrationService.ValidateAndUseCouponAsync(input, userId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取支付信息
|
||||
/// </summary>
|
||||
@ -758,7 +693,7 @@ public class EventClientController : IDynamicApiController
|
||||
RegistrationId = input.RegistrationId,
|
||||
PaymentMethod = paymentMethod,
|
||||
PaymentMethodType = reg.PaymentMethodType,
|
||||
CouponId = null,
|
||||
CouponId = input.CouponId,
|
||||
OpenId = input.OpenId,
|
||||
PaymentProofUrl = null,
|
||||
OperatorUserId = userId
|
||||
@ -772,6 +707,8 @@ public class EventClientController : IDynamicApiController
|
||||
{
|
||||
RegistrationId = unifiedResult.RegistrationId,
|
||||
Amount = unifiedResult.ActualAmount,
|
||||
OriginalAmount = unifiedResult.OriginalAmount,
|
||||
DiscountAmount = unifiedResult.DiscountAmount,
|
||||
PaymentMethod = unifiedResult.PaymentStatus == 1 ? 0 : paymentMethod, // 如果已支付,不需要显示支付方式
|
||||
BankAccountName = merchant?.MerchantName,
|
||||
BankAccountNo = merchant?.AccountNumber,
|
||||
|
||||
@ -57,6 +57,11 @@ public class UnifiedRegistrationInput
|
||||
/// 是否为草稿
|
||||
/// </summary>
|
||||
public bool IsDraft { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 报名渠道 (1-小程序 2-后台管理 3-其他)
|
||||
/// </summary>
|
||||
public int RegistrationChannel { get; set; } = 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -208,9 +208,41 @@ public class UnifiedRegistrationService : ITransient
|
||||
throw Oops.Bah("赛事信息不存在");
|
||||
}
|
||||
|
||||
// 3. 计算优惠后金额
|
||||
var (originalAmount, discountAmount, finalAmount, usedCoupon, usedUserCoupon) =
|
||||
await CalculateFinalAmountAsync(registration.TotalAmount, input.CouponId, input.OperatorUserId);
|
||||
// 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
|
||||
@ -220,7 +252,7 @@ public class UnifiedRegistrationService : ITransient
|
||||
OriginalAmount = originalAmount,
|
||||
DiscountAmount = discountAmount,
|
||||
ActualAmount = finalAmount,
|
||||
PaymentStatus = 0,
|
||||
PaymentStatus = registration.PaymentStatus,
|
||||
IsFree = finalAmount <= 0,
|
||||
CouponRuleNote = usedCoupon != null ? "优惠券使用后不予退还" : null
|
||||
};
|
||||
@ -228,17 +260,24 @@ public class UnifiedRegistrationService : ITransient
|
||||
// 5. 处理免费情况
|
||||
if (finalAmount <= 0)
|
||||
{
|
||||
await HandleFreePaymentAsync(registration, usedUserCoupon, input.OperatorUserId);
|
||||
if (registration.PaymentStatus != 1)
|
||||
{
|
||||
await HandleFreePaymentAsync(registration, usedUserCoupon, input.OperatorUserId);
|
||||
}
|
||||
output.PaymentStatus = 1;
|
||||
return output;
|
||||
}
|
||||
|
||||
// 6. 生成订单号
|
||||
// 6. 生成订单号(仅在不存在时生成)
|
||||
if (string.IsNullOrEmpty(registration.OrderNo))
|
||||
{
|
||||
registration.OrderNo = await _orderNoGeneratorService.GenerateOrderNoAsync();
|
||||
output.OrderNo = registration.OrderNo;
|
||||
}
|
||||
else
|
||||
{
|
||||
output.OrderNo = registration.OrderNo;
|
||||
}
|
||||
|
||||
// 处理支付方式转换逻辑
|
||||
// 微信支付或支付宝支付 -> 线上支付
|
||||
@ -268,18 +307,37 @@ public class UnifiedRegistrationService : ITransient
|
||||
}
|
||||
else
|
||||
{
|
||||
registration.PaymentStatus = 0; // 线上支付初始状态也是待支付
|
||||
// 线上支付:只有在未支付时才设置为待支付
|
||||
if (registration.PaymentStatus != 1)
|
||||
{
|
||||
registration.PaymentStatus = 0; // 待支付
|
||||
}
|
||||
}
|
||||
|
||||
await _registrationRepository.UpdateAsync(registration);
|
||||
|
||||
// 7. 创建订单记录
|
||||
await CreateOrderAsync(registration, eventInfo, originalAmount, discountAmount, finalAmount,
|
||||
input.CouponId, input.OperatorUserId);
|
||||
// 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;
|
||||
@ -315,7 +373,39 @@ public class UnifiedRegistrationService : ITransient
|
||||
return result;
|
||||
}
|
||||
|
||||
// 2. 验证报名时间
|
||||
// 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);
|
||||
}
|
||||
// 当不满足上述两个条件(即非个人报名或非小程序报名用户)时,跳过单位认证状态验证,不执行上述判断逻辑
|
||||
|
||||
// 4. 验证报名时间
|
||||
var now = DateTime.Now;
|
||||
if (now < eventInfo.RegistrationStartTime || now > eventInfo.RegistrationEndTime)
|
||||
{
|
||||
@ -325,7 +415,7 @@ public class UnifiedRegistrationService : ITransient
|
||||
return result;
|
||||
}
|
||||
|
||||
// 3. 验证项目选择(一阶段:单选机制,每个项目只能选择一个组别)
|
||||
// 5. 验证项目选择(一阶段:单选机制,每个项目只能选择一个组别)
|
||||
var projectSelectionResult = await ValidateProjectSelectionAsync(input);
|
||||
if (!projectSelectionResult.IsValid)
|
||||
{
|
||||
@ -336,7 +426,7 @@ public class UnifiedRegistrationService : ITransient
|
||||
}
|
||||
result.Details.AddRange(projectSelectionResult.Details);
|
||||
|
||||
// 4. 验证队员分配(二阶段:人数校验、资质验证、年龄跨组等)
|
||||
// 6. 验证队员分配(二阶段:人数校验、资质验证、年龄跨组等)
|
||||
var teamAssignmentResult = await ValidateTeamAssignmentAsync(input);
|
||||
if (!teamAssignmentResult.IsValid)
|
||||
{
|
||||
@ -347,7 +437,7 @@ public class UnifiedRegistrationService : ITransient
|
||||
}
|
||||
result.Details.AddRange(teamAssignmentResult.Details);
|
||||
|
||||
// 5. 验证报名限制
|
||||
// 7. 验证报名限制
|
||||
var limitCheckResult = await CheckRegistrationLimitAsync(input);
|
||||
if (limitCheckResult.HasLimit)
|
||||
{
|
||||
@ -1280,7 +1370,7 @@ public class UnifiedRegistrationService : ITransient
|
||||
totalPrice = unitPrice * personCount;
|
||||
break;
|
||||
case ChargeMode.UnitPriceProject:
|
||||
unitPrice = eventProject.Price / 100m;
|
||||
unitPrice = eventProject.Price;
|
||||
totalPrice = unitPrice * personCount;
|
||||
break;
|
||||
}
|
||||
@ -1323,6 +1413,65 @@ public class UnifiedRegistrationService : ITransient
|
||||
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);
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
var ep = eventProjects.FirstOrDefault(p => p.Id == item.ProjectId);
|
||||
var bp = baseProjects.FirstOrDefault(p => p.Id == ep?.ProjectId);
|
||||
|
||||
if (ep != null)
|
||||
{
|
||||
var (unitPrice, subtotal) = CalculateItemPrice(feeConfig, ep, item.PersonCount);
|
||||
result.Add((item.ProjectId, bp?.Name ?? "", 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>
|
||||
@ -1648,4 +1797,117 @@ public class UnifiedRegistrationService : ITransient
|
||||
|
||||
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(new ValidationDetail { ItemName = "费用配置验证", IsPassed = false, Message = "赛事费用配置不存在" });
|
||||
return result;
|
||||
}
|
||||
|
||||
// 验证费用配置值是否有效
|
||||
if (eventInfo.ChargeMode == ChargeMode.FixedPrice && feeConfig.Price <= 0)
|
||||
{
|
||||
result.IsValid = false;
|
||||
result.ErrorMessage = "赛事费用配置不正确,请联系管理员";
|
||||
result.Details.Add(new ValidationDetail { ItemName = "费用配置验证", IsPassed = false, Message = "定价模式费用配置不正确" });
|
||||
return result;
|
||||
}
|
||||
|
||||
result.Details.Add(new ValidationDetail { ItemName = "费用配置验证", IsPassed = true, Message = "费用配置验证通过" });
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Details.Add(new ValidationDetail { ItemName = "费用配置验证", IsPassed = true, Message = "免费赛事,无需费用配置" });
|
||||
}
|
||||
|
||||
// 2. 验证赛事项目是否存在
|
||||
var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == eventInfo.Id);
|
||||
if (!eventProjects.Any())
|
||||
{
|
||||
result.IsValid = false;
|
||||
result.ErrorMessage = "赛事未配置任何项目,请联系管理员";
|
||||
result.Details.Add(new ValidationDetail { ItemName = "项目配置验证", IsPassed = false, Message = "赛事项目配置不存在" });
|
||||
return result;
|
||||
}
|
||||
result.Details.Add(new ValidationDetail { ItemName = "项目配置验证", IsPassed = true, Message = $"赛事配置了{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(new ValidationDetail { ItemName = "商户配置验证", IsPassed = false, Message = "赛事关联的商户不存在" });
|
||||
return result;
|
||||
}
|
||||
result.Details.Add(new ValidationDetail { ItemName = "商户配置验证", IsPassed = true, Message = "商户配置验证通过" });
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果赛事没有关联商户,检查是否有平台级别的支付配置
|
||||
var platformPayConfig = await _weChatPayConfigRepository.GetFirstAsync(x => x.Status == AvailabilityStatus.Enable && !x.DeleteMark);
|
||||
if (platformPayConfig == null)
|
||||
{
|
||||
result.IsValid = false;
|
||||
result.ErrorMessage = "支付配置不存在,请联系管理员";
|
||||
result.Details.Add(new ValidationDetail { ItemName = "支付配置验证", IsPassed = false, Message = "平台支付配置不存在" });
|
||||
return result;
|
||||
}
|
||||
result.Details.Add(new ValidationDetail { ItemName = "支付配置验证", IsPassed = true, Message = "平台支付配置验证通过" });
|
||||
}
|
||||
}
|
||||
|
||||
result.Details.Add(new ValidationDetail { ItemName = "配置验证", IsPassed = true, Message = "所有报名相关配置验证通过" });
|
||||
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(new ValidationDetail { ItemName = "单位认证验证", IsPassed = false, Message = "用户未通过单位认证" });
|
||||
return result;
|
||||
}
|
||||
|
||||
result.Details.Add(new ValidationDetail { ItemName = "单位认证验证", IsPassed = true, Message = "用户单位认证验证通过" });
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@ -665,15 +665,22 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("========== 收到微信支付回调请求 ==========");
|
||||
|
||||
using var reader = new StreamReader(_httpContextAccessor.HttpContext.Request.Body);
|
||||
var body = await reader.ReadToEndAsync();
|
||||
|
||||
_logger.LogInformation("微信支付回调 - 请求Body: {Body}", body);
|
||||
|
||||
var headers = _httpContextAccessor.HttpContext.Request.Headers;
|
||||
var timestamp = headers["Wechatpay-Timestamp"].FirstOrDefault();
|
||||
var nonce = headers["Wechatpay-Nonce"].FirstOrDefault();
|
||||
var signature = headers["Wechatpay-Signature"].FirstOrDefault();
|
||||
var serialNumber = headers["Wechatpay-Serial"].FirstOrDefault();
|
||||
|
||||
_logger.LogInformation("微信支付回调 - 头部参数: Timestamp={Timestamp}, Nonce={Nonce}, SerialNumber={SerialNumber}",
|
||||
timestamp, nonce, serialNumber);
|
||||
|
||||
if (string.IsNullOrEmpty(timestamp) || string.IsNullOrEmpty(nonce) ||
|
||||
string.IsNullOrEmpty(signature) || string.IsNullOrEmpty(serialNumber))
|
||||
{
|
||||
@ -697,20 +704,35 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
|
||||
Body = body
|
||||
};
|
||||
|
||||
_logger.LogInformation("微信支付回调 - 开始验证签名");
|
||||
var isValid = await _weChatPayService.VerifyPayNotifyAsync(platformPayConfig, notifyRequest);
|
||||
if (!isValid)
|
||||
{
|
||||
_logger.LogWarning("微信支付回调签名验证失败");
|
||||
return new JsonResult(new { code = "FAIL", message = "签名验证失败" });
|
||||
}
|
||||
_logger.LogInformation("微信支付回调 - 签名验证成功");
|
||||
|
||||
_logger.LogInformation("微信支付回调 - 开始解密数据");
|
||||
var notifyData = await _weChatPayService.DecryptPayNotifyDataAsync(platformPayConfig, notifyRequest);
|
||||
if (notifyData == null || notifyData.TradeState != "SUCCESS")
|
||||
|
||||
if (notifyData == null)
|
||||
{
|
||||
_logger.LogWarning("微信支付回调数据无效或交易未成功");
|
||||
_logger.LogWarning("微信支付回调数据解密失败或为空");
|
||||
return new JsonResult(new { code = "SUCCESS", message = "成功" });
|
||||
}
|
||||
|
||||
_logger.LogInformation("微信支付回调 - 解密成功: OutTradeNo={OutTradeNo}, TradeState={TradeState}, TransactionId={TransactionId}",
|
||||
notifyData.OutTradeNo, notifyData.TradeState, notifyData.TransactionId);
|
||||
|
||||
if (notifyData.TradeState != "SUCCESS")
|
||||
{
|
||||
_logger.LogWarning("微信支付回调交易未成功,TradeState={TradeState}", notifyData.TradeState);
|
||||
return new JsonResult(new { code = "SUCCESS", message = "成功" });
|
||||
}
|
||||
|
||||
_logger.LogInformation("微信支付回调 - 开始处理支付成功逻辑,订单号:{OutTradeNo}", notifyData.OutTradeNo);
|
||||
|
||||
// 调用统一支付成功处理方法
|
||||
await _unifiedRegistrationService.HandlePaymentSuccessAsync(
|
||||
notifyData.OutTradeNo,
|
||||
@ -718,6 +740,9 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
|
||||
notifyData.SuccessTime?.DateTime,
|
||||
0);
|
||||
|
||||
_logger.LogInformation("微信支付回调 - 支付成功处理完成,订单号:{OutTradeNo}", notifyData.OutTradeNo);
|
||||
_logger.LogInformation("========== 微信支付回调处理完成 ==========");
|
||||
|
||||
return new JsonResult(new { code = "SUCCESS", message = "成功" });
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
@ -748,9 +773,20 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
|
||||
if (orderInfo == null || orderInfo.Order == null) throw Oops.Bah("订单不存在");
|
||||
|
||||
if (orderInfo.Order.OrderStatus == OrderStatus.Paid)
|
||||
{
|
||||
_logger.LogInformation("查询订单支付状态 - 订单已支付,订单号:{OrderNo}", orderNo);
|
||||
return 1; // 已支付
|
||||
}
|
||||
|
||||
if (orderInfo.Registration?.PaymentMethod == 1)
|
||||
// 检查是否是微信/支付宝支付(线上支付)
|
||||
bool isOnlinePayment = orderInfo.Registration?.PaymentMethod == 2 ||
|
||||
orderInfo.Registration?.PaymentMethodType == PaymentMethodType.WeChatPay ||
|
||||
orderInfo.Registration?.PaymentMethodType == PaymentMethodType.AliPay;
|
||||
|
||||
_logger.LogInformation("查询订单支付状态 - 订单号:{OrderNo},PaymentMethod={PaymentMethod},PaymentMethodType={PaymentMethodType},IsOnlinePayment={IsOnlinePayment}",
|
||||
orderNo, orderInfo.Registration?.PaymentMethod, orderInfo.Registration?.PaymentMethodType, isOnlinePayment);
|
||||
|
||||
if (isOnlinePayment)
|
||||
{
|
||||
WeChatPayConfig? payConfig = null;
|
||||
if (orderInfo.Order.MerchantId.HasValue)
|
||||
@ -769,7 +805,11 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
|
||||
|
||||
if (payConfig != null)
|
||||
{
|
||||
_logger.LogInformation("查询订单支付状态 - 调用微信支付查询接口,订单号:{OrderNo}", orderNo);
|
||||
var queryResult = await _weChatPayService.QueryOrderAsync(payConfig, orderNo);
|
||||
_logger.LogInformation("查询订单支付状态 - 微信支付查询结果,订单号:{OrderNo},Success={Success},TradeState={TradeState}",
|
||||
orderNo, queryResult.Success, queryResult.TradeState);
|
||||
|
||||
if (queryResult.Success && queryResult.TradeState == "SUCCESS")
|
||||
{
|
||||
orderInfo.Order.OrderStatus = OrderStatus.Paid;
|
||||
@ -786,9 +826,14 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
|
||||
registration.PaymentStatus = 1;
|
||||
await _registrationRepository.UpdateAsync(registration);
|
||||
}
|
||||
_logger.LogInformation("查询订单支付状态 - 订单已更新为已支付,订单号:{OrderNo}", orderNo);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("查询订单支付状态 - 未找到微信支付配置,订单号:{OrderNo}", orderNo);
|
||||
}
|
||||
}
|
||||
|
||||
return 0; // 未支付
|
||||
|
||||
@ -2209,7 +2209,17 @@
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Client.Dtos.PaymentInfoDto.Amount">
|
||||
<summary>
|
||||
金额
|
||||
实际金额
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Client.Dtos.PaymentInfoDto.OriginalAmount">
|
||||
<summary>
|
||||
原始金额
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Client.Dtos.PaymentInfoDto.DiscountAmount">
|
||||
<summary>
|
||||
优惠金额
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Client.Dtos.PaymentInfoDto.PaymentMethod">
|
||||
@ -2506,6 +2516,11 @@
|
||||
- 允许的字符:字母、数字、下划线、连字符
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Client.Dtos.GetPaymentInfoInput.CouponId">
|
||||
<summary>
|
||||
优惠券ID
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:YD_Event.Application.Client.Dtos.UploadMaterialInput">
|
||||
<summary>
|
||||
上传素材输入DTO
|
||||
@ -3329,6 +3344,11 @@
|
||||
<param name="input"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Client.EventClientController.ValidateCoupon(YD_Event.Application.Event.Dtos.ValidateCouponInput)">
|
||||
<summary>
|
||||
验证优惠券
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Client.EventClientController.GetPaymentInfo(YD_Event.Application.Client.Dtos.GetPaymentInfoInput)">
|
||||
<summary>
|
||||
获取支付信息
|
||||
@ -5677,6 +5697,11 @@
|
||||
是否为草稿
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UnifiedRegistrationInput.RegistrationChannel">
|
||||
<summary>
|
||||
报名渠道 (1-小程序 2-后台管理 3-其他)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:YD_Event.Application.Event.Dtos.UnifiedRegistrationItemInput">
|
||||
<summary>
|
||||
统一报名项目输入DTO
|
||||
@ -7454,6 +7479,19 @@
|
||||
计算总费用
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.CalculateRegistrationAmountAsync(System.Int64,System.Collections.Generic.List{YD_Event.Application.Event.UnifiedRegistrationService.CalculateAmountInputItem})">
|
||||
<summary>
|
||||
计算报名金额(对外公开方法)
|
||||
</summary>
|
||||
<param name="eventId">赛事ID</param>
|
||||
<param name="items">报名项目列表(包含人员数量信息)</param>
|
||||
<returns>计算结果</returns>
|
||||
</member>
|
||||
<member name="T:YD_Event.Application.Event.UnifiedRegistrationService.CalculateAmountInputItem">
|
||||
<summary>
|
||||
计算金额输入项(简化版)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.CalculateAge(System.DateTime)">
|
||||
<summary>
|
||||
根据出生日期计算年龄
|
||||
@ -7489,6 +7527,16 @@
|
||||
统一优惠券验证和使用方法
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.ValidateRegistrationConfigAsync(YD_Event.Core.Entities.EventInfo)">
|
||||
<summary>
|
||||
验证报名相关配置参数
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.ValidateUserCertificationAsync(System.Int64)">
|
||||
<summary>
|
||||
验证用户单位认证状态
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:YD_Event.Application.Event.UserEventRecordService">
|
||||
<summary>
|
||||
用户端参赛记录管理
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user