.net 微信V3版 商家转账到零钱
自从2022.5.18号开始,微信就关闭了企业付款到零钱,改为了商家转账到零钱,这个是用微信V3版的接口,之前开通的企业付款到零钱 V2接口还是可以使用的。
微信如果返回的批次创建时间字段不为空,则认为批次受理成功,但不代表支付成功,需要主动查询批次的受理状态;
其中开通商家转账到零钱时有个免密支付,默认最高免密金额是100元,如果开通后且支付超过100元 需要商户号上手动点确认支付才可以完成转账,如果商家不确认 会被默认关闭,所以就需要服务器主动向微信查询受理的状态
下发有根据批次单号 查询状态的示例。
微信官网文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter4_3_1.shtml
/// <summary>
/// 商家转账到零钱
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
public WithDrawsV3Back WithDrawsToWx(WithDrawsToWxDto dto)
{
var config = CommonCode.AppSettings("WeiXinMinAppid", "MchId", "SerialNo", "PrivateKey");
var totalFee = Convert.ToInt32(dto.Amount * 100);
var desc = "提现收益";
var lis = new List<object>();
lis.Add(new
{
out_detail_no = dto.PartnerTradeNo,
transfer_amount = totalFee,
transfer_remark = desc,
openid = dto.OpenId
});
var body = new
{
appid = config[0],
out_batch_no = dto.PartnerTradeNo,
batch_name = desc,
batch_remark = desc,
total_amount = totalFee,
total_num = 1,
transfer_detail_list = lis
};
var url = "https://api.mch.weixin.qq.com/v3/transfer/batches";
string transactionsResponse = WxV3PostJson(url, Newtonsoft.Json.JsonConvert.SerializeObject(body), config[3], config[1], config[2], "POST");
//_log.LogInformation("商户转账到零钱返回:" + transactionsResponse);
var result = Newtonsoft.Json.JsonConvert.DeserializeObject<WithDrawsV3Back>(transactionsResponse);
return result;
}
/// <summary>
/// 根据微信批次单号 查询转账状态
/// </summary>
/// <param name="batch_id"></param>
/// <returns></returns>
public WithDrawsStateRet QryWithDrawsState(string batch_id)
{
var config = CommonCode.AppSettings("WeiXinMinAppid", "MchId", "SerialNo", "PrivateKey");
var url = "https://api.mch.weixin.qq.com/v3/transfer/batches/batch-id/" + batch_id + "?need_query_detail=false&offset=1";
string response = WxV3PostJson(url, string.Empty, config[3], config[1], config[2], "GET");
_log.LogInformation("根据微信批次单号 查询转账状态返回:" + response);
var result = Newtonsoft.Json.JsonConvert.DeserializeObject<WithDrawsStateRet>(response);
return result;
}
/// <summary>
/// V3版本请求接口
/// </summary>
/// <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>
/// <param name="method"></param>
/// <returns></returns>
public string WxV3PostJson(string url, string postData, string privateKey, string merchantId, string serialNo, string method)
{
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);
string value = $"WECHATPAY2-SHA256-RSA2048 {Authorization}";
request.Headers.Add("Authorization", value);
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 e)
{
writer = null;
_log.LogInformation("请求接口失败:" + e.Message);
}
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>
/// 获取authorzation 值
/// </summary>
/// <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接口地址的urln请求时间戳n请求随机串n请求报文主体n
method = string.IsNullOrEmpty(method) ? "" : method;
string message = $"{method}\n{urlPath}\n{timestamp}\n{nonce}\n{jsonParame}\n";
string signTxt = Sign(message, privateKey);
//Authorization和格式
string authorzationTxt = $"mchid=\"{merchantId}\",nonce_str=\"{nonce}\",timestamp=\"{timestamp}\",serial_no=\"{serialNo}\",signature=\"{signTxt}\""; ;
return authorzationTxt;
}
/// <summary>
/// 获取签名
/// </summary>
protected string Sign(string message, string privateKey)
{
byte[] keyData = Convert.FromBase64String(privateKey);
var rsa = RSA.Create();
rsa.ImportPkcs8PrivateKey(keyData, out _);
byte[] data = Encoding.UTF8.GetBytes(message);
return Convert.ToBase64String(rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1));
}
public class WithDrawsV3Back
{
/// <summary>
/// 微信批次单号
/// </summary>
public string batch_id { get; set; }
/// <summary>
/// 商家批次单号
/// </summary>
public string out_batch_no { get; set; }
public string create_time { get; set; }
public string code { get; set; }
public string message { get; set; }
}
/// <summary>
/// 根据微信批次单号 查询转账状态返回
/// </summary>
public class WithDrawsStateRet
{
/// <summary>
/// 转账批次单
/// </summary>
public transfer_batch transfer_batch { get; set; }
/// <summary>
/// 转账明细单列表
/// </summary>
public object transfer_detail_list { get; set; }
}
public class transfer_batch
{
public string mchid { get; set; }
public string out_batch_no { get; set; }
public string batch_id { get; set; }
public string appid { get; set; }
public string batch_status { get; set; }
public string batch_type { get; set; }
public string batch_name { get; set; }
public string close_reason { get; set; }
public int total_amount { get; set; }
public int total_num { get; set; }
public string create_time { get; set; }
public string update_time { get; set; }
public int success_amount { get; set; }
public int success_num { get; set; }
public int fail_amount { get; set; }
public int fail_num { get; set; }
}