使用C#版第三方微信SDK集成微信支付 概述 最近在工作中要开发一个微信支付的功能,项目比较急,第一反应就是去官方文档 找SDK,发现官方只提供了JAVA、PHP、Go的SDK。(对C#玩家真不友好!=_=!)。好在微信支付的开发者社区有位大哥fudiwei (RHQYZ) (github.com) 提供了C#版的SDK,项目地址:DotNetCore.SKIT.FlurlHttpClient.Wechat ,果断Install一下。
SDK简介 SKIT.FlurlHttpClient.Wechat是基于 Flurl.Http
的微信 HTTP API SDK,目前已包含公众平台、开放平台、商户平台、企业微信、广告平台、对话开放平台等模块。
看了一下文档,已经把微信已知的API都封装了,而且更新的还比较快,github上作者Issues回复也很快,还是比较稳定,完全够用啦。
接入使用 我这里只用到了JSAPI支付,其他的需求可查看项目文档,都有详细介绍。
准备工作
申请商户号
开启V3 api权限
下载密钥和序列号
配置文件 我是新建了一个叫tenpay_setting.json的配置文件,也可以直接加到appsetting.json
1 2 3 4 5 6 7 8 9 10 11 12 13 { "Tenpay" : { "Merchants" : [ { "MerchantId" : "********" , "SecretV3" : "*********" , "CertificateSerialNumber" : "*********" , "CertificatePrivateKey" : "apiclient_key.pem" } ] , "NotifyUrl" : "http://www.host.com/api/notify/wx/{merchant_id}/pay" } }
请求客户端创建 项目文档上是在多租户的情况下使用factory创建,我这里没有分租户。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public WechatTenpayClient CreateClient (string merchantId ) { var tenpayMerchantConfig = GetDefaultMerchant(merchantId); var options = new WechatTenpayClientOptions() { MerchantId = tenpayMerchantConfig.MerchantId, MerchantV3Secret = tenpayMerchantConfig.SecretV3, MerchantCertificateSerialNumber = tenpayMerchantConfig.CertificateSerialNumber, MerchantCertificatePrivateKey = GetCertPrivateKey(tenpayMerchantConfig.CertificatePrivateKey), PlatformCertificateManager = _redisCertificateManager, AutoEncryptRequestSensitiveProperty = true , AutoDecryptResponseSensitiveProperty = true }; var wechatTenpayClient = WechatTenpayClientBuilder.Create(options).Build(); return wechatTenpayClient; }
读取私钥内容 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 private static string GetCertPrivateKey (string path ) { try { var certPath = path; var certPrivateKey = File.ReadAllText(certPath); return certPrivateKey; } catch (Exception ex) { throw new BizException($"获取证书内容失败!:{ex.Message} " ); } }
注意,新版本中不需要处理-----BEGIN PRIVATE KEY-----
和-----END PRIVATE KEY-----
,否则会出现Private key format is not supported.
自动刷新证书 项目simple中是使用内存管理证书,我这项目需要其他微服务使用,就用redis缓存管理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 public class RedisCertificateManager : ICertificateManager , ITransient { private const string REDIS_KEY_PREFIX = "wxpaypc-" ; private ICacheManager _cache; public RedisCertificateManager (ICacheManager cacheManager ) { _cache = cacheManager; } private string GenerateRedisKey (string serialNumber ) { return $"{REDIS_KEY_PREFIX} {serialNumber} " ; } public IEnumerable<CertificateEntry> AllEntries () { var certs = _cache.GetByPrefix<CertificateEntry>($"{REDIS_KEY_PREFIX} *" ) ?? new Dictionary<string , CertificateEntry>(); if (certs.Any()) { return certs .Select(t => t.Value) .ToArray(); } return Array.Empty<CertificateEntry>(); } public void AddEntry (CertificateEntry entry ) { string key = GenerateRedisKey(entry.SerialNumber); _cache.Set(key, entry); } public CertificateEntry? GetEntry(string serialNumber) { string key = GenerateRedisKey(serialNumber); var values = AllEntries(); if (values.Any()) { return values.OrderByDescending(a => a.ExpireTime).FirstOrDefault(); } return null ; } public bool RemoveEntry (string serialNumber ) { string key = GenerateRedisKey(serialNumber); _cache.Remove(key); return true ; } }
Worker干的事,为了以后可能出现多商户,也搞了循环商户数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 while (!stoppingToken.IsCancellationRequested) { foreach (var tenpayMerchantOptions in _tenpayOptions.Merchants) { try { const string ALGORITHM_TYPE = "RSA" ; var client = _tenpayService.CreateClient(tenpayMerchantOptions.MerchantId); var request = new QueryCertificatesRequest() { AlgorithmType = ALGORITHM_TYPE }; var response = await client.ExecuteQueryCertificatesAsync(request, cancellationToken: stoppingToken); if (response.IsSuccessful()) { foreach (var certificate in response.CertificateList) { client.PlatformCertificateManager.AddEntry(CertificateEntry.Parse(ALGORITHM_TYPE, certificate)); } _logger.LogInformation("刷新微信商户平台证书成功。" ); } else { _logger.LogWarning( "刷新微信商户平台证书失败(状态码:{0},错误代码:{1},错误描述:{2})。" , response.GetRawStatus(), response.ErrorCode, response.ErrorMessage ); } } catch (Exception ex) { _logger.LogError(ex, "刷新微信商户平台证书遇到异常。" ); } } await Task.Delay(TimeSpan.FromDays(1 ), stoppingToken); }
创建订单 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public async Task<CreatePayTransactionJsapiResponse> CreateOrderByJsapi (CreateOrderByJsapiRequest requestModel ) { var client = CreateClient(requestModel.MerchantId); var notifyUrl = _tenpayOptions.NotifyUrl; if (notifyUrl.Contains("{merchant_id}" )) notifyUrl = notifyUrl.Replace("{merchant_id}" , requestModel.MerchantId); var amount = Convert.ToInt32(requestModel.Amount * 100 ); var request = new CreatePayTransactionJsapiRequest() { OutTradeNumber = requestModel.OutTradeNumber, AppId = requestModel.AppId, Description = requestModel.Description, NotifyUrl = notifyUrl, Amount = new CreatePayTransactionJsapiRequest.Types.Amount() { Total = amount }, Payer = new CreatePayTransactionJsapiRequest.Types.Payer() { OpenId = requestModel.OpenId } }; CreatePayTransactionJsapiResponse response = await client.ExecuteCreatePayTransactionJsapiAsync(request); if (!response.IsSuccessful()) { throw new ( $"JSAPI 下单失败(状态码:{response.GetRawStatus()} ,错误代码:{response.ErrorCode} ,错误描述:{response.ErrorMessage} )。" ); } return response; }
生成客户端参数 拿着下单后的PrepayId就可以去获取客户端所需参数了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public IDictionary<string , string > GetParametersForJsapiPayRequest (ParametersForJsapiPayRequest requestModel ) { try { var client = CreateClient(requestModel.MerchantId); var res = client.GenerateParametersForJsapiPayRequest(requestModel.AppId, requestModel.PrepayId); var ret = new Dictionary<string , string >(res) { { "billno" , requestModel.BillNo } }; return ret; } catch (Exception ex) { throw new ("生成客户端 JSAPI / 小程序调起支付所需的参数字典异常" , ex); } }
回调方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 [HttpPost ] [Route("wx/{merchant_id}/pay" ) ]public async Task<IActionResult> ReceiveMessage ( [FromRoute(Name = "merchant_id" )] string merchantId, [FromHeader (Name = "Wechatpay-Timestamp" )] string timestamp, [FromHeader (Name = "Wechatpay-Nonce" )] string nonce, [FromHeader (Name = "Wechatpay-Signature" )] string signature, [FromHeader (Name = "Wechatpay-Serial" )] string serialNumber) { using var reader = new StreamReader(App.HttpContext.Request.Body, Encoding.UTF8); string content = await reader.ReadToEndAsync(); var client = _tenPayService.CreateClient(merchantId); bool valid = client.VerifyEventSignature( webhookTimestamp: timestamp, webhookNonce: nonce, webhookBody: content, webhookSignature: signature, webhookSerialNumber: serialNumber ); if (!valid) { await ExceptionLogHelper.WriteRequestToLogAsync(_logger, App.HttpContext, new Exception("微信支付验签失败" ), App.Configuration); return new JsonResult(new { code = "FAIL" , message = "验签失败" }); } WechatTenpayEvent callbackModel = client.DeserializeEvent(content); var eventType = callbackModel.EventType?.ToUpper(); TransactionResource callbackResource = client.DecryptEventResource<TransactionResource>(callbackModel); switch (eventType) { case "TRANSACTION.SUCCESS" : { _vipService.DoPaySuccessOp(callbackResource, callbackModel); } break ; default : { if (callbackResource.IsNull()) { await ExceptionLogHelper.WriteRequestToLogAsync(_logger, App.HttpContext, new Exception("微信支付通知失败" ), App.Configuration); } await _vipService.DoPayFailureOpAsync($"回调发生错误:{callbackModel.Summary} " , callbackResource.OutTradeNumber, callbackModel.CreateTime.ToDate(), JToken.FromObject(callbackModel)); } break ; } return new JsonResult(new { code = "SUCCESS" , message = "成功" }); }
至此就ok啦,业务处理的代码就不放了,根据自己的情况编写。
其他 前端 本地测试时,支付用的时wx官方提供的小程序示例,其中有个模块叫接口能力——发起支付,改改提交方法即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 wx.requestPayment ( { "appId" : "**********" , "timeStamp" : "1708416150" , "nonceStr" : "" **********"," , "package" : "prepay_id=" **********"," , "signType" : "RSA" , "paySign" : "" **********"," , "billno" : "cz_517123880726597" , "success" :function (res ){}, "fail" :function (res ){}, "complete" :function (res ){} } )
回调测试 如果没有测试服务器,像我一样,可以使用natapp内网穿透,如果在公司测试的话开个内网穿透就ok了
如果有其他啥好方法也可以分享一下!