From 32c951eab47cc0fe87526583c3756df9d5ba1c81 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 3 Jan 2024 16:35:24 +0800 Subject: [PATCH] =?UTF-8?q?feat(tenpayv3):=20=E6=96=B0=E5=A2=9E=E5=8F=91?= =?UTF-8?q?=E8=B5=B7=E5=BC=82=E5=B8=B8=E9=80=80=E6=AC=BE=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...chatTenpayClientExecuteRefundExtensions.cs | 22 ++++++- ...efundDomesticAbnormalRefundApplyRequest.cs | 62 +++++++++++++++++++ ...fundDomesticAbnormalRefundApplyResponse.cs | 9 +++ ...undDomesticAbnormalRefundApplyRequest.json | 8 +++ ...ndDomesticAbnormalRefundApplyResponse.json | 48 ++++++++++++++ .../TestCase_RequestEncryptionTests.cs | 38 ++++++++++++ 6 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/Refund/CreateRefundDomesticAbnormalRefundApplyRequest.cs create mode 100644 src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/Refund/CreateRefundDomesticAbnormalRefundApplyResponse.cs create mode 100644 test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/ModelSamples/Refund/CreateRefundDomesticAbnormalRefundApplyRequest.json create mode 100644 test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/ModelSamples/Refund/CreateRefundDomesticAbnormalRefundApplyResponse.json diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientExecuteRefundExtensions.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientExecuteRefundExtensions.cs index 0eaf1601..824ee891 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientExecuteRefundExtensions.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientExecuteRefundExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -73,5 +73,25 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 return await client.SendRequestWithJsonAsync(flurlReq, data: request, cancellationToken: cancellationToken); } + + /// + /// 异步调用 [POST] /refund/domestic/refunds/{refund_id}/apply-abnormal-refund 接口。 + /// REF: https://pay.weixin.qq.com/docs/merchant/apis/refund/refunds/create-abnormal-refund.html + /// REF: https://pay.weixin.qq.com/docs/partner/apis/refund/refunds/create-abnormal-refund.html + /// + /// + /// + /// + /// + public static async Task ExecuteCreateRefundDomesticAbnormalRefundApplyAsync(this WechatTenpayClient client, Models.CreateRefundDomesticAbnormalRefundApplyRequest 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 + .CreateRequest(request, HttpMethod.Post, "refund", "domestic", "refunds", request.RefundId, "apply-abnormal-refund"); + + return await client.SendRequestWithJsonAsync(flurlReq, data: request, cancellationToken: cancellationToken); + } } } diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/Refund/CreateRefundDomesticAbnormalRefundApplyRequest.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/Refund/CreateRefundDomesticAbnormalRefundApplyRequest.cs new file mode 100644 index 00000000..a49d2303 --- /dev/null +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/Refund/CreateRefundDomesticAbnormalRefundApplyRequest.cs @@ -0,0 +1,62 @@ +namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models +{ + /// + /// 表示 [POST] /refund/domestic/refunds/{refund_id}/apply-abnormal-refund 接口的请求。 + /// + [WechatTenpaySensitive] + public class CreateRefundDomesticAbnormalRefundApplyRequest : WechatTenpayRequest + { + /// + /// 获取或设置子单子商户号。 + /// + [Newtonsoft.Json.JsonProperty("sub_mchid")] + [System.Text.Json.Serialization.JsonPropertyName("sub_mchid")] + public string? SubMerchantId { get; set; } + + /// + /// 获取或设置微信支付退款号。 + /// + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + public string RefundId { get; set; } = string.Empty; + + /// + /// 获取或设置商户退款单号。 + /// + [Newtonsoft.Json.JsonProperty("out_refund_no")] + [System.Text.Json.Serialization.JsonPropertyName("out_refund_no")] + public string OutRefundNumber { get; set; } = string.Empty; + + /// + /// 获取或设置异常退款处理方式。 + /// + [Newtonsoft.Json.JsonProperty("type")] + [System.Text.Json.Serialization.JsonPropertyName("type")] + public string Type { get; set; } = string.Empty; + + /// + /// 获取或设置收款开户银行。 + /// + [Newtonsoft.Json.JsonProperty("bank_type")] + [System.Text.Json.Serialization.JsonPropertyName("bank_type")] + public string? BankName { get; set; } + + /// + /// 获取或设置收款银行卡号(需使用平台公钥/证书加密)。 + /// + [Newtonsoft.Json.JsonProperty("bank_account")] + [System.Text.Json.Serialization.JsonPropertyName("bank_account")] + [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)] + public string? BankAccountNumber { get; set; } + + /// + /// 获取或设置收款用户姓名(需使用平台公钥/证书加密)。 + /// + [Newtonsoft.Json.JsonProperty("real_name")] + [System.Text.Json.Serialization.JsonPropertyName("real_name")] + [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)] + public string? RealName { get; set; } + } +} diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/Refund/CreateRefundDomesticAbnormalRefundApplyResponse.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/Refund/CreateRefundDomesticAbnormalRefundApplyResponse.cs new file mode 100644 index 00000000..706e920f --- /dev/null +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/Refund/CreateRefundDomesticAbnormalRefundApplyResponse.cs @@ -0,0 +1,9 @@ +namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models +{ + /// + /// 表示 [POST] /refund/domestic/refunds/{refund_id}/apply-abnormal-refund 接口的响应。 + /// + public class CreateRefundDomesticAbnormalRefundApplyResponse : GetRefundDomesticRefundByOutRefundNumberResponse + { + } +} diff --git a/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/ModelSamples/Refund/CreateRefundDomesticAbnormalRefundApplyRequest.json b/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/ModelSamples/Refund/CreateRefundDomesticAbnormalRefundApplyRequest.json new file mode 100644 index 00000000..2ce541cc --- /dev/null +++ b/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/ModelSamples/Refund/CreateRefundDomesticAbnormalRefundApplyRequest.json @@ -0,0 +1,8 @@ +{ + "sub_mchid": "1900000109", + "out_refund_no": "1217752501201407033233368018", + "type": "USER_BANK_CARD", + "bank_type": "ICBC_DEBIT", + "bank_account": "d+xT+MQCvrLHUVDWv/8MR/dB7TkXLVfSrUxMPZy6jWWYzpRrEEaYQE8ZRGYoeorwC+w==", + "real_name": "UPgQcZSdq3zOayJwZ5XLrHY2dZU1W2Cd" +} diff --git a/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/ModelSamples/Refund/CreateRefundDomesticAbnormalRefundApplyResponse.json b/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/ModelSamples/Refund/CreateRefundDomesticAbnormalRefundApplyResponse.json new file mode 100644 index 00000000..6dcd708d --- /dev/null +++ b/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/ModelSamples/Refund/CreateRefundDomesticAbnormalRefundApplyResponse.json @@ -0,0 +1,48 @@ +{ + "refund_id": "50000000382019052709732678859", + "out_refund_no": "1217752501201407033233368018", + "transaction_id": "1217752501201407033233368018", + "out_trade_no": "1217752501201407033233368018", + "channel": "ORIGINAL", + "user_received_account": "招商银行信用卡0403", + "success_time": "2020-12-01T16:18:12+08:00", + "create_time": "2020-12-01T16:18:12+08:00", + "status": "SUCCESS", + "funds_account": "UNSETTLED", + "amount": { + "total": 100, + "refund": 100, + "from": [ + { + "account": "AVAILABLE", + "amount": 444 + } + ], + "payer_total": 90, + "payer_refund": 90, + "settlement_refund": 100, + "settlement_total": 100, + "discount_refund": 10, + "currency": "CNY", + "refund_fee": 100 + }, + "promotion_detail": [ + { + "promotion_id": "109519", + "scope": "GLOBAL", + "type": "COUPON", + "amount": 5, + "refund_amount": 100, + "goods_detail": [ + { + "merchant_goods_id": "1217752501201407033233368018", + "wechatpay_goods_id": "1001", + "goods_name": "iPhone6s 16G", + "unit_price": 528800, + "refund_amount": 528800, + "refund_quantity": 1 + } + ] + } + ] +} diff --git a/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_RequestEncryptionTests.cs b/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_RequestEncryptionTests.cs index 2d89fbf9..e90c783c 100644 --- a/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_RequestEncryptionTests.cs +++ b/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_RequestEncryptionTests.cs @@ -1007,6 +1007,44 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests AssertMockRequestModel(reqB2, (cipher) => Utilities.SM2Utility.Decrypt(SM2_PEM_PRIVATE_KEY, cipher)); } + [Fact(DisplayName = "测试用例:加密请求中的敏感数据([POST] /refund/domestic/refunds/{refund_id}/apply-abnormal-refund)")] + public async Task TestEncryptRequestSensitiveProperty_CreateRefundDomesticAbnormalRefundApply() + { + static Models.CreateRefundDomesticAbnormalRefundApplyRequest GenerateMockRequestModel() + { + return new Models.CreateRefundDomesticAbnormalRefundApplyRequest() + { + BankAccountNumber = MOCK_PLAIN_STR, + RealName = MOCK_PLAIN_STR + }; + } + + static void AssertMockRequestModel(Models.CreateRefundDomesticAbnormalRefundApplyRequest request, Func decryptor) + { + Assert.NotEqual(MOCK_PLAIN_STR, request.BankAccountNumber!); + Assert.NotEqual(MOCK_PLAIN_STR, request.RealName!); + Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.BankAccountNumber!)); + Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.RealName!)); + Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true); + } + + var reqA1 = GenerateMockRequestModel(); + CreateMockClientUseRSA(autoEncrypt: false).EncryptRequestSensitiveProperty(reqA1); + AssertMockRequestModel(reqA1, (cipher) => Utilities.RSAUtility.DecryptWithECB(RSA_PEM_PRIVATE_KEY, cipher)); + + var reqA2 = GenerateMockRequestModel(); + CreateMockClientUseSM2(autoEncrypt: false).EncryptRequestSensitiveProperty(reqA2); + AssertMockRequestModel(reqA2, (cipher) => Utilities.SM2Utility.Decrypt(SM2_PEM_PRIVATE_KEY, cipher)); + + var reqB1 = GenerateMockRequestModel(); + await CreateMockClientUseRSA(autoEncrypt: true).ExecuteCreateRefundDomesticAbnormalRefundApplyAsync(reqB1); + AssertMockRequestModel(reqB1, (cipher) => Utilities.RSAUtility.DecryptWithECB(RSA_PEM_PRIVATE_KEY, cipher)); + + var reqB2 = GenerateMockRequestModel(); + await CreateMockClientUseSM2(autoEncrypt: true).ExecuteCreateRefundDomesticAbnormalRefundApplyAsync(reqB2); + AssertMockRequestModel(reqB2, (cipher) => Utilities.SM2Utility.Decrypt(SM2_PEM_PRIVATE_KEY, cipher)); + } + [Fact(DisplayName = "测试用例:加密请求中的敏感数据([POST] /smartguide/guides)")] public async Task TestEncryptRequestSensitiveProperty_CreateSmartGuideRequest() {