diff --git a/docs/WechatTenpayV3/Basic_ModelDefinition.md b/docs/WechatTenpayV3/Basic_ModelDefinition.md index 9f919d93..a1881361 100644 --- a/docs/WechatTenpayV3/Basic_ModelDefinition.md +++ b/docs/WechatTenpayV3/Basic_ModelDefinition.md @@ -1370,6 +1370,20 @@ - 查询不活跃商户身份核实结果:`GetComplianceInactiveMerchantIdentityVerificationByVerificationId` +- 扩展工具 + + - 电商订单实名校验 + + - 实名信息校验:`GetRealNameVerification` + + - 微信点餐订单 + + - 点餐订单信息同步:`SyncCateringOrderStatus` + + - 微信寄快递 + + - 用户 OpenID 转换:`TransformExpressUserOpenId` + - 其他 - 来账识别 @@ -1576,14 +1590,6 @@ - 服务商银行来账查询:`QueryMerchantFundMerchantIncomeRecords` - - 微信点餐订单 - - - 点餐订单信息同步:`SyncCateringOrderStatus` - - - 微信寄快递 - - - 用户 OpenID 转换:`TransformExpressUserOpenId` - --- diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientExecuteRealNameExtensions.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientExecuteRealNameExtensions.cs new file mode 100644 index 00000000..883f4b0a --- /dev/null +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientExecuteRealNameExtensions.cs @@ -0,0 +1,37 @@ +using System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Flurl.Http; + +namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 +{ + public static class WechatTenpayClientExecuteRealNameExtensions + { + /// + /// 异步调用 [GET] /realname/verify 接口。 + /// + /// REF:
+ /// + ///
+ ///
+ /// + /// + /// + /// + public static async Task ExecuteGetRealNameVerificationAsync(this WechatTenpayClient client, Models.GetRealNameVerificationRequest request, CancellationToken cancellationToken = default) + { + if (client is null) throw new ArgumentNullException(nameof(client)); + if (request is null) throw new ArgumentNullException(nameof(request)); + + IFlurlRequest flurlReq = client + .CreateFlurlRequest(request, HttpMethod.Get, "realname", "verify") + .SetQueryParam("credential_type", request.CredentialType) + .SetQueryParam("encrypted_credential_id", request.CredentialId) + .SetQueryParam("encrypted_name", request.CredentialName) + .SetQueryParam("wxp_trade_no", request.TransactionId); + + return await client.SendFlurlRequestAsJsonAsync(flurlReq, data: request, cancellationToken: cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/_Partner/RealName/GetRealNameVerificationRequest.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/_Partner/RealName/GetRealNameVerificationRequest.cs new file mode 100644 index 00000000..dfce8946 --- /dev/null +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/_Partner/RealName/GetRealNameVerificationRequest.cs @@ -0,0 +1,42 @@ +namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models +{ + /// + /// 表示 [GET] /realname/verify 接口的请求。 + /// + [WechatTenpaySensitive] + public class GetRealNameVerificationRequest : WechatTenpayRequest + { + /// + /// 获取或设置下单用户证件类型。 + /// 默认值:"1" + /// + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + public string CredentialType { get; set; } = "1"; + + /// + /// 获取或设置下单用户证件号(需使用平台公钥/证书加密)。 + /// + [WechatTenpaySensitiveProperty(scheme: Constants.SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256, algorithm: Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1)] + [WechatTenpaySensitiveProperty(scheme: Constants.SignSchemes.WECHATPAY2_SM2_WITH_SM3, algorithm: Constants.EncryptionAlgorithms.SM2_C1C3C2_ASN1)] + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + public string CredentialId { get; set; } = string.Empty; + + /// + /// 获取或设置下单用户姓名(需使用平台公钥/证书加密)。 + /// + [WechatTenpaySensitiveProperty(scheme: Constants.SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256, algorithm: Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1)] + [WechatTenpaySensitiveProperty(scheme: Constants.SignSchemes.WECHATPAY2_SM2_WITH_SM3, algorithm: Constants.EncryptionAlgorithms.SM2_C1C3C2_ASN1)] + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + public string CredentialName { get; set; } = string.Empty; + + /// + /// 获取或设置微信支付交易单号。 + /// + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + public string TransactionId { get; set; } = string.Empty; + } +} diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/_Partner/RealName/GetRealNameVerificationResponse.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/_Partner/RealName/GetRealNameVerificationResponse.cs new file mode 100644 index 00000000..f4aeaf36 --- /dev/null +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/_Partner/RealName/GetRealNameVerificationResponse.cs @@ -0,0 +1,22 @@ +namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models +{ + /// + /// 表示 [GET] /realname/verify 接口的响应。 + /// + public class GetRealNameVerificationResponse : WechatTenpayResponse + { + /// + /// 获取或设置实名比对结果。 + /// + [Newtonsoft.Json.JsonProperty("verify_result")] + [System.Text.Json.Serialization.JsonPropertyName("verify_result")] + public int VerifyResult { get; set; } + + /// + /// 获取或设置实名比对错误信息。 + /// + [Newtonsoft.Json.JsonProperty("err_message")] + [System.Text.Json.Serialization.JsonPropertyName("err_message")] + public string? VerifyErrorMessage { get; set; } + } +} diff --git a/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/ModelSamples/_/_Partner/RealName/GetRealNameVerificationResponse.json b/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/ModelSamples/_/_Partner/RealName/GetRealNameVerificationResponse.json new file mode 100644 index 00000000..485699ba --- /dev/null +++ b/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/ModelSamples/_/_Partner/RealName/GetRealNameVerificationResponse.json @@ -0,0 +1,4 @@ +{ + "verify_result": 0, + "err_message": "realname verify succ" +} diff --git a/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_RequestEncryptionTests.cs b/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_RequestEncryptionTests.cs index a436fbf6..47fa7850 100644 --- a/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_RequestEncryptionTests.cs +++ b/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_RequestEncryptionTests.cs @@ -1774,6 +1774,62 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests } } + [Fact(DisplayName = "测试用例:加密请求中的敏感数据([POST] /realname/verify)")] + public async Task TestEncryptRequestSensitiveProperty_GetRealNameVerificationRequest() + { + static Models.GetRealNameVerificationRequest GenerateMockRequestModel() + { + return new Models.GetRealNameVerificationRequest() + { + CredentialId = MOCK_PLAIN_STR, + CredentialName = MOCK_PLAIN_STR + }; + } + + static void AssertMockRequestModel(Models.GetRealNameVerificationRequest request, Func decryptor) + { + Assert.NotEqual(MOCK_PLAIN_STR, request.CredentialId); + Assert.NotEqual(MOCK_PLAIN_STR, request.CredentialName); + Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.CredentialId)); + Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.CredentialName)); + Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true); + } + + if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey)) + { + using (var client = CreateMockClientUseRSA(autoEncrypt: false)) + { + var request = GenerateMockRequestModel(); + client.EncryptRequestSensitiveProperty(request); + AssertMockRequestModel(request, (cipher) => Utilities.RSAUtility.DecryptWithECB(RSA_PEM_PRIVATE_KEY, (EncodedString)cipher)!); + } + + using (var client = CreateMockClientUseRSA(autoEncrypt: true)) + { + var request = GenerateMockRequestModel(); + await client.ExecuteGetRealNameVerificationAsync(request); + AssertMockRequestModel(request, (cipher) => Utilities.RSAUtility.DecryptWithECB(RSA_PEM_PRIVATE_KEY, (EncodedString)cipher)!); + } + } + + if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantSM2CertificatePrivateKey)) + { + using (var client = CreateMockClientUseSM2(autoEncrypt: false)) + { + var request = GenerateMockRequestModel(); + client.EncryptRequestSensitiveProperty(request); + AssertMockRequestModel(request, (cipher) => Utilities.SM2Utility.Decrypt(SM2_PEM_PRIVATE_KEY, (EncodedString)cipher)!); + } + + using (var client = CreateMockClientUseSM2(autoEncrypt: true)) + { + var request = GenerateMockRequestModel(); + await client.ExecuteGetRealNameVerificationAsync(request); + AssertMockRequestModel(request, (cipher) => Utilities.SM2Utility.Decrypt(SM2_PEM_PRIVATE_KEY, (EncodedString)cipher)!); + } + } + } + [Fact(DisplayName = "测试用例:加密请求中的敏感数据([POST] /refund/domestic/refunds/{refund_id}/apply-abnormal-refund)")] public async Task TestEncryptRequestSensitiveProperty_CreateRefundDomesticAbnormalRefundApply() {