using Baidu.Aip.BodyAnalysis; using Furion.DatabaseAccessor; using Furion.DataEncryption; using Furion.DependencyInjection; using Furion.HttpRemote; using Furion.Logging; using log4net; using Microsoft.Extensions.Options; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Http; using System.Reflection; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Text.Encodings.Web; using System.Threading.Tasks; namespace ApiTools.Core { /// /// 微信支付工具 /// public class WeChatPayUtils( IOptions options, IHttpRemoteService httpRemoteService, IRepository repThreeResourceLog ) : ITransient { private readonly IOptions options = options; private readonly IHttpRemoteService httpRemoteService = httpRemoteService; private readonly IRepository repThreeResourceLog = repThreeResourceLog; public async Task Send( EnumResourceMethod method, string path, WeChatPayRequest request, Func build = null) where TResponse : WeChatPayResponse, new() { var log = new ThreeResourceLog { Method = method, Domain = "https://api.mch.weixin.qq.com", Path = path, Request = request.ToJson() }; if (request.PathParameters != null) { var properties = typeof(TPathParameters).GetProperties(); foreach (var property in properties) { var jsonProperty = property.GetCustomAttribute(); var name = jsonProperty != null ? jsonProperty.PropertyName : property.Name; path = path.Replace($"{{{name}}}", property.GetValue(request.PathParameters).ToString()); } } if (request.QueryParamters != null) { var properties = typeof(TQueryParameters).GetProperties(); var queries = new Dictionary(); foreach (var property in properties) { var jsonProperty = property.GetCustomAttribute(); var name = jsonProperty != null ? jsonProperty.PropertyName : property.Name; var value = property.GetValue(request.QueryParamters).ToString(); if (value.IsNotNull()) { value = UrlEncoder.Default.Encode(value); } queries.Add(name, value); } var queriesString = queries.Select(it => $"{it.Key}={it.Value}").SplitJoin("&"); path = $"{path}?{queriesString}"; } var methodString = method.ToString().ToUpper(); var builder = HttpRequestBuilder.Create(methodString, $"{log.Domain}{path}"); var body = ""; if (request.BodyParamters != null) { body = request.BodyParamters.ToJson(new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); builder = builder.SetJsonContent(body); builder = builder.WithHeader("Content-Type", "application/json"); } var authorization = GetAuthorization(methodString, path, body, options.Value.MchId, options.Value.SerialNo, options.Value.PrivateKey); builder = builder.WithHeader("Authorization", authorization); builder = builder.WithHeader("Accept", "application/json"); if (build != null) { builder = build(builder); } await repThreeResourceLog.InsertNowAsync(log); var stopwatch = Stopwatch.StartNew(); log.Response = await httpRemoteService.SendAsStringAsync(builder); log.UpdatedTime = DateTimeOffset.Now; stopwatch.Stop(); log.ElapsedMilliseconds = stopwatch.ElapsedMilliseconds; var result = log.Response.JsonTo(); log.IsSuccess = false; await repThreeResourceLog.UpdateNowAsync(log); return result; } /// /// 发起批量转账 /// /// /// public async Task PartnerTransferBatches(WeChatPayRequest request) { foreach (var item in request.BodyParamters.TransferDetailList) { item.UserName = item.UserName.IsNotNull() ? Encrypt(options.Value.PublicKey, item.UserName) : null; item.UserIdCard = item.UserIdCard.IsNotNull() ? Encrypt(options.Value.PublicKey, item.UserIdCard) : null; } return await Send( EnumResourceMethod.Post, "/v3/partner-transfer/batches", request, it => it.WithHeader("Wechatpay-Serial", options.Value.SerialNo)); } /// /// 查询特约商户账户实时余额 /// /// /// public async Task EcommerceFundBalance(WeChatPayRequest request) { return await Send( EnumResourceMethod.Get, "/v3/ecommerce/fund/balance/{sub_mchid}", request, it => it.WithHeader("Wechatpay-Serial", options.Value.SerialNo)); } /// /// 生成认证信息 /// /// /// /// /// /// /// /// private string GetAuthorization(string method, string path, string body, string mchId, string serialNo, string privateKey) { var timestamp = DateTime.Now.ToTimeStamp(false); var nonce_str = GetHexDumpRandom(); var signature = GetSignature(method, path, body, privateKey, timestamp, nonce_str); return $"WECHATPAY2-SHA256-RSA2048 mchid=\"{mchId}\",nonce_str=\"{nonce_str}\",signature=\"{signature}\",timestamp=\"{timestamp}\",serial_no=\"{serialNo}\""; } /// /// 加密敏感字段 /// /// /// /// public string Encrypt(string publicKey, string data) { byte[] dataToEncrypt = Encoding.UTF8.GetBytes(data); using (var rsa = new RSACryptoServiceProvider()) { rsa.ImportFromPem(publicKey.ToCharArray()); byte[] encryptedBytes = rsa.Encrypt(dataToEncrypt, true); return Convert.ToBase64String(encryptedBytes); } } /// /// 解密敏感字段 /// /// /// /// public string Decrypt(string privareKey, string encryptedBase64) { byte[] encryptedBytes = Convert.FromBase64String(encryptedBase64); using (var rsa = new RSACryptoServiceProvider()) { rsa.ImportFromPem(privareKey.ToCharArray()); byte[] decryptedBytes = rsa.Decrypt(encryptedBytes, true); return Encoding.UTF8.GetString(decryptedBytes); } } /// /// 生成签名 /// /// /// /// /// /// /// /// private string GetSignature(string method, string path, string body, string privateKey, long timestamp, string nonce_str) { string signString = $"{method}\n{path}\n{timestamp}\n{nonce_str}\n{body}\n"; using (var rsa = new RSACryptoServiceProvider()) { rsa.ImportFromPem(privateKey.ToCharArray()); // 将字符串转换为字节数组(使用UTF8编码) byte[] dataBytes = Encoding.UTF8.GetBytes(signString); // 执行签名:SHA256哈希算法,PKCS#1 v1.5填充模式 byte[] signatureBytes = rsa.SignData( dataBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1 ); // 将签名结果转换为Base64字符串 var base64 = Convert.ToBase64String(signatureBytes); return base64; } } /// /// 生成一个请求随机串 /// /// private string GetHexDumpRandom() { // 16字节缓冲区,与hexdump -n 16对应 byte[] randomBytes = new byte[16]; // 使用加密级随机数生成器,类似于/dev/random using (var rng = RandomNumberGenerator.Create()) { rng.GetBytes(randomBytes); } // 构建输出字符串 StringBuilder sb = new StringBuilder(); // 4/4 "%08X" 对应:将16字节分成4个4字节整数,每个以8位十六进制显示 for (int i = 0; i < 4; i++) { // 从字节数组中解析32位整数(大端字节序) uint value = BitConverter.ToUInt32(randomBytes, i * 4); // 如果系统是小端字节序,需要转换为大端 if (BitConverter.IsLittleEndian) { value = ReverseBytes(value); } // 格式化为8位十六进制大写形式 sb.AppendFormat("{0:X8}", value); } return sb.ToString(); } // 反转32位整数的字节序(小端转大端) private static uint ReverseBytes(uint value) { return (value << 24) | ((value & 0x0000FF00) << 8) | ((value & 0x00FF0000) >> 8) | (value >> 24); } } }