.net 6.0微信支付分账功能实现,支持APIv3/添加分账方/分账回退等

网上支持微信分账API v3版本.net 6.0完整比较少,最近有个项目涉及微信分账,自己整理了一下开源完整代码,供需要之人使用。

根据本人经验,可以参考以下四步来:

1. 建议仔细阅读微信官方文档:请求分账API - 分账 | 微信支付商户文档中心

2. 获取微信支付平台证书公钥

3. 签名认证 Authorization

4. 添加分账方/请求分账

直接上代码,可以直接复制使用,部分参数及命名空间移植时自己稍微修改一下。

代码文件1:获取微信支付平台证书公钥类Certificates.cs

using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Net.Http;
using System.Threading.Tasks;

namespace XXX.Utility.Pay.WeChat.ProfitSharing
{
    /// <summary>
    /// 商户平台证书
    /// </summary>
	public class Certificates
	{
        /// <summary>
        /// 获取商户平台证书序列号
        /// </summary>
        /// <returns></returns>
        public static async Task<string> GetSerialNo()
        {
            var serial_no = "";

            var url = "https://api.mch.weixin.qq.com/v3/certificates";
            HttpClient client = new HttpClient(new HttpClientHelper(WeChatConfig.MCHID, WeChatConfig.SERIAL_NO));
            client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
            client.DefaultRequestHeaders.UserAgent.Add(new System.Net.Http.Headers.ProductInfoHeaderValue(new System.Net.Http.Headers.ProductHeaderValue("wechatpayv3demo", "1.1.0")));
            var result = await client.GetAsync(url);
            string data = await result.Content.ReadAsStringAsync();
            JObject obj = (JObject)JsonConvert.DeserializeObject(data);

            // 如果有多个证书,需要添加条件自行处理一下
            foreach (var item in obj["data"])
            {
                var timestamp = item["effective_time"].ToString();
                var algorithm = item["encrypt_certificate"]["algorithm"].ToString();
                var associated_data = item["encrypt_certificate"]["associated_data"].ToString();
                var ciphertext = item["encrypt_certificate"]["ciphertext"].ToString();
                var nonce = item["encrypt_certificate"]["nonce"].ToString();
                var expire_time = item["expire_time"].ToString();
                serial_no = item["serial_no"].ToString();
            }

            return serial_no;
        }

        /// <summary>
        /// 获取商户平台证书公钥
        /// </summary>
        /// <returns></returns>
        public static async Task<string> GetCertificate()
        {
            var public_key = "";

            var url = "https://api.mch.weixin.qq.com/v3/certificates";
            HttpClient client = new HttpClient(new HttpClientHelper(WeChatConfig.MCHID, WeChatConfig.SERIAL_NO));
            client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
            client.DefaultRequestHeaders.UserAgent.Add(new System.Net.Http.Headers.ProductInfoHeaderValue(new System.Net.Http.Headers.ProductHeaderValue("wechatpayv3demo", "1.1.0")));
            var result = await client.GetAsync(url);
            string data = await result.Content.ReadAsStringAsync();
            JObject obj = (JObject)JsonConvert.DeserializeObject(data);

            foreach (var item in obj["data"])
            {
                var effective_time = item["effective_time"].ToString();
                var algorithm = item["encrypt_certificate"]["algorithm"].ToString();
                var associated_data = item["encrypt_certificate"]["associated_data"].ToString();
                var ciphertext = item["encrypt_certificate"]["ciphertext"].ToString();
                var nonce = item["encrypt_certificate"]["nonce"].ToString();
                var expire_time = item["expire_time"].ToString();
                var serial_no = item["serial_no"].ToString();

                // 如果有多个证书,需要添加条件自行处理一下
                public_key = WeChat.ProfitSharing.AesGcmHelper.AesGcmDecrypt(associated_data, nonce, ciphertext);

                // 以下为验签省略,暂时用不到
                // X509Certificate2 x509Cert = new X509Certificate2(Encoding.UTF8.GetBytes(decrypt_data));
            }

            return public_key;
        }
    }
}

代码文件2:AesGcmHelper.cs

using System;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;
using System.Text;

namespace XXX.Utility.Pay.WeChat.ProfitSharing
{
    /// <summary>
    /// AES-GCM是一种NIST标准的认证加密算法, 是一种能够同时保证数据的保密性、 完整性和真实性的一种加密模式。它最广泛的应用是在TLS中。
    /// 证书和回调报文加解密
    /// </summary>
	public class AesGcmHelper
	{
		public AesGcmHelper()
		{

		}

