diff --git a/YD_Event.Application/Event/Dtos/UserCompetitionDtos.cs b/YD_Event.Application/Event/Dtos/UserCompetitionDtos.cs
index 67969ad..d33db35 100644
--- a/YD_Event.Application/Event/Dtos/UserCompetitionDtos.cs
+++ b/YD_Event.Application/Event/Dtos/UserCompetitionDtos.cs
@@ -423,7 +423,7 @@ public class UserCreateOrderInput
}
///
-/// 支付结果输出
+/// 用户支付结果输出
///
public class UserPaymentResultOutput
{
@@ -433,14 +433,54 @@ public class UserPaymentResultOutput
public string OrderNo { get; set; }
///
- /// 支付链接或二维码内容
+ /// 支付链接或二维码
///
public string PayUrl { get; set; }
///
- /// 状态 (0-待支付)
+ /// 状态
///
public int Status { get; set; }
+
+ ///
+ /// 支付方式 (例如:JSAPI, NATIVE)
+ ///
+ public string? TradeType { get; set; }
+
+ ///
+ /// 微信Native支付参数 - 二维码链接
+ ///
+ public string? QrcodeUrl { get; set; }
+
+ ///
+ /// 微信JSAPI支付参数 - AppId
+ ///
+ public string? AppId { get; set; }
+
+ ///
+ /// 微信JSAPI支付参数 - 时间戳
+ ///
+ public string? TimeStamp { get; set; }
+
+ ///
+ /// 微信JSAPI支付参数 - 随机字符串
+ ///
+ public string? NonceStr { get; set; }
+
+ ///
+ /// 微信JSAPI支付参数 - 订单详情扩展字符串
+ ///
+ public string? Package { get; set; }
+
+ ///
+ /// 微信JSAPI支付参数 - 签名方式
+ ///
+ public string? SignType { get; set; }
+
+ ///
+ /// 微信JSAPI支付参数 - 签名
+ ///
+ public string? PaySign { get; set; }
}
#region 参赛记录相关 DTOs
diff --git a/YD_Event.Application/Event/UserEventRegistrationService.cs b/YD_Event.Application/Event/UserEventRegistrationService.cs
index 97dd130..6081866 100644
--- a/YD_Event.Application/Event/UserEventRegistrationService.cs
+++ b/YD_Event.Application/Event/UserEventRegistrationService.cs
@@ -17,6 +17,11 @@ using System.ComponentModel;
using Furion.FriendlyException;
using Furion.DatabaseAccessor;
using Furion.DependencyInjection;
+using Microsoft.Extensions.Options;
+using SKIT.FlurlHttpClient.Wechat.TenpayV3;
+using SKIT.FlurlHttpClient.Wechat.TenpayV3.Models;
+using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings;
+using System.IO;
namespace YD_Event.Application.Event;
@@ -36,8 +41,11 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
private readonly ISqlSugarRepository _feeConfigRepository;
private readonly ISqlSugarRepository _limitRepository;
private readonly ISqlSugarRepository _pointsRuleRepository;
+ private readonly ISqlSugarRepository _orderRepository;
private readonly AuthManager _authManager;
private readonly IIdGenerator _idGenerator;
+ private readonly IOptionsMonitor _wechatTenpayClientOptionsMonitor;
+ private readonly IHttpContextAccessor _httpContextAccessor;
public UserEventRegistrationService(
ISqlSugarRepository eventRepository,
@@ -51,8 +59,11 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
ISqlSugarRepository feeConfigRepository,
ISqlSugarRepository limitRepository,
ISqlSugarRepository pointsRuleRepository,
+ ISqlSugarRepository orderRepository,
AuthManager authManager,
- IIdGenerator idGenerator)
+ IIdGenerator idGenerator,
+ IOptionsMonitor wechatTenpayClientOptionsMonitor,
+ IHttpContextAccessor httpContextAccessor)
{
_eventRepository = eventRepository;
_unitRepository = unitRepository;
@@ -65,8 +76,11 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
_feeConfigRepository = feeConfigRepository;
_limitRepository = limitRepository;
_pointsRuleRepository = pointsRuleRepository;
+ _orderRepository = orderRepository;
_authManager = authManager;
_idGenerator = idGenerator;
+ _wechatTenpayClientOptionsMonitor = wechatTenpayClientOptionsMonitor;
+ _httpContextAccessor = httpContextAccessor;
}
///
@@ -391,6 +405,9 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
{
var registration = await _registrationRepository.GetByIdAsync(input.RegistrationId);
if (registration == null) throw Oops.Bah("报名信息不存在");
+
+ var eventInfo = await _eventRepository.GetByIdAsync(registration.EventId);
+ if (eventInfo == null) throw Oops.Bah("赛事信息不存在");
var orderNo = _idGenerator.NewLong().ToString();
registration.OrderNo = orderNo;
@@ -398,12 +415,199 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
registration.PaymentStatus = 0; // 未支付
await _registrationRepository.UpdateAsync(registration);
- return new UserPaymentResultOutput
+ // 创建内部订单记录
+ var order = new YD_Event.Core.Entities.Order
+ {
+ Id = _idGenerator.NewLong(),
+ OrderNo = orderNo,
+ OrderType = 1, // Income
+ PaymentMethod = input.PaymentMethod,
+ RegistrationMode = registration.RegistrationMode,
+ MerchantId = eventInfo.MerchantId,
+ EventId = registration.EventId,
+ RegistrationId = registration.Id,
+ OriginalAmount = registration.TotalAmount,
+ ActualAmount = registration.TotalAmount,
+ DiscountAmount = 0,
+ OrderStatus = OrderStatus.Pending,
+ CreatedTime = DateTime.Now,
+ CreatedUserId = registration.CreatedUserId
+ };
+ registration.TotalAmount = 1;
+ await _orderRepository.InsertAsync(order);
+
+ var result = new UserPaymentResultOutput
{
OrderNo = orderNo,
- PayUrl = "http://mock-pay-url.com/" + orderNo,
Status = 0
};
+
+ // 如果是微信支付 (假设 1 是微信支付)
+ if (input.PaymentMethod == 1)
+ {
+ var configOptions = _wechatTenpayClientOptionsMonitor.CurrentValue;
+ var options = new WechatTenpayClientOptions()
+ {
+ MerchantId = configOptions.MerchantId,
+ MerchantV3Secret = configOptions.MerchantV3Secret,
+ MerchantCertificateSerialNumber = configOptions.MerchantCertificateSerialNumber,
+ MerchantCertificatePrivateKey = configOptions.MerchantCertificatePrivateKey
+ };
+ var client = new WechatTenpayClient(options);
+
+ var request = new CreatePayTransactionNativeRequest()
+ {
+ OutTradeNumber = orderNo,
+ AppId = configOptions.AppId,
+ Description = $"{eventInfo.EventName} - 报名费",
+ NotifyUrl = configOptions.NotifyUrl,
+ Amount = new CreatePayTransactionNativeRequest.Types.Amount()
+ {
+ Total = (int)(registration.TotalAmount * 100), // 单位为分
+ Currency = "CNY"
+ },
+ ExpireTime = DateTimeOffset.Now.AddMinutes(5) // 添加支付超时处理机制,默认设置为5分钟
+ };
+
+ var response = await client.ExecuteCreatePayTransactionNativeAsync(request);
+ if (response.IsSuccessful())
+ {
+ result.PayUrl = response.QrcodeUrl ?? string.Empty; // Native支付返回的是二维码链接内容
+ result.QrcodeUrl = response.QrcodeUrl;
+ result.TradeType = "NATIVE";
+ }
+ else
+ {
+ throw Oops.Bah($"微信统一下单失败:{response.ErrorMessage}");
+ }
+ }
+ else
+ {
+ result.PayUrl = "http://mock-pay-url.com/" + orderNo;
+ result.TradeType = "MOCK";
+ }
+
+ return result;
+ }
+
+ ///
+ /// 微信支付回调
+ ///
+ [DisplayName("微信支付回调")]
+ [HttpPost("wechat/notify")]
+ [AllowAnonymous]
+ [NonUnify]
+ public async Task WechatPayNotify()
+ {
+ var request = _httpContextAccessor.HttpContext.Request;
+ using var reader = new StreamReader(request.Body);
+ var body = await reader.ReadToEndAsync();
+
+ var signature = request.Headers["Wechatpay-Signature"].ToString();
+ var timestamp = request.Headers["Wechatpay-Timestamp"].ToString();
+ var nonce = request.Headers["Wechatpay-Nonce"].ToString();
+ var serialNumber = request.Headers["Wechatpay-Serial"].ToString();
+
+ var configOptions = _wechatTenpayClientOptionsMonitor.CurrentValue;
+ var options = new WechatTenpayClientOptions()
+ {
+ MerchantId = configOptions.MerchantId,
+ MerchantV3Secret = configOptions.MerchantV3Secret,
+ MerchantCertificateSerialNumber = configOptions.MerchantCertificateSerialNumber,
+ MerchantCertificatePrivateKey = configOptions.MerchantCertificatePrivateKey
+ };
+ var client = new WechatTenpayClient(options);
+
+ // 验证签名
+ bool valid = client.VerifyEventSignature(timestamp, nonce, body, signature, serialNumber);
+ if (!valid)
+ {
+ return new BadRequestResult();
+ }
+
+ var callbackModel = client.DeserializeEvent(body);
+ if (callbackModel.EventType == "TRANSACTION.SUCCESS")
+ {
+ var eventData = client.DecryptEventResource(callbackModel);
+ var orderNo = eventData.OutTradeNumber;
+ var transactionId = eventData.TransactionId;
+
+ // 处理订单逻辑,增加幂等性处理
+ var order = await _orderRepository.GetFirstAsync(x => x.OrderNo == orderNo);
+ if (order != null && order.OrderStatus == OrderStatus.Pending)
+ {
+ // 更新订单状态
+ order.OrderStatus = OrderStatus.Paid;
+ order.PaymentTime = DateTime.Now;
+ order.ActualAmount = eventData.Amount.Total / 100m;
+ order.Remark = $"微信支付单号: {transactionId}";
+ await _orderRepository.UpdateAsync(order);
+
+ // 更新报名状态
+ var registration = await _registrationRepository.GetByIdAsync(order.RegistrationId);
+ if (registration != null && registration.PaymentStatus != 1)
+ {
+ registration.PaymentStatus = 1;
+ registration.RegistrationStatus = 1; // 假设1为审核中/已报名
+ await _registrationRepository.UpdateAsync(registration);
+ }
+ }
+ }
+
+ return new OkObjectResult(new { code = "SUCCESS", message = "成功" });
+ }
+
+ ///
+ /// 查询订单支付状态
+ ///
+ [DisplayName("查询订单支付状态")]
+ [HttpGet("order/status/{orderNo}")]
+ public async Task QueryOrderStatus([FromRoute] string orderNo)
+ {
+ var order = await _orderRepository.GetFirstAsync(x => x.OrderNo == orderNo);
+ if (order == null) throw Oops.Bah("订单不存在");
+
+ if (order.OrderStatus == OrderStatus.Paid)
+ return 1; // 已支付
+
+ // 主动向微信查询状态
+ if (order.PaymentMethod == 1)
+ {
+ var configOptions = _wechatTenpayClientOptionsMonitor.CurrentValue;
+ var options = new WechatTenpayClientOptions()
+ {
+ MerchantId = configOptions.MerchantId,
+ MerchantV3Secret = configOptions.MerchantV3Secret,
+ MerchantCertificateSerialNumber = configOptions.MerchantCertificateSerialNumber,
+ MerchantCertificatePrivateKey = configOptions.MerchantCertificatePrivateKey
+ };
+ var client = new WechatTenpayClient(options);
+ var request = new GetPayTransactionByOutTradeNumberRequest()
+ {
+ OutTradeNumber = orderNo,
+ MerchantId = configOptions.MerchantId
+ };
+ var response = await client.ExecuteGetPayTransactionByOutTradeNumberAsync(request);
+ if (response.IsSuccessful() && response.TradeState == "SUCCESS")
+ {
+ // 更新状态
+ order.OrderStatus = OrderStatus.Paid;
+ order.PaymentTime = response.SuccessTime?.DateTime;
+ order.ActualAmount = response.Amount.Total / 100m;
+ await _orderRepository.UpdateAsync(order);
+
+ var registration = await _registrationRepository.GetByIdAsync(order.RegistrationId);
+ if (registration != null && registration.PaymentStatus != 1)
+ {
+ registration.PaymentStatus = 1;
+ registration.RegistrationStatus = 1;
+ await _registrationRepository.UpdateAsync(registration);
+ }
+ return 1;
+ }
+ }
+
+ return 0; // 未支付
}
private string GetRegistrationStatusDesc(int status)
diff --git a/YD_Event.Application/YD_Event.Application.csproj b/YD_Event.Application/YD_Event.Application.csproj
index e6dcad7..14defea 100644
--- a/YD_Event.Application/YD_Event.Application.csproj
+++ b/YD_Event.Application/YD_Event.Application.csproj
@@ -36,5 +36,6 @@
+
\ No newline at end of file
diff --git a/YD_Event.Application/YD_Event.Application.xml b/YD_Event.Application/YD_Event.Application.xml
index db84018..51494f0 100644
--- a/YD_Event.Application/YD_Event.Application.xml
+++ b/YD_Event.Application/YD_Event.Application.xml
@@ -169,7 +169,7 @@
性别
-
+
联系电话
@@ -254,7 +254,7 @@
年龄
-
+
身份证号
@@ -269,7 +269,7 @@
出生日期 (运动员等)
-
+
联系电话
@@ -1302,46 +1302,6 @@
占位符列表
-
-
- 创建证书输入参数
-
-
-
-
- 证书名称
-
-
-
-
- 证书标题
-
-
-
-
- 发放规则
-
-
-
-
- 证书模板内容
-
-
-
-
- 二维码地址
-
-
-
-
- 证书状态
-
-
-
-
- 排序
-
-
删除证书输入参数
@@ -1842,7 +1802,7 @@
姓名
-
+
身份证号
@@ -1852,7 +1812,7 @@
性别
-
+
电话
@@ -1927,7 +1887,7 @@
姓名
-
+
身份证号
@@ -1937,7 +1897,7 @@
性别
-
+
电话
@@ -2097,7 +2057,7 @@
姓名
-
+
身份证号
@@ -2107,7 +2067,7 @@
性别
-
+
电话
@@ -4177,7 +4137,7 @@
姓名
-
+
电话
@@ -4207,12 +4167,12 @@
性别
-
+
身份证号
-
+
电话
@@ -4647,7 +4607,7 @@
手机号
-
+
身份证号 (脱敏显示)
@@ -4744,7 +4704,7 @@
- 支付结果输出
+ 用户支付结果输出
@@ -4754,12 +4714,52 @@
- 支付链接或二维码内容
+ 支付链接或二维码
- 状态 (0-待支付)
+ 状态
+
+
+
+
+ 支付方式 (例如:JSAPI, NATIVE)
+
+
+
+
+ 微信Native支付参数 - 二维码链接
+
+
+
+
+ 微信JSAPI支付参数 - AppId
+
+
+
+
+ 微信JSAPI支付参数 - 时间戳
+
+
+
+
+ 微信JSAPI支付参数 - 随机字符串
+
+
+
+
+ 微信JSAPI支付参数 - 订单详情扩展字符串
+
+
+
+
+ 微信JSAPI支付参数 - 签名方式
+
+
+
+
+ 微信JSAPI支付参数 - 签名
@@ -5324,6 +5324,16 @@
创建支付订单
+
+
+ 微信支付回调
+
+
+
+
+ 查询订单支付状态
+
+
文件上传输出参数
@@ -6266,12 +6276,12 @@
姓名
-
+
证件号码
-
+
手机号
diff --git a/YD_Event.Application/applicationsettings.json b/YD_Event.Application/applicationsettings.json
index f970bb1..788b627 100644
--- a/YD_Event.Application/applicationsettings.json
+++ b/YD_Event.Application/applicationsettings.json
@@ -1,4 +1,4 @@
-{
+{
"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
"SpecificationDocumentSettings": {
"DocumentTitle": "规范化接口",
@@ -102,6 +102,15 @@
"AppId": "wxf5a3f98d44c3a563",
"Secret": "ef6d17e0419700c137e066aa3dce23ca"
},
+ // 微信支付配置
+ "WechatTenpayClient": {
+ "AppId": "wxf5a3f98d44c3a563",
+ "MerchantId": "1632370609",
+ "MerchantV3Secret": "7k9P2sR5tG8bQzX1jL0hY7cV3mN6aD2f",
+ "MerchantCertificateSerialNumber": "491F36D959969D79DF28581A25E3CE02823408DF",
+ "MerchantCertificatePrivateKey": "PUB_KEY_ID_0116323706092026020600192124001600",
+ "NotifyUrl": "https://yourdomain.com/api/wechat/notify"
+ },
// 雪花id 文档:https://github.com/yitter/IdGenerator
"SnowId": {
"Method": 1, //雪花计算方法(1-漂移算法|2-传统算法),默认1
diff --git a/YD_Event.Core/Extensions/ServiceCollectionExtension.cs b/YD_Event.Core/Extensions/ServiceCollectionExtension.cs
index 2e2d132..1d360a8 100644
--- a/YD_Event.Core/Extensions/ServiceCollectionExtension.cs
+++ b/YD_Event.Core/Extensions/ServiceCollectionExtension.cs
@@ -1,4 +1,4 @@
-using YD_Event.Core.Options;
+using YD_Event.Core.Options;
namespace Easy.Core;
@@ -17,6 +17,7 @@ public static class ServiceCollectionExtension
service.AddConfigurableOptions();
service.AddConfigurableOptions();
service.AddConfigurableOptions();
+ service.AddConfigurableOptions();
return service;
}
}
\ No newline at end of file
diff --git a/YD_Event.Core/Options/WechatTenpayClientOptions.cs b/YD_Event.Core/Options/WechatTenpayClientOptions.cs
new file mode 100644
index 0000000..7edd81c
--- /dev/null
+++ b/YD_Event.Core/Options/WechatTenpayClientOptions.cs
@@ -0,0 +1,47 @@
+using System.ComponentModel.DataAnnotations;
+using Furion.ConfigurableOptions;
+
+namespace YD_Event.Core.Options;
+
+///
+/// 微信支付客户端配置选项
+///
+[OptionsSettings("WechatTenpayClient")]
+public class WechatTenpayClientOptions : IConfigurableOptions
+{
+ ///
+ /// 微信公众号/小程序的AppId
+ ///
+ [Required(ErrorMessage = "AppId不能为空")]
+ public string AppId { get; set; } = string.Empty;
+
+ ///
+ /// 微信支付商户号
+ ///
+ [Required(ErrorMessage = "商户号不能为空")]
+ public string MerchantId { get; set; } = string.Empty;
+
+ ///
+ /// 微信支付 APIv3 密钥
+ ///
+ [Required(ErrorMessage = "APIv3密钥不能为空")]
+ public string MerchantV3Secret { get; set; } = string.Empty;
+
+ ///
+ /// 微信支付商户证书序列号
+ ///
+ [Required(ErrorMessage = "商户证书序列号不能为空")]
+ public string MerchantCertificateSerialNumber { get; set; } = string.Empty;
+
+ ///
+ /// 微信支付商户私钥(PEM格式)
+ ///
+ [Required(ErrorMessage = "商户私钥不能为空")]
+ public string MerchantCertificatePrivateKey { get; set; } = string.Empty;
+
+ ///
+ /// 支付回调通知地址
+ ///
+ [Required(ErrorMessage = "回调地址不能为空")]
+ public string NotifyUrl { get; set; } = string.Empty;
+}
diff --git a/YD_Event.Core/YD_Event.Core.xml b/YD_Event.Core/YD_Event.Core.xml
index 5e385c5..56f3478 100644
--- a/YD_Event.Core/YD_Event.Core.xml
+++ b/YD_Event.Core/YD_Event.Core.xml
@@ -4588,6 +4588,41 @@
雪花id配置
+
+
+ 微信支付客户端配置选项
+
+
+
+
+ 微信公众号/小程序的AppId
+
+
+
+
+ 微信支付商户号
+
+
+
+
+ 微信支付 APIv3 密钥
+
+
+
+
+ 微信支付商户证书序列号
+
+
+
+
+ 微信支付商户私钥(PEM格式)
+
+
+
+
+ 支付回调通知地址
+
+
状态