开发流程 创建预订单,同时传递回调地址参数 订单创建成功后返回一个 URL 地址 将 URL 地址生成二维码显示在用户界面 执行扫码支付 后台执行回调事件,更改订单状态,同时返回通知给微信服务端 接入准备 请参考:
开发平台 .Net 微信第三方 SDK 这里选用 SKIT.FlurlHttpClient.Wechat ,其它类似的还有盛派微信 SDK(Senparc.Weixin) ,SKIT.FlurlHttpClient.Wechat 官方文档是这样介绍的:基于 Flurl.Http
的微信 API HTTP 客户端,支持公众平台、开放平台、商户平台、企业微信、广告平台、对话开放平台等模块。
特性:
基于 Flurl.Http
,可与 IHttpClientFactory
集成。 支持 .NET Framework 4.6.1+、.NET Standard 2.0+、.NET Core 2.0+、.NET 5、.NET 6。 支持 Windows / Linux / macOS 多平台部署。 支持 System.Text.Json
(默认)和 Newtonsoft.Json
两种序列化方式。 异步式编程。 强类型接口模型。 提供拦截器功能。 包含 SourceLink 符号文件,可在项目中无源代码调试。 完整、完善、完全的微信 API 封装。 安装 如果使用 Visual Studio NuGet 管理器图形化界面,请在搜索结果中勾选“包括预发行版 ”。
1 2 3 4 5 # 通过 NuGet 安装 > Install-Package SKIT.FlurlHttpClient.Wechat.TenpayV3 # 通过 dotnet-tools 安装 > dotnet add package SKIT.FlurlHttpClient.Wechat.TenpayV3
配置 配置 Client 实例:
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 internal class WechatPayClients { public static readonly WechatTenpayClient Instance; static WechatPayClients () { var certificateManager = new SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings.InMemoryCertificateManager(); Instance = new WechatTenpayClient(new WechatTenpayClientOptions() { MerchantId = "" , MerchantV3Secret = "" , MerchantCertificateSerialNumber = "" , MerchantCertificatePrivateKey =System.IO.File.ReadAllText("apiclient_key.pem" ), PlatformCertificateManager = certificateManager, AutoEncryptRequestSensitiveProperty = true , AutoDecryptResponseSensitiveProperty = true , }); } internal static async Task InitializeCertificateManagerAsync () { var request = new SKIT.FlurlHttpClient.Wechat.TenpayV3.Models.QueryCertificatesRequest(); var response = await Instance.ExecuteQueryCertificatesAsync(request); response = Instance.DecryptResponseSensitiveProperty(response); foreach (var certificateModel in response.CertificateList) { Instance.PlatformCertificateManager.AddEntry(new SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings.CertificateEntry(certificateModel)); } } }
MerchantId、MerchantV3Secret、MerchantCertificateSerialNumber 和 MerchantCertificatePrivateKey 这几个参数均为第 1 步“接入准备”中获取的。
API接口 创建预订单 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 public async Task CreatePrePayOrder (){ var request = new CreatePayTransactionNativeRequest() { OutTradeNumber = "or123456" , AppId = "" , Description = "Payment for or123456" , ExpireTime = DateTime.Now.AddMinutes(15 ), NotifyUrl = "https://www.zerow.cn/notification" , Amount = new CreatePayTransactionNativeRequest.Types.Amount() { Total = 100 }, }; var response= await WechatPayClients.Instance.ExecuteCreatePayTransactionNativeAsync(request); if (response.IsSuccessful()) { Console.WriteLine($"QRCode URL:{response.QrcodeUrl} " ); } else { Console.WriteLine($"错误代码:{response.ErrorCode} " ); Console.WriteLine($"错误描述:{response.ErrorMessage} " ); } }
官方支付成功回调,SDK 已实现解密:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 { "id" : "xxxx-xxxx-xxxx-xxxx" , "create_time" : "2022-04-25T17:04:47+08:00" , "resource_type" : "encrypt-resource" , "event_type" : "TRANSACTION.SUCCESS" , "summary" : "支付成功" , "resource" : { "original_type" : "transaction" , "algorithm" : "AEAD_AES_256_GCM" , "ciphertext" : "xxxxxx" , "associated_data" : "transaction" , "nonce" : "xxxxxx" } }
支付回调 创建订单的请求参数中提供了 NotifyUrl 回调地址参数,所以需要有一个回调函数:
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 public async Task PrePayCallback (){ var sr= new StreamReader(Request.InputStream, Encoding.UTF8); bool valid = WechatPayClients.Instance.VerifyEventSignature( callbackTimestamp: Request.Headers.GetValues("Wechatpay-Timestamp" ).First(), callbackNonce: Request.Headers.GetValues("Wechatpay-Nonce" ).First(), callbackBody: sr.ReadToEnd(), callbackSignature: Request.Headers.GetValues("Wechatpay-Signature" ).First(), callbackSerialNumber: Request.Headers.GetValues("Wechatpay-Serial" ).First(), out Exception e ); var callbackModel = WechatPayClients.Instance.DeserializeEvent(sr.ReadToEnd()); var callbackResource = WechatPayClients.Instance.DecryptEventResource<SKIT.FlurlHttpClient.Wechat.TenpayV3.Events.TransactionResource>(callbackModel); if (valid) { if ("TRANSACTION.SUCCESS" .Equals(callbackModel.EventType)) { Console.WriteLine(callbackResource.TransactionId); Console.WriteLine(callbackResource.OutTradeNumber); } } }
查询交易状态 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public async Task QueryTransactionStatus (string outTradeNumber ){ var request = new GetPayTransactionByOutTradeNumberRequest() { OutTradeNumber = outTradeNumber }; var response = await WechatPayClients.Instance.ExecuteGetPayTransactionByOutTradeNumberAsync(request); if (response.IsSuccessful()) { Console.WriteLine(response.TradeState); Console.WriteLine(response.TransactionId); Console.WriteLine(response.OutTradeNumber); } else { Console.WriteLine(response.ErrorMessage); } }
查询交易状态 API:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_2.shtml
交易状态枚举值: SUCCESS:支付成功 REFUND:转入退款 NOTPAY:未支付 CLOSED:已关闭 REVOKED:已撤销(仅付款码支付会返回) USERPAYING:用户支付中(仅付款码支付会返回) PAYERROR:支付失败(仅付款码支付会返回)
申请退款 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 async Task CreateRefund (string outTradeNumber ){ var request = new CreateRefundDomesticRefundRequest() { OutTradeNumber = outTradeNumber, OutRefundNumber = "" , Amount = new CreateRefundDomesticRefundRequest.Types.Amount() { Total = 100 , Refund = 100 }, Reason = “” }; var response = await WechatPayClients.Instance.ExecuteCreateRefundDomesticRefundAsync(request); if (response.IsSuccessful) { Console.WriteLine(response.Status); } else { Console.WriteLine(response.ErrorMessage); } }
申请退款 API:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_9.shtml
退款状态枚举值: SUCCESS:退款成功 CLOSED:退款关闭 PROCESSING:退款处理中 ABNORMAL:退款异常
关闭订单 1 2 3 4 5 6 7 8 9 10 11 12 public async Task CloseTransaction (string outTradeNumber ){ var request = new ClosePayTransactionRequest() { OutTradeNumber = outTradeNumber }; var response = await WechatPayClients.Instance.ExecuteClosePayTransactionAsync(request); if (!response.IsSuccessful()) { Console.WriteLine(response.ErrorMessage); } }
关闭订单 API:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_3.shtml
参考