zoukankan      html  css  js  c++  java
  • 微信支付API V3(.Net Core)

        public class WXPayService : IPayService
        {
            public static readonly ILogger _logger = LogManager.GetCurrentClassLogger();
    
            private static char[] constant =
                              {
                                    '0','1','2','3','4','5','6','7','8','9',
                                    'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
                                    'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'
                                  };
    
            /// <summary>
            /// 
            /// </summary>
            protected ReceivablePayConfig RecePayCfg { get; set; }
    
            public WXPayService(IOptions<ReceivablePayConfig> recePayCfgOpt)
            {
                this.RecePayCfg = recePayCfgOpt.Value;
            }
    
            //public async Task<PayCallBackOutput> 
    
    
    
    
            public string AesGcmDecrypt(string associatedData, string nonce, string ciphertext)
            {
                GcmBlockCipher gcmBlockCipher = new GcmBlockCipher(new AesEngine());
                AeadParameters aeadParameters = new AeadParameters(
                    new KeyParameter(Encoding.UTF8.GetBytes(this.RecePayCfg.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);
            }
    
            private string ToUrl(IEnumerable<KeyValuePair<string, string>> parameters)
            {
                string buff = "";
                foreach (KeyValuePair<string, string> pair in parameters)
                {
                    if (string.IsNullOrEmpty(pair.Value))
                    {
                        continue;
                    }
    
                    if (pair.Key != "sign" && pair.Value.ToString() != "")
                    {
                        buff += pair.Key + "=" + pair.Value + "&";
                    }
                }
                buff = buff.Trim('&');
                return buff;
            }
    
            private string GenerateRandomNumber(int length)
            {
                StringBuilder newRandom = new StringBuilder(62);
                Random rd = new Random();
                for (int i = 0; i < length; i++)
                {
                    newRandom.Append(constant[rd.Next(62)]);
                }
                return newRandom.ToString();
            }
    
            private static long ToUnixEpochDate(DateTime date)
        => (long)Math.Round((date.ToUniversalTime() - new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)).TotalSeconds);
    
            /// <summary>
            /// 统一下单v3(https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_1.shtml)
            /// </summary>
            /// <typeparam name="I"></typeparam>
            /// <param name="outData"></param>
            /// <param name="parameters"></param>
            /// <returns></returns>
            public async Task<Dictionary<string, string>> UnifiedOrder<I>(I outData, params KeyValuePair<string, string>[] parameters) where I : new()
            {
                _logger.Info(new LogInfo() { Method = "UnifiedOrder", Argument = parameters, Description = "统一下单3" });
                //判断是否存在参数
                if (parameters?.Length <= 0)
                {
                    throw new Exception("未提供参数");
                }
    
                string openid = string.Empty;
                if (parameters.Count(t => string.Compare(t.Key, "openid", true) == 0 && !string.IsNullOrWhiteSpace(t.Value)) <= 0)
                {
                    if (parameters.Count(t => string.Compare(t.Key, "code", true) == 0 && !string.IsNullOrWhiteSpace(t.Value)) <= 0)
                    {
                        throw new Exception("未提供code参数");
                    }
                }
                else
                {
                    openid = parameters.FirstOrDefault(t => string.Compare(t.Key, "openid", true) == 0).Value;
                }
                var newparams = parameters.ToList();
                newparams.Add(new KeyValuePair<string, string>("appid", RecePayCfg.appid));
                newparams.Add(new KeyValuePair<string, string>("secret", RecePayCfg.secret));
                newparams.Add(new KeyValuePair<string, string>("grant_type", RecePayCfg.grant_type));
                WXUnifiedOrderReq req = new WXUnifiedOrderReq();
                Dictionary<string, string> sParams2 = new Dictionary<string, string>();
                //判断是否存在openid
                if (!string.IsNullOrWhiteSpace(openid))
                {
                    req.payer = new Payer() { openid = openid };
                    sParams2.Add("openid", openid);
                }
                else
                {
                    string url = this.RecePayCfg.url + this.ToUrl(newparams.OrderBy(t => t.Key));
                    using (HttpClient client = new HttpClient())
                    {
                        var response = await client.GetAsync(url);
                        string contentstr = await response.Content.ReadAsStringAsync();
                        if (response.IsSuccessStatusCode)
                        {
                            WXCodeResult codeResult = null;
                            try
                            {
                                codeResult = JsonConvert.DeserializeObject<WXCodeResult>(contentstr);
                            }
                            catch (Exception ex)
                            {
                                _logger.Error(ex, "微信支付获取openid失败");
                                throw new SinoException("获取OpenId失败");
                            }
    
                            if (codeResult == null || string.IsNullOrEmpty(codeResult.openid))
                            {
                                _logger.Error(new SinoException(contentstr), "微信支付获取openid失败");
                                throw new SinoException("获取OpenId失败");
                            }
                            req.payer = new Payer() { openid = codeResult.openid };
                            sParams2.Add("openid", codeResult.openid);
                        }
                        else
                        {
                            _logger.Error(new SinoException(contentstr), "微信支付获取openid失败");
                            throw new SinoException("获取OpenId失败");
                        }
                    }
                }
                req.appid = RecePayCfg.appid;
                req.mchid = RecePayCfg.mchid;
                req.notify_url = RecePayCfg.notify_url;
                if (outData is DependOutDataWXPay)
                {
                    var tempData = outData as DependOutDataWXPay;
                    req.amount = new Amount() { total = tempData.total };
                    req.description = tempData.description;
                    req.out_trade_no = tempData.out_trade_no;
                }
                else
                {
                    throw new Exception("入参类型不正确");
                }
                var nonceStr = GenerateRandomNumber(32);
                using (var httpClient = new HttpClient(new WXPayHttpHandler(RecePayCfg.mchid, RecePayCfg.serial_no, "apiclient_cert.p12", RecePayCfg.mchid, nonceStr)))
                {
                    string postUrl = RecePayCfg.UnifiedOrderUrl;
                    // POST 方式
                    //一定要这样传递参数,不然在加密签名的时候获取到的参数就是\u0这种形式的数据了,不是传递的这样的数据了,导致加密的结果不正确
    
                    string jsonData = JsonConvert.SerializeObject(req);
                    _logger.Info($"统一下单参数:{jsonData}");
                    var bodyJson = new StringContent(jsonData, Encoding.UTF8, "application/json");
                    httpClient.Timeout = TimeSpan.FromSeconds(10);
                    HttpResponseMessage unifiedOrderRes = null;
                    try
                    {
                        unifiedOrderRes = await httpClient.PostAsync(postUrl, bodyJson);
                    }
                    catch (Exception ex)
                    {
                        throw new SinoException("微信统一下单失败", ex);
                    }
                    _logger.Info("httpclient请求结束");
                    // prepay_id。
                    var postResult = await unifiedOrderRes.Content.ReadAsStringAsync();
                    _logger.Info($"httpclient获取结果{postResult}");
                    if (!unifiedOrderRes.IsSuccessStatusCode)
                    {
                        _logger.Error(new Exception(postResult), "微信统一下单失败");
                        throw new SinoException("微信统一下单失败");
                    }
                    UnifiedOrderRes unifiedOrder = JsonConvert.DeserializeObject<UnifiedOrderRes>(postResult);
    
                    //二次签名
                    string timestamp = ToUnixEpochDate(DateTime.Now).ToString();
    
                    string package = $"prepay_id={unifiedOrder.prepay_id}";
                    sParams2.Add(nameof(RecePayCfg.appid), RecePayCfg.appid);
                    sParams2.Add("nonceStr", nonceStr);
                    sParams2.Add("timeStamp", timestamp);
                    sParams2.Add("package", package);
                    sParams2.Add("signType", "RSA");
                    //需要签名的字符串
                    string needSignStr = $"{RecePayCfg.appid}
    {timestamp}
    {nonceStr}
    {package}
    ";
                    _logger.Info($"paySign签名前:{needSignStr}");
                    string paySign = needSignStr.WXRSAWithSHA256Sign("apiclient_cert.p12", RecePayCfg.mchid);
                    _logger.Info($"paySign签名结果:{paySign}");
                    sParams2.Add("paySign", paySign);
                    return sParams2;
                }
            }
    
            /// <summary>
            /// 商户订单查询
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="input"></param>
            /// <returns></returns>
            public async Task<DecryptionCallBack> PayOrderSearch<T>(T input) where T : new()
            {
                DecryptionCallBack retVal = null;
                PayOrderSearch result = null;
                if (input is PayOrderSearch)
                {
                    result = input as PayOrderSearch;
                }
                else
                {
                    throw new Exception("入参类型不正确");
                }
                var nonceStr = GenerateRandomNumber(32);
                using (var httpClient = new HttpClient(new WXPayHttpHandler(RecePayCfg.mchid, RecePayCfg.serial_no, "apiclient_cert.p12", RecePayCfg.mchid, nonceStr)))
                {
                    string getUrl = $"{RecePayCfg.PayResultSearchUrl}{result.out_trade_no}?mchid={RecePayCfg.mchid}";
                    var httpResponse = await httpClient.GetAsync(getUrl);
                    string contentstr = await httpResponse.Content.ReadAsStringAsync();
                    if (httpResponse.IsSuccessStatusCode)
                    {
                        retVal = JsonConvert.DeserializeObject<DecryptionCallBack>(contentstr);
                    }
                    else
                    {
                        _logger.Error(new SinoException(contentstr), "查询微信订单失败");
                        throw new SinoException("查询微信订单失败");
                    }
                }
                return retVal;
            }
        }
        public static class StringExtension
        {
            
    
            public static string RSAWithPrivateKey(this string message,string certPath)
            {
    
                //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));
                //}
                return string.Empty;
                //try
                //{
                //    RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
                //    byte[] priKeyBytes = Convert.FromBase64String(privateKey);
                //    rsa.ImportCspBlob(priKeyBytes);
                //    byte[] data = System.Text.Encoding.UTF8.GetBytes(message);
                //    return Convert.ToBase64String(rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1));
                //}
                //catch
                //{
                //    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));
                //    }
                //}
                //NLog.ILogger _logger = NLog.LogManager.GetCurrentClassLogger();
                //_logger.Info("进入RSA签名");
    
                //X509Certificate2 cer = new X509Certificate2(certPath);
                //if (cer != null)//获取公钥
                //{
                //    RSA rsa = cer.GetRSAPrivateKey();
                //    //查看在不同平台上的具体类型
                //    _logger.Info($"RSA类型:{rsa.GetType().FullName}");
                //    byte[] data = System.Text.Encoding.UTF8.GetBytes(message);
                //    return Convert.ToBase64String(rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1));
                //    //var isSig = pubkey.VerifyData(butys, CCB_ALG, verify);//验证信息
                //}
                //else
                //{
                //    throw new Exception("证书解析失败");
                //}
            }
    
            /// <summary>
            /// 微信RSA SHA256 签名
            /// </summary>
            /// <param name="message"></param>
            /// <param name="privateKey"></param>
            /// <returns></returns>
            public static string WXRSAWithSHA256Sign(this string message, string certPath, string certPwd)
            {
                //NLog.ILogger _logger = NLog.LogManager.GetCurrentClassLogger();
                X509Certificate2 cer = new X509Certificate2(certPath, certPwd, X509KeyStorageFlags.Exportable);
                if (cer != null)//获取公钥
                {
                    RSA rsa = cer.GetRSAPrivateKey();
                    //查看在不同平台上的具体类型
                    //_logger.Info($"RSA类型:{rsa.GetType().FullName}");
                    byte[] data = System.Text.Encoding.UTF8.GetBytes(message);
                    return Convert.ToBase64String(rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1));
                }
                else
                {
                    throw new Exception("证书解析失败");
                }
            }
        }
    
        // 使用方法
        // HttpClient client = new HttpClient(new HttpHandler("{商户号}", "{商户证书序列号}"));
        // var response = client.GetAsync("https://api.mch.weixin.qq.com/v3/certificates");
        public class WXPayHttpHandler : DelegatingHandler
        {
            private readonly string merchantId;
            private readonly string serialNo;
            private readonly string certPath;
            private readonly string certPwd;
            private readonly string nonceStr;
    
            public WXPayHttpHandler(string merchantId, string merchantSerialNo, string certPath, string certPwd, string nonceStr)
            {
                InnerHandler = new HttpClientHandler();
    
                this.merchantId = merchantId;
                this.serialNo = merchantSerialNo;
                this.certPath = certPath;
                this.certPwd = certPwd;
                this.nonceStr = nonceStr;
            }
    
            protected async override Task<HttpResponseMessage> SendAsync(
                HttpRequestMessage request,
                CancellationToken cancellationToken)
            {
                var auth = await BuildAuthAsync(request);
                string value = $"WECHATPAY2-SHA256-RSA2048 {auth}";
                request.Headers.Add("Authorization", value);
                request.Headers.Add("Accept", "application/json");//如果缺少这句代码就会导致下单接口请求失败,报400错误(Bad Request)
                request.Headers.Add("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)");//如果缺少这句代码就会导致下单接口请求失败,报400错误(Bad Request)
    
                return await base.SendAsync(request, cancellationToken);
            }
    
            protected async Task<string> 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();//debug的时候在这里打个断点,看看body的值是多少,如果跟你传入的参数不一致,说明是有问题的,一定参考我的方法
                }
    
                string uri = request.RequestUri.PathAndQuery;
                var timestamp = DateTimeOffset.Now.ToUnixTimeSeconds();
                string nonce = this.nonceStr;
    
                string message = $"{method}
    {uri}
    {timestamp}
    {nonce}
    {body}
    ";
                string signature = message.WXRSAWithSHA256Sign(this.certPath, this.certPwd);
                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 = this.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));
            //    }
            //}
        }
        /// <summary>
        /// 应收微信支付配置
        /// </summary>
        public class ReceivablePayConfig
        {
            /// <summary>
            /// 主机地址
            /// </summary>
            public string Host { get; set; }
    
            /// <summary>
            /// 路由地址
            /// </summary>
            public string Route { get; set; }
    
            /// <summary>
            /// 生成二维码图片的像素大小 ,我这里设置的是5
            /// </summary>
            public int? PixelsPerModule { get; set; }
    
            /// <summary>
            /// 根据code获取微信授权的地址
            /// </summary>
            public string url { get; set; }
    
            /// <summary>
            /// appid
            /// </summary>
            public string appid { get; set; }
    
            /// <summary>
            /// 
            /// </summary>
            public string secret { get; set; }
    
            /// <summary>
            /// 授权方式
            /// </summary>
            public string grant_type { get; set; }
    
            /// <summary>
            /// 商户号
            /// </summary>
            public string mchid { get; set; }
    
            /// <summary>
            /// 商户序列号
            /// </summary>
            public string serial_no { get; set; }
    
            /// <summary>
            /// 回调通知地址
            /// </summary>
            public string notify_url { get; set; }
    
            /// <summary>
            /// 统一下单url
            /// </summary>
            public string UnifiedOrderUrl { get; set; }
    
            /// <summary>
            /// 商户订单号查询
            /// </summary>
            public string PayResultSearchUrl { get; set; }
    
            /// <summary>
            /// 微信支付私钥
            /// </summary>
            public string PrivateKey { get; set; }
    
            /// <summary>
            /// AES_KEY 微信支付回调使用
            /// </summary>
            public string AES_KEY { get; set; }
    
        }
        public class DependOutDataWXPay
        {
            /// <summary>
            /// 订单总金额,单位为分
            /// </summary>
            public int total { get; set; }
    
            /// <summary>
            /// 商户订单号    
            /// </summary>
            public string out_trade_no { get; set; }
    
            /// <summary>
            /// 商品描述
            /// </summary>
            public string description { get; set; }
        }
    
        public class PayOrderSearch
        {
            /// <summary>
            /// 商户订单号
            /// </summary>
            public string out_trade_no { get; set; }
    
        }
    
        /// <summary>
        /// 订单金额
        /// </summary>
        public class Amount
        {
            /// <summary>
            /// 订单总金额,单位为分
            /// </summary>
            public int total { get; set; }
    
            /// <summary>
            /// CNY:人民币,境内商户号仅支持人民币。
            /// </summary>
            public string currency { get; set; } = "CNY";
        }
    
        /// <summary>
        /// 支付者
        /// </summary>
        public class Payer
        {
            /// <summary>
            /// 用户在直连商户appid下的唯一标识。
            /// </summary>
            public string openid { get; set; }
        }
    
        /// <summary>
        /// 微信统一下单JSAPI(v3)
        /// </summary>
        public class WXUnifiedOrderReq
        {
            /// <summary>
            ///  直连商户申请的公众号appid
            /// </summary>
            public string appid { get; set; }
    
            /// <summary>
            /// 直连商户的商户号,由微信支付生成并下发
            /// </summary>
            public string mchid { get; set; }
    
            /// <summary>
            /// 商品描述
            /// </summary>
            public string description { get; set; }
    
            /// <summary>
            /// 商户订单号    
            /// </summary>
            public string out_trade_no { get; set; }
    
            /// <summary>
            /// 通知URL必须为直接可访问的URL,不允许携带查询串,要求必须为https地址。
            /// </summary>
            public string notify_url { get; set; }
    
            /// <summary>
            /// 订单金额
            /// </summary>
            public Amount amount { get; set; }
    
            /// <summary>
            /// 支付者
            /// </summary>
            public Payer payer { get; set; }
        }
      public class WXCodeResult
        {
            [JsonProperty("access_token")]
            public string access_token { get; set; }
    
            [JsonProperty("expires_in")]
            public int expires_in { get; set; }
    
            [JsonProperty("refresh_token")]
            public string refresh_token { get; set; }
    
            [JsonProperty("openid")]
            public string openid { get; set; }
    
            [JsonProperty("scope")]
            public string scope { get; set; }
        }
       public class UnifiedOrderRes
        {
            public string prepay_id { get; set; }
        }
    {
    "ReceivablePayConfig": {
        "Host": "", /*跳转前端的域名*/
        "Route": "", /* 跳转前端的路由 */
        "PixelsPerModule": 5,
        "url": "https://api.weixin.qq.com/sns/oauth2/access_token?",
        "appid": "",
        "secret": "",
        "grant_type": "authorization_code",
        "mchid": "",
        /* 商户证书序列号 */
        "serial_no": "",
        "notify_url": "" /* 回调通知地址 */,
        "UnifiedOrderUrl": "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi" /* 统一下单url */,
        "PayResultSearchUrl": "https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/", /* 商户订单号查询*/
        /* AES_KEY */
        "AES_KEY": "DCE2C90F34544BB1901DB0459033FBB0",
        /*微信支付私钥*/
        "PrivateKey": ""
      }
    }
  • 相关阅读:
    linux下18种监测网络带宽方式
    python常用正则表达式
    python获取当前路径
    python获取本机的IP
    Linux 误卸载自带python后的解决办法
    jmeter分布式运行
    jmeter非GUI的运行命令
    linux下安装jmeter
    java基础笔记(8)
    java基础笔记(7)
  • 原文地址:https://www.cnblogs.com/yiyanwei/p/14664000.html
Copyright © 2011-2022 走看看