        /// <summary>
        /// ALGORITHM
        /// </summary>
        private static string ALGORITHM = "AES/GCM/NoPadding";
        /// <summary>
        /// TAG_LENGTH_BIT
        /// </summary>
        private static int TAG_LENGTH_BIT = 128;
        /// <summary>
        /// NONCE_LENGTH_BYTE
        /// </summary>
        private static int NONCE_LENGTH_BYTE = 12;
        /// <summary>
        /// AES_KEY(微信商家后台设置的APIv3密钥,“账户中心”-》“API安全”-》“设置APIv3密钥”)
        /// </summary>
        private static string AES_KEY = "addf392ef46617186045dc6478f5c888";

        /// <summary>
        /// 解密
        /// </summary>
        /// <param name="associatedData">附加数据包(可能为空)</param>
        /// <param name="nonce">加密使用的随机串初始化向量</param>
        /// <param name="ciphertext">Base64编码后的密文</param>
        /// <returns></returns>
        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);
        }
    }
}

代码文件3:签名认证 Authorization.cs

using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;

namespace XXX.Utility.Pay.WeChat.ProfitSharing
{
    /// <summary>
    /// 签名认证-HTTP头参数
    /// </summary>
	public class Authorization
	{
        /// <summary>
        /// 获取签名认证-GET
        /// </summary>
        /// <param name="url">请求URL</param>
        /// <param name="method">请求方法</param>
        /// <param name="mchid">商户ID</param>
        /// <param name="serial_no">商户私钥证书对应的序列号</param>
        /// <returns></returns>
        public static string GetAuthorization(string url, string method, string mchid, string serial_no)
        {
            var uri = new Uri(url);
            string url_path = uri.PathAndQuery;
            string nonce = GetRandomString(32);
            var timestamp = DateTimeOffset.Now.ToUnixTimeSeconds();
            method = string.IsNullOrEmpty(method) ? "" : method;
            // !!!特别注意此处:GET请求后面有两个\n
            string message = string.Format("{0}\n{1}\n{2}\n{3}\n\n", method, url_path, timestamp, nonce);

            string sign = Sign(message);

            // 签名认证格式
            string authorization = string.Format("WECHATPAY2-SHA256-RSA2048 mchid=\"{0}\",nonce_str=\"{1}\",timestamp=\"{2}\",serial_no=\"{3}\",signature=\"{4}\"",
                mchid,
                nonce,
                timestamp,
                serial_no,
                sign
                );

            return authorization;
        }

        /// <summary>
        /// 获取签名认证-POST
        /// </summary>
        /// <param name="url">请求URL</param>
        /// <param name="method">请求方法</param>
        /// <param name="data">数据对象</param>
        /// <param name="mchid">商户ID</param>
        /// <param name="serial_no">商户私钥证书对应的序列号</param>
        /// <returns></returns>
        public static string GetAuthorization(string url, string method, string data, string mchid, string serial_no)
        {
            var uri = new Uri(url);
            string url_path = uri.PathAndQuery;
            string nonce = GetRandomString(32);
            var timestamp = DateTimeOffset.Now.ToUnixTimeSeconds();
            method = string.IsNullOrEmpty(method) ? "" : method;
            var message = string.Format("{0}\n{1}\n{2}\n{3}\n{4}\n", method, url_path, timestamp, nonce, data);

            string sign = Sign(message);

            // 签名认证格式
            string authorization = string.Format("WECHATPAY2-SHA256-RSA2048 mchid=\"{0}\",nonce_str=\"{1}\",timestamp=\"{2}\",serial_no=\"{3}\",signature=\"{4}\"",
                mchid,
                nonce,
                timestamp,
                serial_no,
                sign
                );

            return authorization;
        }

