Skip to content

Commit

Permalink
Merge branch 'JeffreySu:Developer-TenpayV3' into Developer-TenpayV3
Browse files Browse the repository at this point in the history
  • Loading branch information
RockRockWhite committed Aug 22, 2021
2 parents ee824ae + 5f225b7 commit 16e5ee8
Show file tree
Hide file tree
Showing 17 changed files with 206 additions and 211 deletions.
4 changes: 2 additions & 2 deletions Samples/net6-mvc/Senparc.Weixin.Sample.Net6/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@
"TenPayV3_CertSecret": "#{TenPayV3_CertSecret}#", //(新)支付证书密码(原始密码和 MchId 相同)
"TenPayV3_TenpayNotify": "#{TenPayV3_TenpayNotify}#", //http://YourDomainName/TenpayV3/PayNotifyUrl
"TenPayV3_PrivateKey": "#{TenPayV3_PrivateKey}#", //(新)证书私钥
"TenPayV3_SerialNumber": "#{TenPayV3_SerialNumber}#", //证书序列号
"TenPayV3_SerialNumber": "#{TenPayV3_SerialNumber}#", //(新)证书序列号
"TenPayV3_ApiV3Key": "#{TenPayV3_APIv3Key}#", //(新)APIv3 密钥
//如果不设置TenPayV3_WxOpenTenpayNotify,默认在 TenPayV3_TenpayNotify 的值最后加上 "WxOpen"
"TenPayV3_WxOpenTenpayNotify": "#{TenPayV3_WxOpenTenpayNotify}#", //http://YourDomainName/TenpayV3/PayNotifyUrlWxOpen

