1、前言
"小魏呀,这个微信支付还要多久?","快了快了老板,就等着最后一步了。。。","搞快点哈,就等着上线呢","........."
因公司业务需要微信支付,以前没弄过花了几天时间写了一个微信v3的JSAPI支付,我滴个乖乖,差点今年小孩的奶粉就没了,还好弄出来了。在这里面各种踩坑,在这里记录一下,我开发的是微信公众号上面拉起微信支付。后台是Core3.1的接口,前端用的是Vue。后面是部署在CentOS上面的
2、写代码之前的准备
你必须要有一个非个人性质的公众号(服务号),还有一个微信商户号。服务号申请地址,微信商户号申请地址,具体的根据网站申请中按人家要求来就行了。个人建议把申请下来的公众号里面的appid 、appsecret,微信商户平台,商户号等数据保存在数据库中。
3、公众号、商户号配置
1)、公众号JS安全域名
登录公众号在左手边菜单:公众号设置---->功能设置------>JS安全域名----->设置。在里面可以连写5个域名下载文件上传到服务器上面 域名要经过ICP备案。可以访问到上面说的那个文件就可以了。
core3.1Api 发布后你放根目录是访问不到的,在configure里面加上访问静态文件 app.UseStaticFiles();然后在根目录建一个文件夹wwwroot 吧域名验证需要的txt文件丢进去 我是这么搞点。暂时没有想到其他骚操作
这里有人要问了 这个设置了是干嘛的,以前我也不知道是干嘛的哈哈,总有一颗好奇的心想知道。现在想想个人理解这个JS安全域名就是一个验证的机制吧。这里设置了加上微信服务号也有一个类似的,后面就可以调用JSAPI支付了。
2)、
这个紧接着在JS安全域名后面 跟着设置一下就可以了 我部署在CentOS上面 看一下文件夹目录,还有一个文件夹里面是是p12文件 后面会提到
这个网页授权意思就是后面要获取到用户的OpenId的时候 要通过这个域名授权。我们就能获取到用户的信息,授权登录这些配置。后面图上还有一个HHhhjZj的文件这个是商户号上面设置的。
3)、微信商户号设置
在微信商户平台上面选择产品中心---->开发配置,这里面设置支付目录。我这里是设置的一个 ,我也不是申请商户号的人 也没有这个权限 。上面的界面跟上面两步骤差不多就不啰嗦了。
4)、微信商户号的key,V3key设置
这里不再重复 参考微信开发文档 微信JSAPI开发接入前准备 https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_1.shtml 慢慢来哦,设置秘钥 我倒是自己想着(阿猫阿狗888666)AMAG66688这种来拼够32位就可以了哈哈。
4、Core3.1后端代码 详解
前面这些弄好了只算搭好了环境 下面开始撸码。微信支付的逻辑就是,获取用户的OpenId------->统一下单获取payId-------------->拉起微信支付------------>支付回调接口写逻辑
下面官网的
参考文档 JSAPI支付
1)、封装微信请求类
这里我单独封装了一个微信支付的请求类。因为调用v3支付必须要符合APIV3接口规则 ,具体的在微信官方文档看
using App.Common.Base;using Microsoft.AspNetCore.Hosting;using Newtonsoft.Json;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;using System.Security.Cryptography.X509Certificates;using System.Text;using System.Threading;using System.Threading.Tasks;namespace App.Common.HttpHelper{ /// <summary> /// 请求类封装 别忘了 调用的时候添加 services.AddHttpClient() 调用都要加参数,实体类返回没有超时时间 /// 容器要添加 /// </summary> public class HttpClientFactoryHelper { private readonly IHttpClientFactory _httpClientFactory; private IWebHostEnvironment _webHostEnvironment; public HttpClientFactoryHelper(IHttpClientFactory httpClientFactory, IWebHostEnvironment webHostEnvironment) { _httpClientFactory = httpClientFactory; _webHostEnvironment = webHostEnvironment; } public void SaveLog(string text) { string thisTime = DateTime.Now.ToString("yyyyMMdd"); var path = _webHostEnvironment.ContentRootPath + $"/ApiInterfaceErrorLog/";//绝对路径 string dirPath = Path.Combine(path, thisTime + "/");//绝对径路 储存文件路径的文件夹 if (!Directory.Exists(dirPath))//查看文件夹是否存在 Directory.CreateDirectory(dirPath); string splitLine = "============================下一条=============================="; string timeNow = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); using StreamWriter file = new StreamWriter(dirPath + thisTime + ".txt", true); file.WriteLine(timeNow+text); file.WriteLine(splitLine); } #region //微信支付请求 /// <summary> /// 微信请求Post /// </summary> /// <param name="url">地址</param> /// <param name="requestString">参数</param> /// <param name="privateKey">私有秘钥 p12文件</param> /// <param name="merchantId">商户号</param> /// <param name="serialNo">商户证书号</param> /// <returns></returns> public async Task<string> WeChatPostAsync(string url,string requestString, string privateKey, string merchantId, string serialNo) { try { var client = _httpClientFactory.CreateClient(); var requestContent = new StringContent(requestString); requestContent.Headers.ContentType.MediaType = "application/json"; var auth = BuildAuthAsync(url, requestString, privateKey, merchantId, serialNo,"POST"); string value = $"WECHATPAY2-SHA256-RSA2048 {auth}"; client.DefaultRequestHeaders.Add("Authorization", value); client.DefaultRequestHeaders.Add("Accept", "application/json"); client.DefaultRequestHeaders.Add("User-Agent", "WOW64"); client.Timeout = TimeSpan.FromSeconds(60); var response = await client.PostAsync(url, requestContent); if (response.IsSuccessStatusCode) { var result = await response.Content.ReadAsStringAsync(); return result; } else { return $"接口【{url}】请求错误,错误代码{response.StatusCode},错误原因{response.ReasonPhrase}具体的话========================================\n{JsonConvert.SerializeObject(response)}"; } } catch(Exception ex) { SaveLog($"接口【{DateTime.Now +url}】请求错误,错误代码{ex.Message}具体=============================================/n{ex.StackTrace}"); return ex.Message + ex.StackTrace; } } /// <summary> /// 微信请求 /// </summary> /// <param name="url">地址</param> /// <param name="requestString">参数</param> /// <param name="privateKey">私有秘钥 p12文件</param> /// <param name="merchantId">商户号</param> /// <param name="serialNo">商户证书号</param> /// <param name="method">Get或者Post</param> /// <returns></returns> public async Task<string> WeChatGetAsync(string url, string privateKey, string merchantId, string serialNo) { try { var client = _httpClientFactory.CreateClient(); var auth = BuildAuthAsync(url, "", privateKey, merchantId, serialNo,"GET"); string value = $"WECHATPAY2-SHA256-RSA2048 {auth}"; client.DefaultRequestHeaders.Add("Authorization", value); client.DefaultRequestHeaders.Add("Accept", "*/*"); client.DefaultRequestHeaders.Add("User-Agent", "WOW64"); client.Timeout = TimeSpan.FromSeconds(60); var response = await client.GetAsync(url); if (response.IsSuccessStatusCode) { var result = await response.Content.ReadAsStringAsync(); return result; } else { return $"接口【{url}】请求错误,错误代码{response.StatusCode},错误原因{response.ReasonPhrase}"; } } catch (Exception ex) { SaveLog($"接口【{DateTime.Now + url}】请求错误,错误代码{ex.Message}具体=============================================/n{ex.StackTrace}"); return ex.Message+ ex.StackTrace; } } protected string BuildAuthAsync(string url,string jsonParame ,string privateKey, string merchantId,string serialNo,string method="") { string body = jsonParame; var uri = new Uri(url); var urlPath = uri.PathAndQuery; var timestamp = DateTimeOffset.Now.ToUnixTimeSeconds(); string nonce = Path.GetRandomFileName(); string message = $"{method}\n{urlPath}\n{timestamp}\n{nonce}\n{body}\n"; //string signature = Sign(message, privateKey); var path = _webHostEnvironment.WebRootPath + "/arsjkll/apiclient_cert.p12"; string signature = Sign(message,path, "1601849569"); return $"mchid=\"{merchantId}\",nonce_str=\"{nonce}\",timestamp=\"{timestamp}\",serial_no=\"{serialNo}\",signature=\"{signature}\""; } /// <summary> /// 签名(CentOs 不支持 换了下面的) /// </summary> /// <param name="message">签名内容</param> /// <param name="privateKey">秘钥</param> /// <returns></returns> public string Sign(string message,string privateKey) { byte[] keyData = Convert.FromBase64String(privateKey); using CngKey cngKey = CngKey.Import(keyData, CngKeyBlobFormat.Pkcs8PrivateBlob); using RSACng rsa = new RSACng(cngKey); byte[] data = System.Text.Encoding.UTF8.GetBytes(message); return Convert.ToBase64String(rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)); } /// <summary> /// 获取签名证书私钥 /// </summary> /// <param name="priKeyFile">证书文件路径</param> /// <param name="keyPwd">密码</param> /// <returns></returns> private static RSA GetPrivateKey(string priKeyFile, string keyPwd) { var pc = new X509Certificate2(priKeyFile, keyPwd, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet); return (RSA)pc.PrivateKey; } /// <summary> //// <summary> /// 根据证书签名数据 后面要做成配置在数据库中 /// </summary> /// <param name="data">要签名的数据</param> /// <param name="certPah">证书路径</param> /// <param name="certPwd">密码</param> /// <returns></returns> public string Sign(string data, string certPah, string certPwd) { var rsa = GetPrivateKey(certPah, certPwd); var rsaClear = new RSACryptoServiceProvider(); var paras = rsa.ExportParameters(true); rsaClear.ImportParameters(paras); var signData = rsa.SignData(Encoding.UTF8.GetBytes(data), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); return Convert.ToBase64String(signData); }
No comments:
Post a Comment