        /// <summary>
        /// 签名
        /// </summary>
        /// <param name="message">需要签名的消息体</param>
        /// <returns></returns>
        public static string Sign(string message)
        {
            // 从证书中读取私钥
            var path = System.Environment.CurrentDirectory + "/wwwroot/apiclient_cert.p12";

            X509Certificate2 cert = new X509Certificate2(path, WeChatConfig.MCHID, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
            var private_key = cert.PrivateKey.ToXmlString(true);

            using (RSACryptoServiceProvider sha256 = new RSACryptoServiceProvider())
            {
                byte[] dataInBytes = Encoding.UTF8.GetBytes(message);
                sha256.FromXmlString(private_key);
                byte[] inArray = sha256.SignData(dataInBytes, CryptoConfig.MapNameToOID("SHA256"));
                string sign = Convert.ToBase64String(inArray);

                return sign;
            }
        }

        /// <summary>
        /// 加密微信支付平台证书序列号
        /// </summary>
        /// <param name="text">微信支付平台证书序列号</param>
        /// <param name="publicKey">公钥</param>
        /// <returns></returns>
        public static string RSAEncrypt(string text, byte[] publicKey)
        {
            var cer = new X509Certificate2(publicKey);
            var rsaParam = cer.GetRSAPublicKey().ExportParameters(false);
            var rsa = new RSACryptoServiceProvider();
            rsa.ImportParameters(rsaParam);
            var buff = rsa.Encrypt(Encoding.UTF8.GetBytes(text), true);

            return Convert.ToBase64String(buff);
        }

        /// <summary>
        /// 生成随机字符串
        /// </summary>
        /// <param name="length">字符串长度</param>
        /// <returns></returns>
        public static string GetRandomString(int length)
        {
            const string key = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";

            Random rnd = new Random();
            byte[] buffer = new byte[8];

            ulong bit = 31;
            ulong result = 0;
            int index = 0;
            StringBuilder sb = new StringBuilder((length / 5 + 1) * 5);

            while (sb.Length < length)
            {
                rnd.NextBytes(buffer);

                buffer[5] = buffer[6] = buffer[7] = 0x00;
                result = BitConverter.ToUInt64(buffer, 0);

                while (result > 0 && sb.Length < length)
                {
                    index = (int)(bit & result);
                    sb.Append(key[index]);
                    result = result >> 5;
                }
            }

            return sb.ToString();
        }
    }
}

代码文件4:HttpClientHelper.cs

using System;
using System.IO;
using System.Net.Http;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using NetPay.WeChat;

namespace MiPeiPei.Utility.Pay.WeChat
{
	public class HttpClientHelper : DelegatingHandler
    {
        /// <summary>
        /// 商户ID
        /// </summary>
        private readonly string merchantId;
        /// <summary>
        /// 商户私钥
        /// </summary>
        private readonly string serialNo;

        public HttpClientHelper(string merchantId, string merchantSerialNo)
        {
            InnerHandler = new HttpClientHandler();

            this.merchantId = merchantId;
            this.serialNo = merchantSerialNo;
        }

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

            var resp = await base.SendAsync(request, cancellationToken);
            string resp_body = await resp.Content.ReadAsStringAsync();