//开放平台
"Component_Appid": "#{Component_Appid}#",
"Component_Secret": "#{Component_Secret}#",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public void JsAPiAsyncTest()
[TestMethod()]
public void CertificatesTest()
{
var result = BasePayApis.Certificates().GetAwaiter().GetResult();
var result = BasePayApis.CertificatesAsync().GetAwaiter().GetResult();
Assert.IsNotNull(result);
Console.WriteLine(result.ToJson(true));
Assert.IsTrue(result.ResultCode.Success);
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,17 @@
</PropertyGroup>

<ItemGroup>
<None Remove="appsettings.Development.json" />
<None Remove="appsettings.json" />
<None Remove="appsettings.Test.json" />
</ItemGroup>

<ItemGroup>
<Content Include="appsettings.Development.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Include="appsettings.Test.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@
"TenPayV3_CertSecret": "#{TenPayV3_CertSecret}#", //(新)支付证书密码(原始密码和 MchId 相同)
"TenPayV3_TenpayNotify": "#{TenPayV3_TenpayNotify}#", //http://YourDomainName/TenpayV3/PayNotifyUrl
"TenPayV3_PrivateKey": "#{TenPayV3_PrivateKey}#", //(新)证书私钥
"TenPayV3_SerialNumber": "#{TenPayV3_SerialNumber}#", //证书序列号
"TenPayV3_SerialNumber": "#{TenPayV3_SerialNumber}#", //(新)证书序列号
"TenPayV3_ApiV3Key": "#{TenPayV3_APIv3Key}#", //(新)APIv3 密钥
//如果不设置TenPayV3_WxOpenTenpayNotify,默认在 TenPayV3_TenpayNotify 的值最后加上 "WxOpen"
"TenPayV3_WxOpenTenpayNotify": "#{TenPayV3_WxOpenTenpayNotify}#", //http://YourDomainName/TenpayV3/PayNotifyUrlWxOpen

//开放平台
"Component_Appid": "#{Component_Appid}#",
"Component_Secret": "#{Component_Secret}#",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@
using Microsoft.Extensions.DependencyInjection;
using Senparc.CO2NET.Trace;
using Senparc.Weixin.Entities;
using Senparc.Weixin.TenPayV3.Entities;
using Senparc.Weixin.TenPayV3.Helpers;

namespace Senparc.Weixin.TenPayV3.Apis
{
Expand All @@ -84,11 +86,11 @@ private static string ReurnPayApiUrl(string urlFormat)
#region 平台证书

/// <summary>
///
/// 获取平台证书
/// </summary>
/// <param name="timeOut"></param>
/// <returns></returns>
public static async Task<CertificatesResultJson> Certificates(int timeOut = Config.TIME_OUT)
public static async Task<CertificatesResultJson> CertificatesAsync(int timeOut = Config.TIME_OUT)
{
var url = ReurnPayApiUrl("https://api.mch.weixin.qq.com/{0}v3/certificates");
TenPayApiRequest tenPayApiRequest = new();
Expand All @@ -97,6 +99,28 @@ public static async Task<CertificatesResultJson> Certificates(int timeOut = Conf
return await tenPayApiRequest.RequestAsync<CertificatesResultJson>(url, null, timeOut, ApiRequestMethod.GET);
}

/// <summary>
/// 获取平台证书的公钥(会从远程请求,不会缓存)
/// </summary>
/// <param name="timeOut"></param>
/// <returns></returns>
public static async Task<PublicKeyCollection> GetPublicKeysAsync(int timeOut = Config.TIME_OUT)
{
var certificates = await CertificatesAsync();
if (certificates.data?.Length == 0)
{
throw new TenpayApiRequestException("Certificates 获取结果为空");
}

PublicKeyCollection keys = new();
var tenpayV3Setting = Senparc.Weixin.Config.SenparcWeixinSetting.TenpayV3Setting;//TODO:改成从构造函数配置
foreach (var cert in certificates.data)
{
keys[cert.serial_no] = ApiSecurityHelper.AesGcmDecryptCiphertext(tenpayV3Setting.TenPayV3_APIv3Key, cert.encrypt_certificate.nonce, cert.encrypt_certificate.associated_data, cert.encrypt_certificate.ciphertext);
}
return keys;
}

#endregion

#region 下单接口
Expand All @@ -114,7 +138,6 @@ public static async Task<JsApiReturnJson> JsApiAsync(JsApiRequestData data, int
return await tenPayApiRequest.RequestAsync<JsApiReturnJson>(url, data, timeOut);
}


// TODO: 待测试
/// <summary>
/// JSAPI合单支付下单接口
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Senparc.Weixin.TenPayV3.Entities
{
/// <summary>
/// 储存公钥
/// <para>Key:serial_no,Value:Key</para>
/// </summary>
public class PublicKeyCollection : ConcurrentDictionary<string, string>
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Senparc.Weixin.Exceptions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Senparc.Weixin.TenPayV3
{
public class TenpaySecurityException : WeixinException
{
public TenpaySecurityException(string message, Exception inner, bool logged = false) : base(message, inner, logged)
{
}

public TenpaySecurityException(string message, bool logged = false) : this(message, null, logged)
{
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using Senparc.Weixin.TenPayV3.Apis.BasePay.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace Senparc.Weixin.TenPayV3.Helpers
{
public class ApiSecurityHelper
{
/// <summary>
/// 解密微信支付接口 ciphertext 内容
/// </summary>
/// <returns></returns>
public static string AesGcmDecryptCiphertext(string aes_key, string nonce, string associated_data, string ciphertext)
{

//将解密所需数据转换为Bytes
var keyBytes = Encoding.UTF8.GetBytes(aes_key);
var nonceBytes = Encoding.UTF8.GetBytes(nonce);
var associatedBytes = associated_data == null ? null : Encoding.UTF8.GetBytes(associated_data);

//AEAD_AES_256_GCM 解密
var encryptedBytes = Convert.FromBase64String(ciphertext);
//tag size is 16 TODO: what is tag size?
var cipherBytes = encryptedBytes[..^16];
var tag = encryptedBytes[^16..];
var decryptedData = new byte[cipherBytes.Length];
using var cipher = new AesGcm(keyBytes);
cipher.Decrypt(nonceBytes, cipherBytes, tag, decryptedData, associatedBytes);
var decrypted_string = Encoding.UTF8.GetString(decryptedData);

return decrypted_string;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,16 @@ public void SetHeader(HttpClient client)
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue($"({userAgentValues.OSVersion})"));
}

public async Task<HttpResponseMessage> GetHttpResponseMessageAsync(string url, object data, int timeOut = Config.TIME_OUT, ApiRequestMethod requestMethod = ApiRequestMethod.POST)
/// <summary>
/// 获取 HttpResponseMessage 对象
/// </summary>
/// <param name="url"></param>
/// <param name="data">如果为 GET 请求,此参数可为 null</param>
/// <param name="timeOut"></param>
/// <param name="requestMethod"></param>
/// <param name="checkDataNotNull">非 GET 请求情况下,是否强制检查 data 参数不能为 null,默认为 true</param>
/// <returns></returns>
public async Task<HttpResponseMessage> GetHttpResponseMessageAsync(string url, object data, int timeOut = Config.TIME_OUT, ApiRequestMethod requestMethod = ApiRequestMethod.POST, bool checkDataNotNull = true)
{
try
{
Expand Down Expand Up @@ -109,7 +118,10 @@ public async Task<HttpResponseMessage> GetHttpResponseMessageAsync(string url, o
case ApiRequestMethod.PUT:
case ApiRequestMethod.PATCH:
//检查是否为空
//_ = data ?? throw new ArgumentNullException($"{nameof(data)} 不能为 null!");
if (checkDataNotNull)
{
_ = data ?? throw new ArgumentNullException($"{nameof(data)} 不能为 null!");
}

//设置请求 Json 字符串
//var jsonString = SerializerHelper.GetJsonString(data, new CO2NET.Helpers.Serializers.JsonSetting(true));
Expand Down Expand Up @@ -162,10 +174,10 @@ public async Task<T> RequestAsync<T>(string url, object data, int timeOut = Conf

//TODO:待测试 加入验证签名
//获取响应结果
string content = await responseMessage.Content.ReadAsStringAsync();//TODO:如果不正确也要返回详情

if (resutlCode.Success)
{
string content = await responseMessage.Content.ReadAsStringAsync();

//TODO:待测试
//验证微信签名
//result.Signed = VerifyTenpaySign(responseMessage.Headers, content);
Expand All @@ -177,7 +189,7 @@ public async Task<T> RequestAsync<T>(string url, object data, int timeOut = Conf

try
{
result.Signed = TenPaySignHelper.VerifyTenpaySign(wechatpayTimestamp, wechatpayNonce, wechatpaySignature, content);
//result.Signed = TenPaySignHelper.VerifyTenpaySign(wechatpayTimestamp, wechatpayNonce, wechatpaySignature, content);
}
catch (Exception ex)
{
Expand All @@ -187,6 +199,7 @@ public async Task<T> RequestAsync<T>(string url, object data, int timeOut = Conf
else
{
result = createDefaultInstance?.Invoke() ?? GetInstance<T>(true);
resutlCode.Additional = content;
}
//T result = resutlCode.Success ? (await responseMessage.Content.ReadAsStringAsync()).GetObject<T>() : new T();
result.ResultCode = resutlCode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ public record class TenPayApiResultCode
new TenPayApiResultCode("400","INVALID_REQUEST","无效请求","请根据接口返回的详细信息检查"),
new TenPayApiResultCode("400","APPID_MCHID_NOT_MATCH","appid和mch_id不匹配","请确认appid和mch_id是否匹配")
}},
{"401", new[] { new TenPayApiResultCode("401","SIGN_ERROR","签名错误","请检查签名参数和方法是否都符合签名算法要求") } },
{"401", new[] { new TenPayApiResultCode("401","SIGN_ERROR","签名错误","请检查签名参数和方法是否都符合签名算法要求"),
new TenPayApiResultCode("401","SIGN_ERROR","商户未设置加密的密钥","请登录商户平台操作!请参考http://kf.qq.com/faq/180830E36vyQ180830AZFZvu.html")//通过测试得到
} },
{"403", new[] { new TenPayApiResultCode("403","TRADE_ERROR","交易错误","因业务原因交易失败,请查看接口返回的详细信息"),
new TenPayApiResultCode("403","RULE_LIMIT","业务规则限制","因业务规则限制请求频率,请查看接口返回的详细信息"),
new TenPayApiResultCode("403","OUT_TRADE_NO_USED","商户订单号重复","请核实商户订单号是否重复提交"),
Expand Down Expand Up @@ -57,25 +59,29 @@ public static TenPayApiResultCode TryGetCode(HttpStatusCode httpStatusCode)
{
if (result.Length == 1)
{
return result[0];
return result[0] with { };
}

return result.First() with
{
ErrorCode = string.Join(',', result.Select(z => z.ErrorCode)),
ErrorMessage = string.Join(',', result.Select(z => z.ErrorMessage)),
Solution = string.Join(',', result.Select(z => z.Solution)),
ErrorCode = string.Join(";\n", result.Select(z => z.ErrorCode)),
ErrorMessage = string.Join(";\n", result.Select(z => z.ErrorMessage)),
Solution = string.Join(";\n", result.Select(z => z.Solution)),
};
}

return new TenPayApiResultCode(httpStatusCode.ToString(), "UNKNOW CODE", "未知的代码", "请检查日志", false);//没有匹配到
}

public string StateCode { get; set; }
public string ErrorCode { get; set; }
public string ErrorMessage { get; set; }
public string Solution { get; set; }
public bool Success { get; set; } = false;
public bool Success { get; protected set; } = false;
public string StateCode { get; protected set; }
public string ErrorCode { get; protected set; }
public string ErrorMessage { get; internal set; }
public string Solution { get; internal set; }
/// <summary>
/// 额外信息
/// </summary>
public string Additional { get; set; }

public TenPayApiResultCode() { }

Expand Down

0 comments on commit 16e5ee8

Please sign in to comment.