隐藏

C# .Net Core 微信H5支付API V3【统一下单,回调】

发布:2023/3/28 20:24:02作者:管理员 来源:本站 浏览次数:940

关于API v3


为了在保证支付安全的前提下,带给商户简单、一致且易用的开发体验,我们推出了全新的微信支付API v3。


相较于之前的微信支付API,主要区别是:


   遵循统一的REST的设计风格

   使用JSON作为数据交互的格式,不再使用XML

   使用基于非对称密钥的SHA256-RSA的数字签名算法,不再使用MD5或HMAC-SHA256

   不再要求携带HTTPS客户端证书(仅需携带证书序列号)

   使用AES-256-GCM,对回调中的关键信息进行加密保护


如下方法转载:


.Net Core 微信H5支付API V3【统一下单,回调】_平庸的俗人的博客-CSDN博客


统一下单的方法


   public class H5MyWXPayUtil

       {

           public readonly static string appid = “”// 这里填写你自己的appid

           // 商户号

           public readonly static string mch_id = “”;// 这里填写你自己的商户号

           // 商户秘钥

           public readonly static string partnerkey = “”// 这里填写你自己的商户秘钥

           // 回调地址

           public readonly static string notifyurl = “”// 回调地址随便外网能访问就行  /tPosition/WX_Callback

           // 统一下单接口

           public readonly static string url = "https://api.mch.weixin.qq.com/v3/pay/transactions/h5";

   

           #region  H5请求的需要的参数

           /// <summary>

           /// h5支付需要的类

           /// </summary>

           public class bodyModel {

               /// <summary>

               /// 直连商户号

               /// </summary>

               public string mchid { get; set; }

               /// <summary>

               /// 应用ID

               /// </summary>

               public string appid { get; set; }

               /// <summary>

               /// 商品描述

               /// </summary>

               public string description { get; set; }

               

               /// <summary>

               /// 商户订单号

               /// </summary>

               public string out_trade_no { get; set; }

               /// <summary>

               /// 通知地址

               /// </summary>

               public string notify_url { get; set; }

               /// <summary>

               /// 订单金额

               /// </summary>

               public object amount { get; set; }

               /// <summary>

               /// 景信息

               /// </summary>

               public object  scene_info { get; set; }

              /// <summary>

              /// 附加数据

              /// </summary>

              public  string attach { get; set; }

   

   

           }

           public class H5amount {

               /// <summary>

               /// 金钱

               /// </summary>

               public int total { get; set; }

           }

   

           /// <summary>

           /// 场景信息

           /// </summary>

           public class H5scene_info {

               /// <summary>

               /// 客户端IP

               /// </summary>

               public string payer_client_ip { get; set; }

               /// <summary>

               /// H5场景信息

               /// </summary>

               public object h5_info { get; set; }

   

       }

           /// <summary>

           /// H5场景信息

           /// </summary>

           public class H5type {

               /// <summary>

               /// 场景类型

               /// </summary>

               public string type { get; set; }

   

           }

   

           #endregion

   

           #region 请求返回的参数

   

           public class returnParameters {

               /// <summary>

               /// 返回结果【Success/Error】

               /// </summary>

               public bool result { get; set; }

               /// <summary>

               /// 描述

               /// </summary>

               public string errmsg { get; set; }

               /// <summary>

               /// 返回成功的链接

               /// </summary>

               public string h5_url { get; set; }

           }

   

           #endregion

           /// <summary>

           /// H5支付封装

           /// </summary>

           /// <param name="description">商品的描述</param>

           /// <param name="total">金钱,分为单位</param>

           /// <param name="attach">附加数据</param>

           /// <param name="payer_client_ip">客户端的ip地址</param>

           /// <returns></returns>

           public static string  H5Getprepay(string description, decimal total, string attach,string payer_client_ip) {

               returnParameters returnParameters = new returnParameters();

               //body参数

               var formData = new bodyModel

               {

                   appid=appid,//应用ID

                   mchid=mch_id,//商户号

                   description= description,//商品描述

                   out_trade_no = getRandomTime(),//商户订单号

                   attach =attach,//附加数据

                   notify_url=notifyurl,//通知地址

                   amount =new H5amount {

                         total= Convert.ToInt32(total * 100),//金额

                   },

                   scene_info =new H5scene_info {

                       payer_client_ip= payer_client_ip,//客户端Ip

                       h5_info=new H5type {

                           type= "Wap"

                       }

                   }

   

               };

   

               HttpClient client = new HttpClient(new HttpHandlerH5("{商户号}", "{商户证书序列号}"));

               var bodyJson = new StringContent(formData.ToJson(), Encoding.UTF8, "application/json");

               var response =  client.PostAsync(url, bodyJson);

               var rep = response.Result;//在这里会等待task返回

               var respStr = rep.Content.ReadAsStringAsync();

               string responseBodyAsText = respStr.Result;

               JObject jo = (JObject)JsonConvert.DeserializeObject(responseBodyAsText);

               if (response.Result.IsSuccessStatusCode)

               {

                   returnParameters.h5_url = jo["h5_url"].ToString();

                   returnParameters.result = true;

               }

               else

               {

                   returnParameters.errmsg = jo["message"].ToString();

                   returnParameters.result = false;

               }

               return   JsonConvert.SerializeObject(returnParameters);

   

           }

   

           /// 生成订单号

           /// </summary>

           /// <returns></returns>

           private static string getRandomTime()

           {

               Random rd = new Random();//用于生成随机数

               string DateStr = DateTime.Now.ToString("yyyyMMddHHmmssMM");//日期

            string str = DateStr + rd.Next(10000).ToString().PadLeft(4, '0');//带日期的随机数

               return str;

           }

       }


