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 @@ 统一优惠券验证和使用方法 + + + 验证报名相关配置参数 + + + + + 验证用户单位认证状态 + + 用户端参赛记录管理