            return resp;
        }

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

            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 static string Sign(string message)
        {
            var path = System.Environment.CurrentDirectory + "/wwwroot/apiclient_cert.p12";
            X509Certificate2 cert = new X509Certificate2(path, Config.MCHID, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
            var privateKey = cert.PrivateKey.ToXmlString(true);

            using (RSACryptoServiceProvider sha256 = new RSACryptoServiceProvider())
            {
                byte[] dataInBytes = Encoding.UTF8.GetBytes(message);
                sha256.FromXmlString(privateKey);
                byte[] inArray = sha256.SignData(dataInBytes, CryptoConfig.MapNameToOID("SHA256"));
                string sign = Convert.ToBase64String(inArray);
                return sign;
            }
        }
    }
}

业务代码1:请求分账PSOrders.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace XXX.Utility.Pay.WeChat.ProfitSharing
{
    /// <summary>
    /// 处理分账:请求分账/查询分账/请求分账回退/分账回退查询
    /// </summary>
	public class PSOrders
	{

        /// <summary>
        /// 请求分账
        /// </summary>
        /// <param name="transaction_id">微信订单号</param>
        /// <param name="out_order_no">商户分账单号</param>
        /// <param name="receivers">分账接收方列表</param>
        /// <returns></returns>
        public static string AddProfitSharing(string transaction_id, string out_order_no, object receivers)
        {
            // 获取微信支付平台证书公钥
            var certificate = Certificates.GetCertificate();
            var public_key = certificate.Result;

            SortedDictionary<string, object> dic = new SortedDictionary<string, object>();
            dic.Add("appid", WeChatConfig.APPID);
            dic.Add("transaction_id", transaction_id);
            dic.Add("out_order_no", out_order_no);
            dic.Add("receivers", receivers);
            dic.Add("unfreeze_unsplit", true); // 是否解冻剩余未分资金
            var data = Newtonsoft.Json.JsonConvert.SerializeObject(dic);

            // 请求分账API
            var url = "https://api.mch.weixin.qq.com/v3/profitsharing/orders";
            // 通过证书获取商户私钥序列号
            var path = System.Environment.CurrentDirectory + "/wwwroot/apiclient_cert.p12";
            X509Certificate2 cert = new X509Certificate2(path, WeChatConfig.MCHID, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
            var serial_no = cert.SerialNumber;

            var result = RequestAddProfitSharingData(url, data, WeChatConfig.MCHID, serial_no, public_key);

            return result;
        }

        /// <summary>
        /// 发送分账请求
        /// </summary>
        /// <param name="url">URL</param>
        /// <param name="data">数据对象</param>
        /// <param name="mchid">商户ID</param>
        /// <param name="serial_no">商户私钥序列号</param>
        /// <param name="public_key">微信平台公钥</param>
        /// <returns></returns>
        public static string RequestAddProfitSharingData(string url, string data, string mchid, string serial_no, string public_key)
        {
            var wechatpay_serial_no = Certificates.GetSerialNo().Result;
            byte[] byte_public_key = Encoding.UTF8.GetBytes(public_key);

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.Method = "POST";
            request.ContentType = "application/json;";
            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 = Authorization.GetAuthorization(url, "POST", data, mchid, serial_no);
            request.Headers.Add("Authorization", authorization);
            // !!!如果请求数据中存在敏感信息则需要在Headers添加Wechatpay-Serial,但“wechatpay_serial_no”不需要加密
            request.Headers.Add("Wechatpay-Serial", wechatpay_serial_no);

            byte[] paramJsonBytes;
            paramJsonBytes = Encoding.UTF8.GetBytes(data);
            request.ContentLength = paramJsonBytes.Length;

            Stream writer;
            try
            {
                writer = request.GetRequestStream();
            }
            catch (Exception)
            {
                writer = null;
            }
            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;
        }

        /// <summary>
        /// 查询分账结果
        /// </summary>
        /// <param name="out_order_no">路径参数:商户分账单号</param>
        /// <param name="transaction_id">查询参数:微信订单号</param>
        /// <returns></returns>
        public static string SearchProfitSharing(string out_order_no,string transaction_id)
        {
            // 通过证书获取商户私钥序列号
            var path = System.Environment.CurrentDirectory + "/wwwroot/apiclient_cert.p12";
            X509Certificate2 cert = new X509Certificate2(path, WeChatConfig.MCHID, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
            var serial_no = cert.SerialNumber;

            var url = "https://api.mch.weixin.qq.com/v3/profitsharing/orders/" + out_order_no + "?transaction_id=" + transaction_id;

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.Method = "GET";
            request.ContentType = "application/json;";
            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 = Authorization.GetAuthorization(url, "GET", WeChatConfig.MCHID, serial_no);
            request.Headers.Add("Authorization", authorization);

            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 result = reader.ReadToEnd();

            return result;
        }

        /// <summary>
        /// 请求分账回退
        /// </summary>
        /// <param name="profitsharing_order_id"></param>
        /// <param name="out_order_no"></param>
        /// <param name="out_return_no"></param>
        /// <param name="return_mchid"></param>
        /// <param name="amount"></param>
        /// <param name="description"></param>
        /// <returns></returns>
        public static string ProfitSharingReturn(string profitsharing_order_id, string out_order_no, string out_return_no, string return_mchid,int amount,string description)
        {
            SortedDictionary<string, object> dic = new SortedDictionary<string, object>();
            dic.Add("order_id", profitsharing_order_id);
            dic.Add("out_order_no", out_order_no);
            dic.Add("out_return_no", out_return_no);
            dic.Add("return_mchid", return_mchid);
            dic.Add("amount", amount);
            dic.Add("description", description);
            var data = Newtonsoft.Json.JsonConvert.SerializeObject(dic);

            // 请求分账API
            var url = "https://api.mch.weixin.qq.com/v3/profitsharing/return-orders";
            // 通过证书获取商户私钥序列号
            var path = System.Environment.CurrentDirectory + "/wwwroot/apiclient_cert.p12";
            X509Certificate2 cert = new X509Certificate2(path, WeChatConfig.MCHID, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
            var serial_no = cert.SerialNumber;

            var result = RequestProfitSharingReturnData(url, data, WeChatConfig.MCHID, serial_no);

            return result;
        }

        /// <summary>
        /// 发送请求分账回退
        /// </summary>
        /// <param name="url">URL</param>
        /// <param name="data">数据对象</param>
        /// <param name="mchid">商户ID</param>
        /// <param name="serial_no">商户私钥序列号</param>
        /// <returns></returns>
        public static string RequestProfitSharingReturnData(string url, string data, string mchid, string serial_no)
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.Method = "POST";
            request.ContentType = "application/json;";
            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 = Authorization.GetAuthorization(url, "POST", data, mchid, serial_no);
            request.Headers.Add("Authorization", authorization);
      
            byte[] paramJsonBytes;
            paramJsonBytes = Encoding.UTF8.GetBytes(data);
            request.ContentLength = paramJsonBytes.Length;

            Stream writer;
            try
            {
                writer = request.GetRequestStream();
            }
            catch (Exception)
            {
                writer = null;
            }
            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;
        }


    }
}

业务代码2:添加分账方 Receivers.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Text;

namespace XXX.Utility.Pay.WeChat.ProfitSharing
{
    /// <summary>
    /// 分账接收方相关业务处理:新增/删除
    /// </summary>
	public class Receivers
	{
        /// <summary>
        /// 添加分账接收方
        /// </summary>
        /// <param name="type">接收方类型,MERCHANT_ID:商户ID;PERSONAL_OPENID:个人openid(由父商户APPID转换得到)</param>
        /// <param name="account">接收方账号,类型是MERCHANT_ID时,是商户号;类型是PERSONAL_OPENID时,是个人openid类型是PERSONAL_SUB_OPENID时,是个人sub_openid</param>
        /// <param name="name">分账接收方全称,分账接收方类型是MERCHANT_ID时,是商户全称(必传);当商户是小微商户或个体户时,是开户人姓名;</param>
        /// <param name="relation_type">与分账方的关系类型</param>
        /// <returns></returns>
        public static string AddReceiver(string type, string account, string name, string relation_type)
        {
            // 获取微信支付平台证书公钥
            var certificate = Certificates.GetCertificate();
            var public_key = certificate.Result;

            // 敏感信息加密(分账接收方全称属于敏感信息需要加密)
            byte[] byte_public_key = Encoding.UTF8.GetBytes(public_key);
            var enrypt_name = Authorization.RSAEncrypt(name, byte_public_key);

            SortedDictionary<string, object> dic = new SortedDictionary<string, object>();
            dic.Add("appid", WeChatConfig.APPID);
            dic.Add("type", type);
            dic.Add("account", account);
            dic.Add("name", enrypt_name);
            dic.Add("relation_type", relation_type);
            // dic.Add("custom_relation", "代理商");
            var data = Newtonsoft.Json.JsonConvert.SerializeObject(dic);

            var url = "https://api.mch.weixin.qq.com/v3/profitsharing/receivers/add";
            var path = System.Environment.CurrentDirectory + "/wwwroot/apiclient_cert.p12";
            X509Certificate2 cert = new X509Certificate2(path, WeChatConfig.MCHID, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
            var serial_no = cert.SerialNumber;

            var result = RequestData(url, data, WeChatConfig.MCHID, serial_no, public_key);

            return result;
        }

        /// <summary>
        /// 发送请求
        /// </summary>
        /// <param name="url">URL</param>
        /// <param name="data">数据对象</param>
        /// <param name="mchid">商户ID</param>
        /// <param name="serial_no"></param>
        /// <param name="public_key"></param>
        /// <returns></returns>
        public static string RequestData(string url, string data, string mchid, string serial_no, string public_key)
        {
            var wechatpay_serial_no = "1D99F55A2525B50D10571B129F274FB801769CCC";
            byte[] byte_public_key = Encoding.UTF8.GetBytes(public_key);
 
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.Method = "POST";
            request.ContentType = "application/json;";
            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 = Authorization.GetAuthorization(url, "POST", data, mchid, serial_no);
            request.Headers.Add("Authorization", authorization);
            request.Headers.Add("Wechatpay-Serial", wechatpay_serial_no);

            byte[] paramJsonBytes;
            paramJsonBytes = Encoding.UTF8.GetBytes(data);
            request.ContentLength = paramJsonBytes.Length;

            Stream writer;
            try
            {
                writer = request.GetRequestStream();
            }
            catch (Exception)
            {
                writer = null;
            }
            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;
        }

    }
}

业务代码3:页面添加分账方业务处理

/// <summary>
/// 新增代理商数据
/// </summary>
/// <param name="agents">Agents对象</param>
/// <returns>返回新增代理商后的单条JSON数据</returns>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Agents agents)
        {
            try
            {
                log.Information("agents=" + agents.ToJson());
                agents.user_id = GetUserID();
                agents.created_at = DateTime.Now;
                if (!_context.mpp_agents.Any(t => t.agent_name!.Equals(agents.agent_name)))
                {
                    
                    // 添加代理商至微信分账方
                    try
                    {
                        var relation_code = "";
                        if (agents.relation_type > 0)
                        {
                            relation_code = _context.mpp_agent_relations.FirstOrDefault(t => t.relation_id == agents.relation_type).relation_code;
                        }

                        if (!string.IsNullOrEmpty(agents.profitsharing_account) && !string.IsNullOrEmpty(agents.agent_name))
                        {
                            var type = agents.profitsharing_type == 1 ? "PERSONAL_OPENID" : "MERCHANT_ID";

                            // 添加接收方至微信接口
                            var result = Utility.Pay.WeChat.ProfitSharing.Receivers.AddReceiver(type, agents.profitsharing_account, agents.agent_name, relation_code);
                            log.Warning("result=" + result);
                            if (result.Contains("account"))
                            {
                                // 提交成功返回信息
                                // {"account":"1533356070","custom_relation":"代理商","name":"b645v6m2JN1zoHKJoZ08OhamM7NMvK9/SJB45t1XUQJvagUyfhGVtq527kPjOM8Er/qZWzD4YmPk4DSUk9wqJcyCBk0xYuvixcetfXuysGFJ8+Rm6/lZkVKBr973kwF8ywRfZwfkz3+nWEQw06LfdV8hTGRlmqt1/TlfPJCQaVl5uh8HuX+LJx0w/2wu6p76xfLPhv6X2cWc6oKXwB4QBf0YT0TxF5y4I6R6H0KmactNDeDxjyZbxorzF74QNQoLk8f7UmQoPad93C79qVNWSvEelR/hRX4AF3orTaFFEBmyKu+gC5a6ykPChfNP98XmPX1nevNHn7pnoelmufEaAA==","relation_type":"PARTNER","type":"MERCHANT_ID"}
                                _context.mpp_agents.Add(agents);
                                await _context.SaveChangesAsync();
                                AddLogs((int)ENUMHelper.LogType.Create, (int)ENUMHelper.Platform.Admin + "" + (int)ENUMHelper.OperateType.Create + "" + (int)ENUMHelper.InfoType.Info, "新增代理商,agents=" + agents.ToJson());

                                return Json(new { code = 0, msg = "添加成功", data = agents });
                            }
                            else
                            {
                                // 添加失败
                                return Json(new { code = 0, errcode = (int)ENUMHelper.ExceptionType.Create, errmsg = "添加失败," + result });
                            }
                        }
                        else
                        {
                            return Json(new { code = 0, errcode = (int)ENUMHelper.ExceptionType.Create, errmsg = "添加失败,分账方不能为空!"});
                        }

                    }
                    catch (Exception ex)
                    {
                        log.Fatal(ex, " -> 添加代理商至微信分账方");
                        return Json(new { code = 0, errcode = (int)ENUMHelper.ExceptionType.Create, errmsg = "添加失败" + ex.Message });
                    }
                }
                else
                {
                    return Json(new { code = 0, errcode = (int)ENUMHelper.ExceptionType.Create, errmsg = "添加失败,请不要重复添加!"});
                }
            }
            catch (Exception ex)
            {
                log.Fatal(ex, " -> Create");
                return Json(new { code = 0, errcode = (int)ENUMHelper.ExceptionType.Create, errmsg = ex.Message });
            }
        }

业务代码4:页面订单请求分账时业务处理

/// <summary>
/// 处理订单分账
/// </summary>
/// <param name="order_id">订单ID</param>
/// <returns></returns>
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult ProfitSharing(long order_id)
{
    try
    {
        log.Information("order_id=" + order_id);

        // 处理最高不能超过比例金额
        var agent_profitsharing = _context.mpp_agent_profitsharing.FirstOrDefault(t => t.profitsharing_id == 1);
        if (agent_profitsharing != null)
        {
            if (agent_profitsharing.is_open)
            {
                var max_rate = agent_profitsharing.profitsharing_rate; //分账最高比例
                

                // 1.处理分账逻辑 =======================================================================
                var order = _context.mpp_orders.FirstOrDefault(t => t.order_id == order_id);
                var order_trades = _context.mpp_order_trades.FirstOrDefault(t => t.out_trade_no.Equals(order.out_trade_no));

                // 2.判断医院情况 =======================================================================
                var hospitals = _context.mpp_hospitals.FirstOrDefault(t => t.hospital_id == order.hospital_id);
                if (hospitals.is_profitsharing)
                {
                    // 3. 查询分账方 =======================================================================
                    var total_money = 0; // 分
                    List<Dictionary<string, object>> receivers = new List<Dictionary<string, object>>();// 支持多个分账方
                    var agent_hospitals = _context.mpp_agent_hospitals.Where(t => t.hospital_id == order.hospital_id).ToList();
                    agent_hospitals.ForEach(c =>
                    {
                        // 获取分账方信息
                        var agents = _context.mpp_agents.FirstOrDefault(t => t.agent_id == c.agent_id);
                        if (agents != null)
                        {
                            var type = agents.profitsharing_type == 1 ? "PERSONAL_OPENID" : "MERCHANT_ID";

                            // 处理分账金额
                            var profit_sharing_money = 0;
                            if (order.rule_type_id == 1)
                            {
                                // 按小时计费:结算金额*分账比例
                                double rate = (c.profitsharing_rate / (double)100) * (max_rate / (double)100);
                                double money = (double)order.used_money * 100 * rate;
                                log.Warning("处理分账金额-按小时计费,money=" + money);
                                profit_sharing_money = Convert.ToInt32(money);
                            }
                            else
                            {
                                // 按次计费:分账金额
                                profit_sharing_money = c.profitsharing_money;
                                log.Warning("处理分账金额-按次计费,profit_sharing_money=" + profit_sharing_money);
                            }

                            // 只有代理商关联医院设置为可分账(非医院分账状态)
                            if (c.is_show)
                            {
                                total_money += profit_sharing_money;

                                Dictionary<string, object> receiver = new Dictionary<string, object>();
                                receiver.Add("type", type);
                                receiver.Add("account", agents.profitsharing_account);
                                // receiver.Add("name", enrypt_name);
                                receiver.Add("amount", profit_sharing_money);
                                receiver.Add("description", "分账金额(" + agents.agent_name + ")");
                                receivers.Add(receiver);
                            }
                        }
                    });

                    // 判断单个或多个代理商累加分账金额是否超过最高分账金额
                    var max_money = order.order_money * (decimal)0.3; // 元
                    var max_money_fen = Convert.ToInt32(max_money * 100); // 转成分
                    if (total_money > max_money_fen)
                    {
                        return Json(new { code = 0, errcode = 40005, errmsg = "分账金额("+ total_money + ")已超过此订单最高分账金额("+ max_money_fen + ")!" });
                    }
                    else
                    {
                         处理分账接收方
                        //var certificate = Certificates.GetCertificate();
                        //var public_key = certificate.Result;
                         敏感信息加密(分账接收方全称属于敏感信息需要加密)
                        //byte[] byte_public_key = Encoding.UTF8.GetBytes(public_key);
                        //var enrypt_name = Authorization.RSAEncrypt("李XX", byte_public_key);

                        var transaction_id = order_trades.transaction_id;
                        var out_trade_no = Utility.Helper.CommonHelper.GenerateProfitSharingNo(order_id);
                        var result = PSOrders.AddProfitSharing(order_trades.transaction_id, out_trade_no, receivers);
                        if (result.Contains("state"))
                        {
                            // 分账成功返回信息
                            // {"order_id":"30001100622023110956307821070","out_order_no":"600000024471324827","receivers":[{"account":"oEQuD5MZX55UuIearW1CEq03jKe4","amount":1,"create_time":"2023-11-09T00:48:18+08:00","description":"分账给李XX","detail_id":"36001100622023110980515070967","finish_time":"1970-01-01T08:00:00+08:00","result":"PENDING","type":"PERSONAL_OPENID"},{"account":"1634407701","amount":5,"create_time":"2023-11-09T00:48:18+08:00","description":"解冻给分账方","detail_id":"36001100622023110980515070968","finish_time":"1970-01-01T08:00:00+08:00","result":"PENDING","type":"MERCHANT_ID"}],"state":"PROCESSING","transaction_id":"4200001992202311098498175251"}

                            JObject obj = null;
                            try
                            {
                                obj = (JObject)JsonConvert.DeserializeObject(result);
                            }
                            catch (Exception ex)
                            {
                                return Json(new { code = 0, msg = ex.Message });
                            }

                            if (result.Contains("receivers"))
                            {
                                // 处理分账接收方
                                foreach (var item in obj["receivers"])
                                {
                                    var receiver_account = item["account"].ToString();
                                    var receiver_amount = item["amount"].ToString();
                                    var receiver_create_time = item["create_time"].ToString();
                                    var receiver_description = item["description"].ToString();
                                    var receiver_detail_id = item["detail_id"].ToString();
                                    var receiver_finish_time = item["finish_time"].ToString();
                                    var receiver_result = item["result"].ToString();
                                    var receiver_type = item["type"].ToString();
                                    var fail_reason = "";
                                    if (item.ToString().Contains("fail_reason"))
                                    {
                                        fail_reason = item["fail_reason"].ToString();
                                    }
                                    var receiver_flag = 1;
                                    if (receiver_description.Contains("解冻给分账方"))
                                    {
                                        receiver_flag = 2;
                                    } 

                                    // 添加分账接收方记录
                                    try
                                    {
                                        var agent_name = _context.mpp_agents.FirstOrDefault(t => t.profitsharing_account.Equals(receiver_account)).agent_name;
                                        _context.mpp_profitsharing_receivers.Add(new ProfitsharingReceivers()
                                        {
                                            profitsharing_order_id = obj["order_id"].ToString(),
                                            agent_name = agent_name,
                                            receiver_account = receiver_account,
                                            receiver_amount = Convert.ToInt32(receiver_amount),
                                            receiver_create_time = receiver_create_time,
                                            receiver_description = receiver_description,
                                            receiver_detail_id = receiver_detail_id,
                                            receiver_finish_time = receiver_finish_time,
                                            receiver_result = receiver_result,
                                            receiver_type = receiver_type,
                                            fail_reason= fail_reason,
                                            receiver_flag = receiver_flag,
                                            receiver_remark = "",
                                            created_at = DateTime.Now
                                        });
                                        _context.SaveChanges();
                                    }
                                    catch (Exception ex)
                                    {
                                        log.Fatal(ex, System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name + " -> 添加分账接收方记录");
                                    }
                                }
                            }

                            // 添加分账记录
                            try
                            {
                                _context.mpp_profitsharing.Add(new Profitsharing()
                                {
                                    order_id = order.order_id,
                                    hospital_id = order.hospital_id,
                                    department_id = order.dept_id,
                                    device_id = order.device_id,
                                    order_money = order.order_money,
                                    rent_money = order.rent_money,
                                    actual_money = order.used_money,
                                    profitsharing_rate = max_rate,
                                    app_id = Utility.Pay.WeChat.WeChatConfig.APPID,
                                    profitsharing_order_id = obj["order_id"].ToString(),
                                    out_order_no = obj["out_order_no"].ToString(),
                                    status_id = obj["state"].ToString() == "PROCESSING" ? 1 : 2,
                                    transaction_id = obj["transaction_id"].ToString(),
                                    profitsharing_remark = obj["state"].ToString(),
                                    user_id = GetUserID(),
                                    created_at = DateTime.Now
                                });
                                _context.SaveChanges();
                            }
                            catch (Exception ex)
                            {
                                log.Fatal(ex, System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name + " -> 添加分账记录");
                            }

                            return Json(new { code = 0, msg = "分账成功" });
                        }
                        else
                        {
                            // 订单日志记录-订单分账
                            AddOrderLog(order.order_id, order.member_id, 58, "100580", "订单分账", result);

                            return Json(new { code = 0, errcode = 40005, errmsg = "分账失败," + result });
                        }
                    }
                }
                else
                {
                    return Json(new { code = 0, errcode = 40005, errmsg = "医院分账功能已关闭!" });
                }
            }
            else
            {
                return Json(new { code = 0, errcode = 40005, errmsg = "平台分账功能已关闭!" });
            }
        }
        else
        {
            return Json(new { code = 0, errcode = 40005, errmsg = "分账功能异常,请联系管理员!" });
        }
    }
    catch (Exception ex)
    {
        log.Fatal(ex, System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name + " -> Delete");
        return Json(new { code = 0, errcode = 40005, errmsg = ex.Message });
    }
}

以上为项目实际中的代码,部分命名空间需要自行修改一下,整体业务能跑通,如下截图:

需要注意的一点,前端订单支付需要新增一个是否支持分账,如下代码:

/// <summary>
/// 微信支付统一下单
/// </summary>
/// <param name="open_id">微信小程序OPENID</param>
/// <param name="out_trade_no">支付流水编号</param>
/// <param name="total_fee">支付金额</param>
/// <param name="env">TEST OR PRODUCTION</param>
/// <param name="is_profit_sharing">是否需要分账</param>
/// <returns></returns>
private string GetUnifiedOrderParam(string open_id, string out_trade_no, string total_fee, string env, bool is_profit_sharing)
{
    string appid = WeChatConfig.APPID;
    string secret = WeChatConfig.SERCRET;
    string mch_id = WeChatConfig.MCHID;
    string ip = WeChatConfig.IP;
    string PayResulturl = env == "TEST" ? WeChatConfig.NOTIFY_URL_TEST : WeChatConfig.NOTIFY_URL;

    string strcode = "XXX-设备租赁";商品描述交易字段格式根据不同的应用场景按照以下格式:APP——需传入应用市场上的APP名字-实际商品名称,天天爱消除-游戏充值。
    byte[] buffer = Encoding.UTF8.GetBytes(strcode);
    string body = Encoding.UTF8.GetString(buffer, 0, buffer.Length);
    System.Random Random = new System.Random();

    var dic = new Dictionary<string, string>
        {
            {"appid", appid},
            {"mch_id", mch_id},
            {"nonce_str", GetRandomString(20)},
            {"body", body},
            {"out_trade_no", out_trade_no}, // 商户自己的订单号码
            {"total_fee", total_fee},
            {"spbill_create_ip", ip}, // 服务器的IP地址
            {"notify_url", PayResulturl}, // 异步通知的地址,不能带参数
            {"trade_type", "JSAPI" },
            {"openid", open_id}
        };

    // 如果需要进行分账,则需要添加参数profit_sharing=Y
    if (is_profit_sharing)
    {
        dic.Add("profit_sharing", "Y");
    }

    // 加入签名
    dic.Add("sign", GetSignString(dic));

    var sb = new StringBuilder();
    sb.Append("<xml>");
    foreach (var d in dic)
    {
        sb.Append("<" + d.Key + ">" + d.Value + "</" + d.Key + ">");
    }
    sb.Append("</xml>");
    return sb.ToString();
}

  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值