diff --git a/YD_Event.Application/Client/Dtos/EventClientDtos.cs b/YD_Event.Application/Client/Dtos/EventClientDtos.cs
index 4b6b75c..274445c 100644
--- a/YD_Event.Application/Client/Dtos/EventClientDtos.cs
+++ b/YD_Event.Application/Client/Dtos/EventClientDtos.cs
@@ -641,10 +641,20 @@ public class PaymentInfoDto
public long RegistrationId { get; set; }
///
- /// 金额
+ /// 实际金额
///
public decimal Amount { get; set; }
+ ///
+ /// 原始金额
+ ///
+ public decimal OriginalAmount { get; set; }
+
+ ///
+ /// 优惠金额
+ ///
+ public decimal DiscountAmount { get; set; }
+
///
/// 支付方式 (1-线下 2-线上)
///
@@ -959,6 +969,11 @@ public class GetPaymentInfoInput
[MaxLength(50, ErrorMessage = "OpenId长度不能超过50个字符")]
[RegularExpression(@"^[a-zA-Z0-9_-]+$", ErrorMessage = "OpenId格式不正确,只能包含字母、数字、下划线和连字符")]
public string OpenId { get; set; }
+
+ ///
+ /// 优惠券ID
+ ///
+ public long? CouponId { get; set; }
}
///
diff --git a/YD_Event.Application/Client/EventClientController.cs b/YD_Event.Application/Client/EventClientController.cs
index 62e531e..ac49a37 100644
--- a/YD_Event.Application/Client/EventClientController.cs
+++ b/YD_Event.Application/Client/EventClientController.cs
@@ -499,105 +499,28 @@ public class EventClientController : IDynamicApiController
[DisplayName("计算报名金额")]
public async Task 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()
+ 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;
}
+ ///
+ /// 验证优惠券
+ ///
+ [HttpPost("coupon/validate")]
+ [DisplayName("验证优惠券")]
+ public async Task ValidateCoupon([FromBody] ValidateCouponInput input)
+ {
+ var userId = GetCurrentUserId();
+ return await _unifiedRegistrationService.ValidateAndUseCouponAsync(input, userId);
+ }
+
///
/// 获取支付信息
///
@@ -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,
diff --git a/YD_Event.Application/Event/Dtos/UnifiedRegistrationDtos.cs b/YD_Event.Application/Event/Dtos/UnifiedRegistrationDtos.cs
index f0eb42e..c39ccb0 100644
--- a/YD_Event.Application/Event/Dtos/UnifiedRegistrationDtos.cs
+++ b/YD_Event.Application/Event/Dtos/UnifiedRegistrationDtos.cs
@@ -57,6 +57,11 @@ public class UnifiedRegistrationInput
/// 是否为草稿
///
public bool IsDraft { get; set; }
+
+ ///
+ /// 报名渠道 (1-小程序 2-后台管理 3-其他)
+ ///
+ public int RegistrationChannel { get; set; } = 1;
}
///
diff --git a/YD_Event.Application/Event/UnifiedRegistrationService.cs b/YD_Event.Application/Event/UnifiedRegistrationService.cs
index 4a4a295..e9104c3 100644
--- a/YD_Event.Application/Event/UnifiedRegistrationService.cs
+++ b/YD_Event.Application/Event/UnifiedRegistrationService.cs
@@ -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;
}
+ ///
+ /// 计算报名金额(对外公开方法)
+ ///
+ /// 赛事ID
+ /// 报名项目列表(包含人员数量信息)
+ /// 计算结果
+ public async Task<(decimal totalAmount, ChargeMode chargeMode, List<(long projectId, string projectName, decimal unitPrice, int personCount, decimal subtotal)> itemDetails)> CalculateRegistrationAmountAsync(
+ long eventId, List 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(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);
+ }
+
+ ///
+ /// 计算金额输入项(简化版)
+ ///
+ public class CalculateAmountInputItem
+ {
+ public long ProjectId { get; set; }
+ public int PersonCount { get; set; }
+ }
+
///
/// 根据出生日期计算年龄
///
@@ -1648,4 +1797,117 @@ public class UnifiedRegistrationService : ITransient
return result;
}
+
+ ///
+ /// 验证报名相关配置参数
+ ///
+ private async Task ValidateRegistrationConfigAsync(EventInfo eventInfo)
+ {
+ var result = new RegistrationValidationResult
+ {
+ IsValid = true,
+ Details = new List()
+ };
+
+ // 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;
+ }
+
+ ///
+ /// 验证用户单位认证状态
+ ///
+ private async Task ValidateUserCertificationAsync(long userId)
+ {
+ var result = new RegistrationValidationResult
+ {
+ IsValid = true,
+ Details = new List()
+ };
+
+ // 验证用户是否通过了单位认证
+ 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;
+ }
}
diff --git a/YD_Event.Application/Event/UserEventRegistrationService.cs b/YD_Event.Application/Event/UserEventRegistrationService.cs
index 79a1975..ba226c6 100644
--- a/YD_Event.Application/Event/UserEventRegistrationService.cs
+++ b/YD_Event.Application/Event/UserEventRegistrationService.cs
@@ -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; // 未支付
diff --git a/YD_Event.Application/YD_Event.Application.xml b/YD_Event.Application/YD_Event.Application.xml
index 1b4bfbd..4746e16 100644
--- a/YD_Event.Application/YD_Event.Application.xml
+++ b/YD_Event.Application/YD_Event.Application.xml
@@ -2209,7 +2209,17 @@
- 金额
+ 实际金额
+
+
+
+
+ 原始金额
+
+
+
+
+ 优惠金额
@@ -2506,6 +2516,11 @@
- 允许的字符:字母、数字、下划线、连字符
+
+
+ 优惠券ID
+
+
上传素材输入DTO
@@ -3329,6 +3344,11 @@
+
+
+ 验证优惠券
+
+
获取支付信息
@@ -5677,6 +5697,11 @@
是否为草稿
+
+
+ 报名渠道 (1-小程序 2-后台管理 3-其他)
+
+
统一报名项目输入DTO
@@ -7454,6 +7479,19 @@
计算总费用
+
+
+ 计算报名金额(对外公开方法)
+
+ 赛事ID
+ 报名项目列表(包含人员数量信息)
+ 计算结果
+
+
+
+ 计算金额输入项(简化版)
+
+
根据出生日期计算年龄
@@ -7489,6 +7527,16 @@
统一优惠券验证和使用方法
+
+
+ 验证报名相关配置参数
+
+
+
+
+ 验证用户单位认证状态
+
+
用户端参赛记录管理