网上支持微信分账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();
}