#region Apache License Version 2.0 /*---------------------------------------------------------------- Copyright 2023 Jeffrey Su & Suzhou Senparc Network Technology Co.,Ltd. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Detail: https://github.com/JeffreySu/WeiXinMPSDK/blob/master/license.md ----------------------------------------------------------------*/ #endregion Apache License Version 2.0 /*---------------------------------------------------------------- Copyright (C) 2022 Senparc 文件名:TenPayHttpClient.cs 文件功能描述:TenPayHttpClient 创建标识:Senparc - 20210920 修改标识:Senparc - 20220511 修改描述:v0.6.2.2 TenPayHttpClient 赋值问题 ----------------------------------------------------------------*/ using Client.TenPayHttpClient.Signer; using Senparc.CO2NET; using Senparc.CO2NET.Extensions; using Senparc.CO2NET.Helpers; using Senparc.CO2NET.HttpUtility; using Senparc.CO2NET.Trace; using Senparc.Weixin; using Senparc.Weixin.Entities; using Senparc.Weixin.TenPayV3; using Senparc.Weixin.TenPayV3.Apis.BasePay; using Senparc.Weixin.TenPayV3.Apis.Entities; using Senparc.Weixin.TenPayV3.TenPayHttpClient.Verifier; using System; using System.IO; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; namespace Senparc.Weixin.TenPayV3.TenPayHttpClient { public class BasePayApis2 { private readonly SenparcHttpClient _httpClient; private ISenparcWeixinSettingForTenpayV3 _tenpayV3Setting; private readonly CertType _certType; /// /// 构造函数 /// /// public BasePayApis2(SenparcHttpClient httpClient, ISenparcWeixinSettingForTenpayV3 senparcWeixinSettingForTenpayV3 = null, CertType certType = CertType.RSA) { this._httpClient = httpClient; _tenpayV3Setting = senparcWeixinSettingForTenpayV3 ?? Senparc.Weixin.Config.SenparcWeixinSetting.TenpayV3Setting; this._certType = certType; } /// /// 返回可用的微信支付地址(自动判断是否使用沙箱) /// /// 如:https://api.mch.weixin.qq.com/{0}pay/unifiedorder /// private static string ReturnPayApiUrl(string urlFormat) { //注意:目前微信支付 V3 还没有支持沙箱,此处只是预留 return string.Format(urlFormat, Senparc.Weixin.Config.UseSandBoxPay ? "sandboxnew/" : ""); } /// /// JSAPI下单接口 /// 在微信支付服务后台生成JSAPI预支付交易单,返回预支付交易会话标识 /// /// 微信支付需要POST的Data数据 /// 超时时间,单位为ms /// public async Task JsApiAsync(TransactionsRequestData data, int timeOut = Config.TIME_OUT) { var url = ReturnPayApiUrl(Senparc.Weixin.Config.TenPayV3Host + "/{0}v3/pay/transactions/jsapi"); HttpClient hc = null;//注入 TenPayHttpClient tenPayApiRequest = new(_httpClient, _tenpayV3Setting); return await tenPayApiRequest.SendAsync(url, data, timeOut); } } public class TenPayHttpClient { private readonly SenparcHttpClient _httpClient; private ISenparcWeixinSettingForTenpayV3 _tenpayV3Setting; private readonly HttpClient _client; private readonly ISigner _signer; private readonly IVerifier _verifier; public TenPayHttpClient(SenparcHttpClient httpClient, ISenparcWeixinSettingForTenpayV3 senparcWeixinSettingForTenpayV3 = null, CertType certType = CertType.RSA) { this._httpClient = httpClient; this._client = this._httpClient.Client; _tenpayV3Setting = senparcWeixinSettingForTenpayV3 ?? Senparc.Weixin.Config.SenparcWeixinSetting.TenpayV3Setting; //从工厂获得签名和验签的方法类 _signer = TenPayCertFactory.GetSigner(certType); _verifier = TenPayCertFactory.GetVerifier(certType); #region 配置UA //ACCEPT header _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("*/*")); //User-Agent header var userAgentValues = UserAgentValues.Instance; _client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("Senparc.Weixin.TenPayV3-C#", userAgentValues.TenPayV3Version)); _client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue($"(Senparc.Weixin {userAgentValues.SenparcWeixinVersion})")); _client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(".NET", userAgentValues.RuntimeVersion)); _client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue($"({userAgentValues.OSVersion})")); #endregion } public async Task SendAsync(string url, object data, int timeOut = Senparc.Weixin.Config.TIME_OUT, ApiRequestMethod requestMethod = ApiRequestMethod.POST, bool checkSign = true, Func createDefaultInstance = null) where T : ReturnJsonBase/*, new()*/ { T result = null; HttpMethod method = GetHttpMethod(requestMethod); try { var request = new HttpRequestMessage(method, url); //设置超时时间 _client.Timeout = TimeSpan.FromMilliseconds(timeOut); //设置请求 Json 字符串 string jsonString = data != null ? data.ToJson(false, new Newtonsoft.Json.JsonSerializerSettings() { NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore }) : ""; WeixinTrace.SendApiPostDataLog(url, jsonString); //记录Post的Json数据 request.Content = new StringContent(jsonString, Encoding.UTF8, mediaType: "application/json"); // 进行签名 var authorization = await GenerateAuthorizationHeader(request); request.Headers.Add("Authorization", $"WECHATPAY2-{_signer.GetAlgorithm()} {authorization}"); // 发送请求 var responseMessage = await _client.SendAsync(request); //获取响应结果 string content = await responseMessage.Content.ReadAsStringAsync();//TODO:如果不正确也要返回详情 #if DEBUG Console.WriteLine("Content:" + content + ",,Headers:" + responseMessage.Headers.ToString()); #endif //检查响应代码 TenPayApiResultCode resutlCode = TenPayApiResultCode.TryGetCode(responseMessage.StatusCode, content); if (resutlCode.Success) { result = content.GetObject(); if (checkSign) { result.VerifySignSuccess = await VerifyResponseMessage(responseMessage, content); } } else { result = createDefaultInstance?.Invoke() ?? GetInstance(true); resutlCode.Additional = content; } result.ResultCode = resutlCode; return result; } catch (Exception ex) { SenparcTrace.BaseExceptionLog(ex); result = createDefaultInstance?.Invoke() ?? GetInstance(false); if (result != null) { result.ResultCode = new() { ErrorMessage = ex.Message }; } return result; } } protected HttpMethod GetHttpMethod(ApiRequestMethod requestMethod) { return requestMethod switch { ApiRequestMethod.GET => HttpMethod.Get, ApiRequestMethod.POST => HttpMethod.Post, ApiRequestMethod.PUT => HttpMethod.Put, ApiRequestMethod.PATCH => HttpMethod.Patch, ApiRequestMethod.DELETE => HttpMethod.Delete, _ => throw new ArgumentOutOfRangeException(nameof(requestMethod)), }; } protected async Task GenerateAuthorizationHeader(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 = _signer.Sign(message, _tenpayV3Setting.TenPayV3_PrivateKey); return $"mchid=\"{_tenpayV3Setting.TenPayV3_MchId}\",nonce_str=\"{nonce}\",timestamp=\"{timestamp}\",serial_no=\"{_tenpayV3Setting.TenPayV3_SerialNumber}\",signature=\"{signature}\""; } protected async Task VerifyResponseMessage(HttpResponseMessage responseMessage, string content) { var wechatpayTimestamp = responseMessage.Headers.GetValues("Wechatpay-Timestamp").First(); var wechatpayNonce = responseMessage.Headers.GetValues("Wechatpay-Nonce").First(); var wechatpaySignatureBase64 = responseMessage.Headers.GetValues("Wechatpay-Signature").First();//后续需要base64解码 var wechatpaySerial = responseMessage.Headers.GetValues("Wechatpay-Serial").First(); try { var pubKey = await TenPayV3InfoCollection.GetAPIv3PublicKeyAsync(this._tenpayV3Setting, wechatpaySerial); return _verifier.Verify(wechatpayTimestamp, wechatpayNonce, wechatpaySignatureBase64, content, pubKey); } catch (Exception ex) { throw new TenpayApiRequestException("RequestAsync 签名验证失败:" + ex.Message, ex); } } /// /// 获取实例 /// /// /// /// private T GetInstance(bool throwIfFaild) where T : ReturnJsonBase { if (typeof(T).IsClass) { return Senparc.CO2NET.Helpers.ReflectionHelper.CreateInstance(typeof(T).FullName, typeof(T).Assembly.GetName().Name); } else if (throwIfFaild) { throw new TenpayApiRequestException("GetInstance 失败,此类型无法自动生成:" + typeof(T).FullName); } return null; } } }