签名生成



   using System;

   using System.IO;

   using System.Net.Http;

   using System.Security.Cryptography;

   using System.Threading;

   using System.Threading.Tasks;

   

   namespace HttpHandlerDemo

   {

       // 使用方法

       // HttpClient client = new HttpClient(new HttpHandler("{商户号}", "{商户证书序列号}"));

       // ...

       // var response = client.GetAsync("https://api.mch.weixin.qq.com/v3/certificates");

       public class HttpHandler : DelegatingHandler

       {

           private readonly string merchantId;

           private readonly string serialNo;

   

           public HttpHandler(string merchantId, string merchantSerialNo)

           {

               InnerHandler = new HttpClientHandler();

   

               this.merchantId = merchantId;

               this.serialNo = merchantSerialNo;

           }

   

           protected async override Task SendAsync(

               HttpRequestMessage request,

               CancellationToken cancellationToken)

           {

               var auth = await BuildAuthAsync(request);

               string value = $"WECHATPAY2-SHA256-RSA2048 {auth}";

               request.Headers.Add("Authorization", value);

   

               return await base.SendAsync(request, cancellationToken);

           }

   

           protected async Task BuildAuthAsync(HttpRequestMessage request)

           {

               string method = request.Method.ToString();

               string body = "";

               if (method == "POST" || method == "PUT" || method == "PATCH")

               {

                   var content = request.Content;

                   body = await content.ReadAsStringAsync();

               }

   

               string uri = request.RequestUri.PathAndQuery;

               var timestamp = DateTimeOffset.Now.ToUnixTimeSeconds();

               string nonce = Path.GetRandomFileName();

   

               string message = $"{method}\n{uri}\n{timestamp}\n{nonce}\n{body}\n";

               string signature = Sign(message);

               return $"mchid=\"{merchantId}\",nonce_str=\"{nonce}\",timestamp=\"{timestamp}\",serial_no=\"{serialNo}\",signature=\"{signature}\"";

           }

   

           protected string Sign(string message)

           {

               // NOTE: 私钥不包括私钥文件起始的-----BEGIN PRIVATE KEY-----

               //        亦不包括结尾的-----END PRIVATE KEY-----

               string privateKey = "{你的私钥}";

               byte[] keyData = Convert.FromBase64String(privateKey);

               using (CngKey cngKey = CngKey.Import(keyData, CngKeyBlobFormat.Pkcs8PrivateBlob))

               using (RSACng rsa = new RSACng(cngKey))

               {

                   byte[] data = System.Text.Encoding.UTF8.GetBytes(message);

                   return Convert.ToBase64String(rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1));

               }

           }

       }

   }



支付回调方法:


    public async Task<string> PayH5NotifyUrl()

           {

             

             

               //获取回调POST过来的xml数据的代码

               Stream stream = HttpContext.Request.Body;

               byte[] buffer = new byte[HttpContext.Request.ContentLength.Value];

               await stream.ReadAsync(buffer, 0, buffer.Length); //.net core 3.1需要用ReadAsync才不会出错

              string str = System.Text.Encoding.UTF8.GetString(buffer);

               JObject jsonObj = JObject.Parse(str);

               string summary = jsonObj["summary"].ToString();//回调摘要

               if (summary== "支付成功")

               {

               

                   string associatedData = ((JObject)jsonObj["resource"])["associated_data"].ToString();//附加数据

                   string nonce = ((JObject)jsonObj["resource"])["nonce"].ToString();//随机串

                   string ciphertext = ((JObject)jsonObj["resource"])["ciphertext"].ToString();//数据密文

                   //调用解密

                   var decryptStr = AesGcm.AesGcmDecrypt(associatedData, nonce, ciphertext);

                   JObject resource = JObject.Parse(decryptStr);

                   string attach = resource["attach"].ToString();//附加数据

                   string  out_trade_no= resource["out_trade_no"].ToString();//商户订单号

                   string transaction_id = resource["transaction_id"].ToString();//信支付订单号

                   string total_fee = ((JObject)resource["amount"])["total"].ToString();//总金额

                   string return_code = resource["trade_state"].ToString();//交易状态

                 

                   try

                   {

                       //业务逻辑

                       if (return_code.ToUpper()== "SUCCESS")

                       {

                           return "{"

                           + "\"code\":\"SUCCESS\","

                           + "\"message\":\"成功\","

                           + "}";

                       }

                   }

                   catch (Exception e)

                   {

                       throw;

                   }

               }

               return "{"

                            + "\"code\":\"FAIL\","

                            + "\"message\":\"ERROR\","

                            + "}";

   

           }


解密方法:


   public class AesGcm

   {

       private static string ALGORITHM = "AES/GCM/NoPadding";

       private static int TAG_LENGTH_BIT = 128;

       private static int NONCE_LENGTH_BYTE = 12;

       private static string AES_KEY = "yourkeyhere";//APIv3密钥

   

       public static string AesGcmDecrypt(string associatedData, string nonce, string ciphertext)

       {

           GcmBlockCipher gcmBlockCipher = new GcmBlockCipher(new AesEngine());

           AeadParameters aeadParameters = new AeadParameters(

               new KeyParameter(Encoding.UTF8.GetBytes(AES_KEY)),

               128,

               Encoding.UTF8.GetBytes(nonce),

               Encoding.UTF8.GetBytes(associatedData));

           gcmBlockCipher.Init(false, aeadParameters);

   

           byte[] data = Convert.FromBase64String(ciphertext);

           byte[] plaintext = new byte[gcmBlockCipher.GetOutputSize(data.Length)];

           int length = gcmBlockCipher.ProcessBytes(data, 0, data.Length, plaintext, 0);

           gcmBlockCipher.DoFinal(plaintext, length);

           return Encoding.UTF8.GetString(plaintext);

       }

   }


常见问题: