2

.Net Core微信服务商二次进件

 2 years ago
source link: https://www.cnblogs.com/liuYingwei/p/15437991.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

最近商城进行微信服务商二次进件的开发,大致有几个点

一,服务商签名

二,服务商证书获取

三,图片上传

四,敏感信息加密

五,查询进件状态

除此之外,就是进件信息的拼装

 电商二级商户进件申请单-状态流转

一 服务商签名

首先准备必须的配置:商户号、证书、秘钥、小程序appid、appsecret

        #region 服务商签名
        private string SrvPayBuildAuthAsync(string uri, string body, string method = "POST")
        {
            var timestamp = DateTimeOffset.Now.ToUnixTimeSeconds();
            string nonce = Guid.NewGuid().ToString();

            string message = $"{method}\n{uri}\n{timestamp}\n{nonce}\n{body}\n";
            string signature = SrvSign(message);

            return $"mchid=\"{_wxCfg.SrvPayMerchantId}\",nonce_str=\"{nonce}\",timestamp=\"{timestamp}\",serial_no=\"{_wxCfg.SrvPayCertNo}\",signature=\"{signature}\"";
        }

        private string SrvSign(string message)
        {
            var bytes = Utils.ReadBytesIfExist(_wxCfg.SrvPayCertFile);
            if (bytes is null)
            {
                return "";
            }
            X509Certificate2 cert = new(bytes, _wxCfg.SrvPayMerchantId);
            RSA rsa = cert.GetRSAPrivateKey();
            var signData = rsa.SignData(Encoding.UTF8.GetBytes(message), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
            return Convert.ToBase64String(signData);
        }

二 获取证书

分为:第一步获取证书,第二步解密证书

1 获取证书

https://api.mch.weixin.qq.com/v3/certificates

        #region 获取平台证书
        public async Task<CertificatesOutModel> GetSrvCert()
        {
            string uri = "/v3/certificates";
            var auth = SrvPayBuildAuthAsync(uri, "", "GET");
            var header = new Dictionary<string, string>
            {
                { "Authorization",$"WECHATPAY2-SHA256-RSA2048 {auth}"},
                { "Accept","*/*" },
                { "Accept-Encoding","gzip,deflate,brn" },
                { "User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36 Edg/90.0.818.46" },
            };
            return await GetUrlAsync<CertificatesOutModel>(uri, header);
        }
        #endregion

使用的实体:CertificatesOutModel

    public sealed class CertificatesOutModel : IWXResponse
    {
        [JsonPropertyName("data")]
        public IEnumerable<Certificates> Data { get; set; }
        public string Code { get; set; }
        public string Message { get; set; }
    }
    public class Certificates
    {
        [JsonPropertyName("serial_no")]
        public string SerialNo { get; set; }

        [JsonPropertyName("effective_time")]
        public string EffectiveTime { get; set; }

        [JsonPropertyName("expire_time")]
        public string ExpireTime { get; set; }

        [JsonPropertyName("encrypt_certificate")]
        public EncryptCertificate EncryptCertificate { get; set; }
    }
请求方法:GetUrlAsync
 protected async Task<T> GetUrlAsync<T>(string url, Dictionary<string, string> headers = null)
        {
            HttpResponseMessage res = null;
            try
            {
                if (headers != null && headers.Count > 0)
                {
                    foreach (var header in headers)
                    {
                        _client.DefaultRequestHeaders.TryAddWithoutValidation(header.Key, header.Value);
                    }
                }
                res = await _client.GetAsync(url);
                res.EnsureSuccessStatusCode();
                var result = await res.Content.ReadAsStringAsync();
                if (result == null)
                {
                    return default;
                }

                return result.ToJson<T>();
            }
            catch
            {
                var result = await res.Content.ReadAsStringAsync();
                if (result == null)
                {
                    return default;
                }
                return result.ToJson<T>();
            }
        }

2,解密证书

            //获取证书
            var cert = await _wxClient.GetSrvCert();
            var certificateModel = cert.Data.FirstOrDefault();
            if (!cert.Data.Any())
            {
                return new MKResult<V3WXPayApplymentIdOutModel>(code: 400, msg: "未获取到平台证书");
            }
            if (!string.IsNullOrEmpty(applyment.Body.SerialNo))
            {
                certificateModel = cert.Data.SingleOrDefault(s => s.SerialNo == applyment.Body.SerialNo);
            }
            certificateModel.EncryptCertificate.Ciphertext = AESUtility.AesGcmDecrypt(
                      _wxCfg.SrvApiV3Key,
                      certificateModel.EncryptCertificate.AssociatedData,
                       certificateModel.EncryptCertificate.Nonce,
                      certificateModel.EncryptCertificate.Ciphertext
                   );
 public class AESUtility
    {
        public static string AesGcmDecrypt(string key, string associatedData, string nonce, string ciphertext)
        {
            GcmBlockCipher gcmBlockCipher = new(new AesEngine());
            AeadParameters aeadParameters = new(
                new KeyParameter(Encoding.UTF8.GetBytes(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);
        }
    }

三,上传图片

因为我的图片保存在oss,首先要网络图片Bytes,对图片进行sha256,方法在后面

        protected async Task<byte[]> GetUrlBytesAsync(string url, Dictionary<string, string> headers = null)
        {
            try
            {
                if (headers != null && headers.Count > 0)
                {
                    foreach (var header in headers)
                    {
                        _client.DefaultRequestHeaders.TryAddWithoutValidation(header.Key, header.Value);
                    }
                }

                var res = await _client.GetAsync(url);
                res.EnsureSuccessStatusCode();
                return await res.Content.ReadAsByteArrayAsync();
            }
            catch
            {
                return default;
            }
        }

然后上传图片

        /// <summary>
        /// 上传图片
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public async Task<MKResult<V3WXPayFileUploadOutModel>> UploadFile(string url)
        {

            string fileContentType;
            string filetype;
            if (url!.Contains(".bmp", StringComparison.OrdinalIgnoreCase))
            {
                fileContentType = "image/bmp";
                filetype = ".bmp";
            }
            else if (url!.Contains(".jpg", StringComparison.OrdinalIgnoreCase))
            {
                fileContentType = "image/jpeg";
                filetype = ".jpg";
            }
            else if (url!.Contains(".jpeg", StringComparison.OrdinalIgnoreCase))
            {
                fileContentType = "image/jpeg";
                filetype = ".jpeg";
            }
            else
            {
                fileContentType = "image/png";
                filetype = ".png";
            }

            UploadMerchantMediaImageRequest meta = new();
            var fileBytes = await GetUrlBytesAsync(url);//获取网络图片Bytes
            if ((fileBytes?.Length ?? 0) == 0)
            {
                return new MKResult<V3WXPayFileUploadOutModel>(code: 400, msg: "转换图片失败");
            }
            meta.FileHash = GetHash(fileBytes);
            meta.FileName = Guid.NewGuid().ToString("N").ToLower() + filetype;

            string boundary = "--BOUNDARY--" + DateTimeOffset.Now.Ticks.ToString("x");
            using var fileContent = new ByteArrayContent(fileBytes);
            using var metaContent = new StringContent(meta.ToJson(), Encoding.UTF8, "application/json");
            using var httpContent = new MultipartFormDataContent(boundary);
            httpContent.Add(metaContent, "\"meta\"");//meta 必须要加双引号
            httpContent.Add(fileContent, "\"file\"", "\"" + meta.FileName + "\"");//必须要加双引号          
            httpContent.Headers.ContentType = MediaTypeHeaderValue.Parse("multipart/form-data; boundary=" + boundary);// boundary不能加引号
            metaContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
            fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse(fileContentType);

            var uri = $"/v3/merchant/media/upload";
            var res = await V3UpLoadFile<V3WXPayFileUploadOutModel>(uri, meta.ToJson(), httpContent);
            return new MKResult<V3WXPayFileUploadOutModel>(res, 1);
        }
  private async Task<T> V3UpLoadFile<T>(string uri, string meta, MultipartFormDataContent content)
        {
            var auth = SrvPayBuildAuthAsync(uri, meta);
            var header = new Dictionary<string, string>
            {
                { "Authorization",$"WECHATPAY2-SHA256-RSA2048 {auth}"},
                { "Accept","*/*" },
                { "Accept-Encoding","gzip,deflate,brn" },
                { "User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36 Edg/90.0.818.46" },
            };
            return await V3PostFileAsync<T>(uri, header, content);
        }
 protected async Task<T> V3PostFileAsync<T>(string url, Dictionary<string, string> headers, MultipartFormDataContent content)
        {
            HttpResponseMessage res = null;
            try
            {
                if (headers != null && headers.Count > 0)
                {
                    foreach (var header in headers)
                    {
                        _client.DefaultRequestHeaders.TryAddWithoutValidation(header.Key, header.Value);
                    }
                }
                res = await _client.PostAsync(url, content);
                res.EnsureSuccessStatusCode();
                var result = await res.Content.ReadAsStringAsync();
                if (result == null)
                {
                    return default;
                }
                return result.ToJson<T>();
            }
            catch
            {
                var result = await res.Content.ReadAsStringAsync();
                if (result == null)
                {
                    return default;
                }
                return result.ToJson<T>();
            }
            finally
            {
                if (content != null)
                {
                    content.Dispose();
                }
            }
        }
   #region 二进制内容进行sha256
        private static string GetHash(byte[] bytes)
        {
            if (bytes == null) throw new ArgumentNullException(nameof(bytes));

            using SHA256 sha = SHA256.Create();
            byte[] hashBytes = sha.ComputeHash(bytes);
            return BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
        }

四,敏感信息加密

使用获取到的证书certificateModel,进行加密

    public static class RSAUtility
    {
        public static string RSAEncrypt(string text, Certificates certificateModel)
        {
            var bytes = Encoding.UTF8.GetBytes(certificateModel.EncryptCertificate.Ciphertext);
            using var x509 = new X509Certificate2(bytes);
            var rsaParam = x509.GetRSAPublicKey().ExportParameters(false);
            var rsa = new RSACryptoServiceProvider();
            rsa.ImportParameters(rsaParam);
            var buff = rsa.Encrypt(Encoding.UTF8.GetBytes(text), true);
            return Convert.ToBase64String(buff);
        }

    }

五,查询进件状态

直接使用进件返回的Id,调用接口查询就Ok了


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK