.Net微信服务商平台ApiV3接口

Posted who is that

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了.Net微信服务商平台ApiV3接口相关的知识,希望对你有一定的参考价值。

  最近做个对接微信服务商平台的小程序项目,大概要实现的流程是:a)特约商户进件 > b)生成带参数的小程序码 > c)小程序支付 > d)分账,记录一下,希望能对需要的朋友有所帮助

开始

在开始之前建议仔细读微信官方文档,接口规则及api文档

https://pay.weixin.qq.com/wiki/doc/apiv3_partner/wechatpay/wechatpay-1.shtml

https://pay.weixin.qq.com/wiki/doc/apiv3_partner/index.shtml

目录

整个流程开发步骤如下:

一、(签名)

二、(获取证书、敏感信息加密)

三、(上传图片)

四、(特约商户进件)

五、(生成小程序码)

六、(微信小程序支付)

七、(分账)

正文

在开始之前请确保你已经获取商户号、证书、秘钥、小程序appid、appsecret

一、签名

文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_0.shtml

1、生成签名

/// <param name="url">微信的接口地址</param>
        /// <param name="method">请求的方式GET,POST,PUT</param>
        /// <param name="jsonParame">post请求的数据,json格式  ,get时传空</param>
        /// <param name="privateKey">apiclient_key.pem中的内容,不要-----BEGIN PRIVATE KEY-----  -----END PRIVATE KEY-----</param>
        /// <param name="merchantId">发起请求的商户(包括直连商户、服务商或渠道商)的商户号 mchid</param>
        /// <param name="serialNo">商户证书号</param>
        /// <returns></returns>
        protected string GetAuthorization(string url, string method, string jsonParame, string privateKey, string merchantId, string serialNo)
        {
            var uri = new Uri(url);
            string urlPath = uri.PathAndQuery;
            string nonce = Guid.NewGuid().ToString();
            var timestamp = DateTimeOffset.Now.ToUnixTimeSeconds();
            //数据签名     HTTP请求方法\\n接口地址的url\\n请求时间戳\\n请求随机串\\n请求报文主体\\n
            method = string.IsNullOrEmpty(method) ? "" : method;
            string message = string.Format("{0}\\n{1}\\n{2}\\n{3}\\n{4}\\n", method, urlPath, timestamp, nonce, jsonParame);
            string signTxt = Sign(message, privateKey);

            //Authorization和格式
            string authorzationTxt = string.Format("WECHATPAY2-SHA256-RSA2048 mchid=\\"{0}\\",nonce_str=\\"{1}\\",timestamp=\\"{2}\\",serial_no=\\"{3}\\",signature=\\"{4}\\"",
                merchantId,
                nonce,
                timestamp,
                serialNo,
                signTxt
                );
            return authorzationTxt;
        }

protected string Sign(string message, 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));
            }
        }

 2、放到请求头

string Authorization = GetAuthorization(url, method, postData, privateKey, merchantId, serialNo);
request.Headers.Add("Authorization", Authorization);

3、完整的请求方法

/// <param name="url">微信的接口地址</param>
/// <param name="postData">post请求的数据,json格式 </param>
/// <param name="privateKey">apiclient_key.pem中的内容,不要-----BEGIN PRIVATE KEY-----  -----END PRIVATE KEY-----</param>
/// <param name="merchantId">发起请求的商户(包括直连商户、服务商或渠道商)的商户号 mchid</param>
/// <param name="serialNo">商户证书号</param>
/// <returns></returns>
public string postJson(string url, string postData, string privateKey, string merchantId, string serialNo, string method = "POST")
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.Method = method;
            request.ContentType = "application/json;charset=UTF-8";
            request.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3100.0 Safari/537.36";
            request.Accept = "application/json";

            string Authorization = GetAuthorization(url, method, postData, privateKey, merchantId, serialNo);
            request.Headers.Add("Authorization", Authorization);
            if (!string.IsNullOrEmpty(postData))
            {
                byte[] paramJsonBytes;
                paramJsonBytes = System.Text.Encoding.UTF8.GetBytes(postData);
                request.ContentLength = paramJsonBytes.Length;
                Stream writer;
                try
                {
                    writer = request.GetRequestStream();
                }
                catch (Exception)
                {
                    writer = null;
                    Console.Write("连接服务器失败!");
                }
                writer.Write(paramJsonBytes, 0, paramJsonBytes.Length);
                writer.Close();
            }

            HttpWebResponse response;
            try
            {
                response = (HttpWebResponse)request.GetResponse();
            }
            catch (WebException ex)
            {
                response = ex.Response as HttpWebResponse;
            }
            Stream resStream = response.GetResponseStream();
            StreamReader reader = new StreamReader(resStream);
            string text = reader.ReadToEnd();
            return text;
        }

二、获取证书、敏感信息加密

调用特约商户进件接口之前需要做三个工作:获取证书、敏感信息加密、上传图片

获取证书的目的是敏感信息加密需要用证书里解密得到的pubkey,然后用pubkey去对敏感信息进行加密

1、获取证书

获取证书接口比较简单,直接调用上边的请求方法

public static certModel GetCert()
{
    string url = "https://api.mch.weixin.qq.com/v3/certificates";
    string merchantId = WxPayConfig.MCHID;   //商户号
    string serialNo = WxPayConfig.SERIAL_NO;  //证书编号
    string privateKey = WxPayConfig.PRIVATEKEY; // NOTE: 私钥不包括私钥文件起始的-----BEGIN PRIVATE KEY----- 亦不包括结尾的-----END PRIVATE KEY-----

    string transactionsResponse = postJson(url, string.Empty, privateKey, merchantId, serialNo,"GET");
    var result = JsonConvert.DeserializeObject<certModel>(transactionsResponse);
    return result;      
}
GetCert()

用到的model

public class certModel
    {
        public List<Data> data { get; set; }
    }

    public class Data
    {
        public string serial_no { get; set; }
        public string effective_time { get; set; }
        public string expire_time { get; set; }
        public Encrypt_certificate encrypt_certificate { get; set; }

    }
    public class Encrypt_certificate
    {
        public string algorithm { get; set; }
        public string nonce { get; set; }
        public string associated_data { get; set; }
        public string ciphertext { get; set; }
    }
certModel

调用成功直接返回证书list,我们需要用v3秘钥解密得到公钥

var cmodel = GetCert().data.OrderByDescending(t => t.expire_time).FirstOrDefault();
string pubkey = AesGcmHelper.AesGcmDecrypt(cmodel.encrypt_certificate.associated_data, cmodel.encrypt_certificate.nonce, cmodel.encrypt_certificate.ciphertext);
pubkey = pubkey.Replace("-----BEGIN CERTIFICATE-----", "").Replace("-----END CERTIFICATE-----", "");

//解密方法
public class AesGcmHelper
    {
        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 = WxPayConfig.V3KEY;//你的v3秘钥

        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);
        }
    }
pubkey

2、敏感信息加密

我们上一步得到了pubkey,然后对一些敏感信息字段(如用户的住址、银行卡号、手机号码等)进行加密

//text 为要加密的字段值
RSAEncrypt(text, UTF8Encoding.UTF8.GetBytes(pubkey));

public static string RSAEncrypt(string text, byte[] publicKey)
        {
            using (var x509 = new X509Certificate2(publicKey))
            {
                using (var rsa = (RSACryptoServiceProvider)x509.PublicKey.Key)
                {
                    var buff = rsa.Encrypt(Encoding.UTF8.GetBytes(text), RSAEncryptionPadding.OaepSHA1);

                    return Convert.ToBase64String(buff);
                }
            }
        }
RSAEncrypt

这一步需要注意

//使用OaepSHA1
var buff = rsa.Encrypt(Encoding.UTF8.GetBytes(text), RSAEncryptionPadding.OaepSHA1);

三、上传图片

特约商户进件需要上传身份证、营业执照、银行卡等,这就需要通过图片上传API预先生成MediaID

先看接口文档https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter2_1_1.shtml

我封装了一个方法,直接上代码

public string UploadImg(string imgPath)
        {
            string filePath = HttpContext.Current.Server.MapPath(imgPath);
            var filename = Path.GetFileName(filePath);
            FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
            Byte[] imgBytesIn = new Byte[fs.Length];
            fs.Read(imgBytesIn, 0, imgBytesIn.Length);
            fs.Close();

            byte[] hash = SHA256Managed.Create().ComputeHash(imgBytesIn);

            StringBuilder builder = new StringBuilder();
            for (int i = 0; i < hash.Length; i++)
            {
                builder.Append(hash[i].ToString("x2"));
            }
            var sha256 = builder.ToString();
            string metaStr = "{\\"filename\\":\\""+ filename + "\\",\\"sha256\\":\\"" + sha256 + "\\"}";
            string media_id = UploadImgApi(metaStr, imgBytesIn, filename);
            return media_id;
        }

public static string UploadImgApi(string metaStr, Byte[] imgBytesIn,string filename)
        {
            string url = "https://api.mch.weixin.qq.com/v3/merchant/media/upload";
            
            string merchantId = WxPayConfig.MCHID;   //商户号
            string serialNo = WxPayConfig.SERIAL_NO;  //证书编号
            string privateKey = WxPayConfig.PRIVATEKEY;
            #region 定义请求体中的内容 并转成二进制

            string boundary = "lc199aecd61b4653ef";
            string Enter = "\\r\\n";
            string campaignIDStr1
                = "--" + boundary
                + Enter
                + "Content-Disposition: form-data; name=\\"meta\\";"
                + Enter
                + "Content-Type:application/json;"
                + Enter
                + Enter
                + metaStr
                + Enter
                + "--" + boundary
                + Enter
                + "Content-Disposition:form-data;name=\\"file\\";filename=\\""+ filename + "\\";"
                + Enter
                + "Content-Type:image/jpeg"
                + Enter
                + Enter;
            byte[] byteData2
                = imgBytesIn;
            string campaignIDStr3 
                = Enter
                + "--" + boundary 
                + Enter;
            var byteData1 = System.Text.Encoding.UTF8.GetBytes(campaignIDStr1);

            var byteData3 = System.Text.Encoding.UTF8.GetBytes(campaignIDStr3);
            #endregion

            string transactionsResponse = UploadImg_postJson(url, byteData1, byteData2, byteData3, metaStr, privateKey, merchantId, serialNo, boundary, "POST");
            var result=JsonConvert.DeserializeObject<uploadModel>(transactionsResponse);
            Thread.Sleep(500);
            return result.media_id;
        }

public class uploadModel
    {
        public string media_id { get; set; }
    }
UploadImg

上传图片api需要注意请求主体类型、参与签名的字符串及body格式

我又单独写了个请求方法

public string UploadImg_postJson(string url, byte[] b1, byte[] b2, byte[] b3, string metaStr, string privateKey, string merchantId, string serialNo, string boundary, string method = "POST")
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.Method = method;
            //request.ContentType = "application/json;charset=UTF-8";
            request.ContentType = "multipart/form-data;boundary=" + boundary;
            request.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3100.0 Safari/537.36";
            request.Accept = "application/json";
            string Authorization = GetAuthorization(url, method, metaStr, privateKey, merchantId, serialNo);
            request.Headers.Add("Authorization", Authorization);

            Stream writer;
            try
            {
                writer = request.GetRequestStream();
            }
            catch (Exception)
            {
                writer = null;
            }
            writer.Write(b1, 0, b1.Length);
            writer.Write(b2, 0, b2.Length);
            writer.Write(b3, 0, b3.Length);
            writer.Close();

            HttpWebResponse response;
            try
            {
                response = (HttpWebResponse)request.GetResponse();
            }
            catch (WebException ex)
            {
                response = ex.Response as HttpWebResponse;
            }
            Stream resStream = response.GetResponseStream();
            StreamReader reader = new StreamReader(resStream);
            string text = reader.ReadToEnd();
            return text;
        }
UploadImg_postJson

最终返回media_id,存入合适的位置,方便使用

{
"media_id": "H1ihR9JUtVj-J7CJqBUY5ZOrG_Je75H-rKhTG7FUmg9sxNTbRN54dFiUHnhg
rBQ6EKeHoGcHTJMHn5TAuLVjHUQDBInSWXcIHYXOeRa2OHA"
}

四、特约商户进件

上边步骤通了之后,到这就很简单了,这一步的主要难点是请求参数太多了,很容易出错

注意:

• 商户上送敏感信息时使用微信支付平台公钥加密,证书序列号包含在请求HTTP头部的Wechatpay-Seria

需要在请求接口helder里加入Wechatpay-Seria

request.Headers.Add("Wechatpay-Serial", serial_no);

另外一点注意的这里的serial_no是GetCert()接口里获取到的serial_no

调用成功返回微信支付申请单号

{
  "applyment_id": 2000002124775691
}

这一步完成之后可以拿到applyment_id请求查询申请单接口获取结果

成功返回sign_url签约连接发给特约商户相应负责人完成签约,即结束商户进件流程

 五、生成带参数的小程序码

特约商户签约完成之后,服务商平台便可以代商户发起收款,此时我们需要分别给不同的商户生成不同的收款码,其实只需要传入商家的id即可区别处理

