1. 从零开始集成微信支付V3 Native支付
微信支付V3的Native支付方式特别适合PC网站、线下扫码等场景,用户扫描二维码即可完成支付。最近我在一个电商项目中用C#实现了这个功能,整个过程比想象中简单很多,主要得益于SKIT.FlurlHttpClient.Wechat.TenpayV3这个优秀的第三方库。下面我就把完整的实现过程分享给大家。
先说说为什么选择这个库。官方SDK虽然功能完整,但配置复杂,而这个第三方库封装得更加友好,API设计符合C#开发者的习惯。我用下来最大的感受是:它把微信支付V3复杂的签名、加密流程都隐藏在了简洁的API后面,开发者只需要关注业务逻辑。
2. 环境准备与基础配置
2.1 项目初始化
首先创建一个ASP.NET Core Web API项目,建议使用.NET 6或更高版本。通过NuGet安装必要的包:
dotnet add package SKIT.FlurlHttpClient.Wechat.TenpayV3这个库会自动处理依赖关系,包括Flurl.Http等基础组件。我建议在项目中创建一个专门的配置类来管理微信支付参数,就像这样:
public static class WechatPayConfig { public static string AppId { get; } = "你的小程序/公众号AppId"; public static string MerchantId { get; } = "你的商户号"; public static string ApiV3Key { get; } = "APIv3密钥"; public static string MerchantSerialNumber { get; } = "商户证书序列号"; public static string PrivateKeyPath { get; } = "apiclient_key.pem的绝对路径"; public static string NotifyUrl { get; } = "https://yourdomain.com/api/wechatpay/notify"; }2.2 证书准备
微信支付V3使用双向认证,需要准备以下文件:
- apiclient_cert.pem(商户证书)
- apiclient_key.pem(商户私钥)
这两个文件需要从微信商户平台下载。我踩过的一个坑是:私钥文件必须保持原始格式,不要用文本编辑器随意修改。建议直接放在项目根目录的certs文件夹下,并通过.gitignore排除这些敏感文件。
3. 构建支付客户端
3.1 初始化支付客户端
创建一个单例的WechatTenpayClient实例非常重要,因为每次初始化都会加载证书,频繁创建会影响性能。我通常在Startup.cs中这样配置:
services.AddSingleton(provider => { var options = new WechatTenpayClientOptions { MerchantId = WechatPayConfig.MerchantId, MerchantV3Secret = WechatPayConfig.ApiV3Key, MerchantCertificateSerialNumber = WechatPayConfig.MerchantSerialNumber, MerchantCertificatePrivateKey = File.ReadAllText(WechatPayConfig.PrivateKeyPath) }; return WechatTenpayClientBuilder.Create(options).Build(); });3.2 处理平台证书
微信支付V3要求验证平台证书,这里有个小技巧:使用内存证书管理器可以简化流程:
var manager = new InMemoryCertificateManager(); options.PlatformCertificateManager = manager;实际项目中,你可能需要实现自动更新平台证书的逻辑。我在项目中使用了一个后台服务定期检查并更新证书。
4. 实现Native下单接口
4.1 构建请求参数
Native支付的核心是创建一个预支付订单并获取支付二维码。这是我的实现代码:
public async Task<string> CreateNativeOrderAsync(string orderId, string description, int totalFee) { var request = new CreatePayTransactionNativeRequest { OutTradeNumber = orderId, Description = description, Amount = new CreatePayTransactionNativeRequest.Types.Amount { Total = totalFee // 单位是分 }, NotifyUrl = WechatPayConfig.NotifyUrl }; var response = await _client.ExecuteCreatePayTransactionNativeAsync(request); if (!response.IsSuccessful()) { _logger.LogError($"微信支付下单失败:{response.ErrorCode} - {response.ErrorMessage}"); throw new WechatPayException(response.ErrorMessage); } return response.PrepayId; }几个关键点需要注意:
- OutTradeNumber必须是唯一的,我通常使用业务订单ID
- Total金额单位是分,不是元
- NotifyUrl是支付结果通知地址,必须外网可访问
4.2 生成支付二维码
拿到prepay_id后,前端可以通过以下URL生成二维码:
weixin://wxpay/bizpayurl?pr=你的prepay_id在实际项目中,我通常会把这个URL和订单信息一起返回给前端:
return Ok(new { qrCodeUrl = $"weixin://wxpay/bizpayurl?pr={prepayId}", orderId = orderId, expiresAt = DateTime.Now.AddMinutes(30) });5. 支付结果通知处理
5.1 配置通知接口
支付成功后,微信会回调你设置的NotifyUrl。这个接口需要处理验签和业务逻辑:
[HttpPost("notify")] public async Task<IActionResult> HandleNotify() { try { var notification = await _client.DeserializeEventAsync(Request.Body); if (notification.EventType == "TRANSACTION.SUCCESS") { var resource = notification.Resource; // 处理支付成功逻辑 await _orderService.CompleteOrderAsync(resource.OutTradeNumber); return Ok(); } } catch (Exception ex) { _logger.LogError(ex, "处理微信支付通知失败"); } return BadRequest(); }5.2 处理重复通知
微信可能会多次发送相同的通知,所以你的接口需要实现幂等性处理。我的做法是在数据库中记录已处理的通知ID:
if (await _paymentRepository.ExistsNotificationAsync(notification.Id)) { return Ok(); // 已经处理过,直接返回成功 }6. 常见问题与调试技巧
6.1 签名验证失败
这是最常见的问题,通常有几个原因:
- 证书序列号配置错误
- APIv3密钥不匹配
- 时间戳差异过大
我建议在开发阶段开启调试日志:
FlurlHttp.Clients.WithDefaults(builder => builder.ConfigureHttpClient(client => client.HttpClientFactory = new DebugLoggingHttpClientFactory()));6.2 订单状态查询
有时候支付结果通知会有延迟,可以通过主动查询确认状态:
public async Task<PayTransaction> QueryOrderAsync(string orderId) { var request = new GetPayTransactionByOutTradeNumberRequest { OutTradeNumber = orderId }; var response = await _client.ExecuteGetPayTransactionByOutTradeNumberAsync(request); return response.Transaction; }这个方法在用户支付后主动查询订单状态时特别有用。
6.3 处理退款
退款流程与支付类似,但需要注意证书的使用:
public async Task RefundAsync(string orderId, string refundId, int amount) { var request = new CreateRefundDomesticRefundRequest { OutTradeNumber = orderId, OutRefundNumber = refundId, Amount = new CreateRefundDomesticRefundRequest.Types.Amount { Refund = amount, Total = amount // 这里应该是订单总金额 } }; await _client.ExecuteCreateRefundDomesticRefundAsync(request); }7. 最佳实践与性能优化
7.1 客户端生命周期管理
WechatTenpayClient是线程安全的,建议作为单例使用。但在高并发场景下,你可能需要调整默认的HttpClient配置:
services.AddHttpClient<WechatTenpayClient>(client => { client.Timeout = TimeSpan.FromSeconds(10); });7.2 错误处理策略
微信支付API可能会因为网络问题失败,建议实现重试机制:
var policy = Policy<WechatTenpayResponse> .HandleResult(r => !r.IsSuccessful()) .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); await policy.ExecuteAsync(() => _client.ExecuteCreatePayTransactionNativeAsync(request));7.3 日志记录
完善的日志对排查问题至关重要。我通常会记录完整的请求和响应:
client.Configure(settings => { settings.BeforeCall = call => { _logger.LogDebug($"请求:{call.Request.Url}"); }; settings.AfterCall = call => { _logger.LogDebug($"响应:{call.Response.StatusCode}"); }; });8. 安全注意事项
8.1 敏感信息保护
永远不要在客户端存储或传输商户证书和私钥。在我的项目中,这些敏感信息只存在于服务器环境变量中。
8.2 请求验证
所有支付通知和回调都应该验证签名:
if (!_client.VerifyEventSignature( Request.Headers["Wechatpay-Signature"], Request.Headers["Wechatpay-Nonce"], Request.Headers["Wechatpay-Timestamp"], Request.Body)) { return BadRequest("签名验证失败"); }8.3 防重复支付
业务系统应该检查订单状态,防止用户重复支付:
var order = await _orderRepository.GetAsync(orderId); if (order.Status == OrderStatus.Paid) { throw new InvalidOperationException("订单已支付"); }在实际项目中,我还会使用数据库事务确保支付和订单状态更新的原子性。