前言
首先要开通小程序的支付能力(https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_0.shtml),按要求提交审核材料,审核通过后,我们可以得到APPID、微信支付商户号mch_id、API密钥key、Appsecret。之后在商户后台绑定同一主体的APPID并授权,发起授权后,商户需要自行前往对应平台确认授权申请。最后在商户后台设置回调地址。
一、HttpHandler
public class HttpHandler : DelegatingHandler
{
private readonly string merchantId;
private readonly string serialNo;
public HttpHandler(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);
request.Headers.Add("User-Agent", "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Mobile Safari/537.36");
request.Headers.Add("Accept", "application/json");
return await base.SendAsync(request, cancellationToken);
}
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;
DateTime DateStart = new DateTime(1970, 1, 1, 8, 0, 0);
var timestamp = Convert.ToInt32((DateTime.Now - DateStart).TotalSeconds);
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 string Sign(string message)
{
string privateKey = File.ReadAllText(@"" + PathHelper.AuthPath + this.merchantId.Trim() + ".txt", Encoding.UTF8).ToString().Trim();
byte[] bt = Encoding.UTF8.GetBytes(message);
var sha256 = new SHA256CryptoServiceProvider();
byte[] rgbHash = sha256.ComputeHash(bt);
RSACryptoServiceProvider key = new RSACryptoServiceProvider();
var _privateKey = RSAPrivateKeyJava2DotNet(privateKey);
key.FromXmlString(_privateKey);
RSAPKCS1SignatureFormatter formatter = new RSAPKCS1SignatureFormatter(key);
formatter.SetHashAlgorithm("SHA256");//此处是你需要加签的hash算法,需要和上边你计算的hash值的算法一致,不然会报错。
byte[] inArray = formatter.CreateSignature(rgbHash);
var signature = Convert.ToBase64String(inArray);
return signature;
}
public static string RSAPrivateKeyJava2DotNet(string privateKey)
{
var privateKeyParam = (RsaPrivateCrtKeyParameters)PrivateKeyFactory.CreateKey(Convert.FromBase64String(privateKey));
return
string.Format(
"<RSAKeyValue><Modulus>{0}</Modulus><Exponent>{1}</Exponent><P>{2}</P><Q>{3}</Q><DP>{4}</DP><DQ>{5}</DQ><InverseQ>{6}</InverseQ><D>{7}</D></RSAKeyValue>",
Convert.ToBase64String(privateKeyParam.Modulus.ToByteArrayUnsigned()),
Convert.ToBase64String(privateKeyParam.PublicExponent.ToByteArrayUnsigned()),
Convert.ToBase64String(privateKeyParam.P.ToByteArrayUnsigned()),
Convert.ToBase64String(privateKeyParam.Q.ToByteArrayUnsigned()),
Convert.ToBase64String(privateKeyParam.DP.ToByteArrayUnsigned()),
Convert.ToBase64String(privateKeyParam.DQ.ToByteArrayUnsigned()),
Convert.ToBase64String(privateKeyParam.QInv.ToByteArrayUnsigned()),
Convert.ToBase64String(privateKeyParam.Exponent.ToByteArrayUnsigned()));
}
}
二、jsapi统一下单
[HttpPost]
[Route("jsapi")]
public async Task<dynamic> JsapiPay(dynamic pay)
{
//string pay1 = "{\"appid\":\"\",\"mchid\":\"\",\"description\":\"\",\"out_trade_no\":\"\",\"notify_url\":\"\",\"amount\":{\"total\":,\"currency\":\"CNY\",},\"payer\":{\"openid\":\"\"}}\"";;
var payPara = JsonConvert.DeserializeObject<dynamic>(JsonConvert.SerializeObject(pay));
MerchantModel merchantModel = MerchantBLL.GetMerchant(null, null, null, Convert.ToString(payPara.mchid));
var httpHandler = new HttpHandler(merchantModel.merchantNum.Trim(), merchantModel.merchantAuthNum.Trim());
HttpClient client = new HttpClient(httpHandler);
var bodyJson = new StringContent(JsonConvert.SerializeObject(pay), Encoding.UTF8, "application/json");
var resp2 = await client.PostAsync("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi", bodyJson);
var resp3 = await resp2.Content.ReadAsStringAsync();
DateTime DateStart = new DateTime(1970, 1, 1, 8, 0, 0);
var timestamp = Convert.ToInt64((DateTime.Now - DateStart).TotalSeconds).ToString();
var nonceStr = Path.GetRandomFileName();
string message = $"{payPara.appid}\n{timestamp}\n{nonceStr}\nprepay_id={JsonHelper.GetJsonValue(resp3, "prepay_id")}\n";
string privateKey = File.ReadAllText(@"" + PathHelper.AuthPath + merchantModel.merchantNum.Trim() + ".txt", Encoding.UTF8).ToString().Trim();
byte[] bt = Encoding.UTF8.GetBytes(message);
var sha256 = new SHA256CryptoServiceProvider();
byte[] rgbHash = sha256.ComputeHash(bt);
RSACryptoServiceProvider key = new RSACryptoServiceProvider();
var _privateKey = RSAPrivateKeyJava2DotNet(privateKey);
key.FromXmlString(_privateKey);
RSAPKCS1SignatureFormatter formatter = new RSAPKCS1SignatureFormatter(key);
formatter.SetHashAlgorithm("SHA256");//此处是你需要加签的hash算法,需要和上边你计算的hash值的算法一致,不然会报错。
byte[] inArray = formatter.CreateSignature(rgbHash);
var signature = Convert.ToBase64String(inArray);
return "{\"signature\":\""+ signature + "\",\"timestamp\":\"" + timestamp + "\",\"nonceStr\":\"" + nonceStr + "\",\"prepay_id\":\"" + JsonHelper.GetJsonValue(resp3, "prepay_id") + "\"}";
}
二、查询订单
[HttpGet]
[Route("get")]
public async Task<dynamic> GetPay(string mchid, string transaction_id = null, string out_trade_no = null)
{
MerchantModel merchantModel = MerchantBLL.GetMerchant(null, null, null, Convert.ToString(mchid));
HttpClient client = new HttpClient(new HttpHandler(merchantModel.merchantNum.Trim(), merchantModel.merchantAuthNum.Trim()));
if (transaction_id != null)
{
//微信支付订单号查询
var res1 = await client.GetAsync("https://api.mch.weixin.qq.com/v3/pay/transactions/id/" + transaction_id + "?mchid=" + mchid);
return res1;
}
else
{
//商户订单号查询
var res2 = await client.GetAsync("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/" + out_trade_no + "?mchid=" + mchid);
return res2;
}
}
三、关闭订单
[HttpPost]
[Route("close")]
public async Task<dynamic> ClosePay(dynamic pay, string out_trade_no)
{
//1、商户订单支付失败需要生成新单号重新发起支付,要对原订单号调用关单,避免重复支付;
//2、系统下单后,用户支付超时,系统退出不再受理,避免用户继续,请调用关单接口。
var payPara = JsonConvert.DeserializeObject<dynamic>(JsonConvert.SerializeObject(pay));
MerchantModel merchantModel = MerchantBLL.GetMerchant(null, null, null, Convert.ToString(payPara.mchid));
HttpClient client = new HttpClient(new HttpHandler(merchantModel.merchantNum.Trim(), merchantModel.merchantAuthNum.Trim()));
var bodyJson = new StringContent(JsonConvert.SerializeObject(pay), Encoding.UTF8, "application/json");
var res = await client.PostAsync("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/" + out_trade_no + "/close", bodyJson);
return res;//无数据(Http状态码为204)
}
四、退款
[HttpPost]
[Route("refunds")]
public async Task<dynamic> RefundsPay(dynamic pay)
{
var payPara = JsonConvert.DeserializeObject<dynamic>(JsonConvert.SerializeObject(pay));
MerchantModel merchantModel = MerchantBLL.GetMerchant(null, null, null, Convert.ToString(payPara.mchid));
HttpClient client = new HttpClient(new HttpHandler(merchantModel.merchantNum.Trim(), merchantModel.merchantAuthNum.Trim()));
var bodyJson = new StringContent(JsonConvert.SerializeObject(pay), Encoding.UTF8, "application/json");
var res = await client.PostAsync("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds", bodyJson);
return res;
}
五、通知回调(注意解析参数格式)
[HttpPost]
[Route("notify")]
public dynamic NotifyPay()
{
HttpRequest httpRequest = HttpContext.Current.Request;
string id = httpRequest.QueryString["id"].ToString().Trim();
string create_time = httpRequest.QueryString["return_msg"].ToString().Trim();
string resource_type = httpRequest.QueryString["resource_type"].ToString().Trim();
string event_type = httpRequest.QueryString["event_type"].ToString().Trim();
string summary = httpRequest.QueryString["summary"].ToString().Trim();
string resource = httpRequest.QueryString["resource"].ToString().Trim();
File.AppendAllText(@"" + PathHelper.LogPath + DateTime.Now.ToString("yyyyMMdd") + ".txt", DateTime.Now + " " + "id:" + id + ",create_time:" + create_time + ",resource_type:" + resource_type + ",event_type:" + event_type + ",summary:" + summary + ",resource:" + resource + "\n", Encoding.UTF8);
if (summary == "支付成功")
{
return "{\"code\":200,\"message\":\"SUCCESS\"}";
}
else
{
return "{\"code\":200,\"message\":\"支付失败\"}";
}
}
注意:完全参考微信支付文档仅使用.net 7