//storeid是商家唯一Id
public static string CreateQR(string storeid)
        {
            {
                var page = "pages/custom/index";//扫码打开页面
                var scene = storeid;//参数
                //获取小程序的appid和secret
                var appId = WxPayConfig.XCXAPPID;
                var secret = WxPayConfig.XCXKEY;
                string result = HttpGet($"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appId}&secret={secret}");

                tokenModel rb = JsonConvert.DeserializeObject<tokenModel>(result);

                var url = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" + rb.access_token;

                var strUrl = url;
                var request = (HttpWebRequest)WebRequest.Create(strUrl);
                request.Method = "POST";
                request.ContentType = "application/json;charset=UTF-8";
                var data = new JsonData { ["page"] = page, ["scene"] = scene };

                string jso = data.ToJson();
                var payload = Encoding.UTF8.GetBytes(jso);
                request.ContentLength = payload.Length;
                var writer = request.GetRequestStream();
                writer.Write(payload, 0, payload.Length);
                writer.Close();
                var response = (HttpWebResponse)request.GetResponse();
                var s = response.GetResponseStream();

                var qrCodeImgBase64 = StreamToBytes(s);

                //将流保存
                string filename= storeid + ".png";
                string returnpath= "/UpLoadFiles/StoreQR/" + filename;
                string filepath = HttpContext.Current.Server.MapPath("/UpLoadFiles/StoreQR/") + filename;
                System.IO.File.WriteAllBytes(filepath, qrCodeImgBase64);
                return returnpath;
            }
        }
CreateQR

六、小程序支付JSAPI下单接口

有了商家码就能分别为商家发起支付申请,签约商户都有一个分配的商户号sub_mchid,注意请求参数里的商户号即这个,需要根据二维码的参数去获取

public static string V3Pay(string sub_mchid,string openid,int amount,string ordernumber,string description)
        {
            string url = "https://api.mch.weixin.qq.com/v3/pay/partner/transactions/jsapi";
            
            string appid = WxPayConfig.XCXAPPID;
            string merchantId = WxPayConfig.MCHID;   //商户号
            string serialNo = WxPayConfig.SERIAL_NO;  //证书编号
            string privateKey = WxPayConfig.PRIVATEKEY; // NOTE: 私钥不包括私钥文件起始的-----BEGIN PRIVATE KEY----- 亦不包括结尾的-----END PRIVATE KEY-----
            
            WxPayData postData = new WxPayData();
            postData.SetValue("sp_appid", appid);
            postData.SetValue("sp_mchid", merchantId);
            postData.SetValue("sub_mchid", sub_mchid);
            postData.SetValue("description", description);
            postData.SetValue("out_trade_no", ordernumber);
            postData.SetValue("notify_url", WxPayConfig.NOTIFY_URL);
            WxPayData settle_info = new WxPayData();
            settle_info.SetValue("profit_sharing",true);
            postData.SetValue("settle_info", settle_info);
            WxPayData _amount = new WxPayData();
            _amount.SetValue("total", amount);
            _amount.SetValue("currency", "CNY");
            postData.SetValue("amount", _amount);
            WxPayData payer = new WxPayData();
            payer.SetValue("sp_openid", openid);
            postData.SetValue("payer", payer);

            var postJson = postData.ToJsonFor();
            string result = postJson(url, postJson, privateKey, merchantId, serialNo, "POST");
            var result = JsonConvert.DeserializeObject<payModel>(result);
            return result.prepay_id;
        }
V3Pay

请求参数按照自己的方法去构建,json格式

通过JSAPI下单接口获取到发起支付的必要参数prepay_id,然后使用微信支付提供的小程序方法调起小程序支付

public static string GetJsApiParameters(string prepay_id)
        {
            string appid = WxPayConfig.XCXAPPID;
            string privateKey = WxPayConfig.PRIVATEKEY;
            string timestamp = WxPayApi.GenerateTimeStamp();
            string nonceStr = WxPayApi.GenerateNonceStr();
            string package = "prepay_id=" + prepay_id;
            WxPayData jsApiParam = new WxPayData();
            jsApiParam.SetValue("appId", appid);
            jsApiParam.SetValue("timeStamp", timestamp);
            jsApiParam.SetValue("nonceStr", nonceStr);
            jsApiParam.SetValue("package", package);
            jsApiParam.SetValue("signType", "RSA");
            string message = string.Format("{0}\\n{1}\\n{2}\\n{3}\\n", appid, timestamp, nonceStr, package);
            string signTxt = Sign(message, privateKey);
            jsApiParam.SetValue("paySign", signTxt);

            string parameters = jsApiParam.ToJson();
            

企业微信服务商平台申请

1.打开链接

https://open.work.weixin.qq.com/

2.点申请扫码登录,按引导操作

3.顶部导航区会多出了一个服务商平台

 

4.进入服务商平台,会提示创建一个品牌,就可以创建应用了

 

 

 

以上是关于.Net微信服务商平台ApiV3接口的主要内容,如果未能解决你的问题,请参考以下文章

企业微信服务商平台申请

企业微信服务商平台申请

微信服务商的分账功能总结

企业微信应用和行业方案服务商入门考试答案?

微信服务商下的支付/收款

php微信支付服务商退款要下载啥证书