diff --git a/hb/example_test.go b/hb/example_test.go new file mode 100644 index 0000000..df0986e --- /dev/null +++ b/hb/example_test.go @@ -0,0 +1,200 @@ +package hb + +import ( + "context" + "fmt" + "log" + "testing" +) + +// ExampleClient_Refund_MD5 使用MD5签名的退款示例 +func ExampleClient_Refund_MD5() { + // 创建客户端(使用MD5签名) + client, err := NewClient( + "888009941110054", + WithSignKey("your-64-digit-sign-key-here"), + WithBaseURL("https://uatipos.10086.cn"), // 测试环境 + ) + if err != nil { + log.Fatalf("创建客户端失败: %v", err) + } + + // 构建退款请求 + req := &RefundRequest{ + RequestId: "20211105053044", + SignType: SignTypeMD5, + OrderId: "20211105045315", + Amount: "1", + } + + // 发送退款请求 + resp, err := client.Refund(context.Background(), req) + if err != nil { + log.Fatalf("退款请求失败: %v", err) + } + + fmt.Printf("返回码: %s\n", resp.ReturnCode) + fmt.Printf("退款结果: %s\n", resp.Status) + fmt.Printf("流水号: %s\n", resp.PayNo) + + // 判断退款请求是否成功 + if IsRefundRequestSuccess(resp.ReturnCode) { + fmt.Println("退款请求提交成功") + } + + // 判断退款最终结果 + if IsRefundSuccess(resp.Status) { + fmt.Println("退款最终成功") + } + + // Output: + // 返回码: 000000 + // 退款结果: SUCCESS + // 流水号: 205206151912517707 + // 退款请求提交成功 + // 退款最终成功 +} + +// ExampleClient_Refund_RSA 使用RSA签名的退款示例 +func ExampleClient_Refund_RSA() { + // 商户RSA私钥(PEM格式) + privateKey := `-----BEGIN PRIVATE KEY----- +MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAJ...(你的私钥) +-----END PRIVATE KEY-----` + + // 创建客户端(使用RSA签名) + client, err := NewClient( + "888009941110054", + WithPrivateKey(privateKey), + WithBaseURL("https://ipos.10086.cn"), // 生产环境 + ) + if err != nil { + log.Fatalf("创建客户端失败: %v", err) + } + + // 构建退款请求 + req := &RefundRequest{ + RequestId: "20211105053044", + SignType: SignTypeRSA, + OrderId: "20211105045315", + Amount: "1", + } + + // 发送退款请求 + resp, err := client.Refund(context.Background(), req) + if err != nil { + log.Fatalf("退款请求失败: %v", err) + } + + fmt.Printf("返回码: %s\n", resp.ReturnCode) + fmt.Printf("退款结果: %s\n", resp.Status) + + // Output: + // 返回码: 000000 + // 退款结果: SUCCESS +} + +// TestSignMD5 测试MD5签名 +func TestSignMD5(t *testing.T) { + signStr := "merchantId=888009941110054&requestId=20211105053044&signType=MD5&type=OrderRefund&version=2.0.0&orderId=20211105045315&amount=1" + signKey := "test-sign-key-64-chars-length-string-for-testing" + + hmac := SignMD5(signStr, signKey) + if hmac == "" { + t.Error("MD5签名结果为空") + } + t.Logf("MD5签名结果: %s", hmac) + + // 验证签名 + if !VerifyMD5(signStr, signKey, hmac) { + t.Error("MD5签名验证失败") + } +} + +// TestBuildSignString 测试签名串构建 +func TestBuildSignString(t *testing.T) { + params := map[string]string{ + "merchantId": "888009941110054", + "requestId": "20211105053044", + "signType": "MD5", + "type": "OrderRefund", + "version": "2.0.0", + "orderId": "20211105045315", + "amount": "1", + } + + keys := getRequestSignKeys() + signStr := buildSignString(params, keys) + + expected := "merchantId=888009941110054&requestId=20211105053044&signType=MD5&type=OrderRefund&version=2.0.0&orderId=20211105045315&amount=1" + if signStr != expected { + t.Errorf("签名串不匹配\n期望: %s\n实际: %s", expected, signStr) + } +} + +// TestParseResponse 测试响应解析 +func TestParseResponse(t *testing.T) { + rawResponse := `merchantId=888009941110054&payNo=205206151912517707&returnCode=000000&message=SUCCESS&signType=RSA&type=OrderRefund&version=2.0.0&amount=1&orderId=20211105045315&status=SUCCESS&serverCert=3082042030820308A003020102020420B82E72300D06092A864886F70D0101050500302F310B300906035504061302434E3120301E06035504030C17434D434120456E74657270726973652043415F32303438301E170D3230303830363031343834395A170D3232303830363031343834395A3076310B300906035504061302434E310F300D06035504080C06E6B996E58D97310F300D06035504070C06E995BFE6B2993145304306035504030C3CE4B8ADE59BBDE7A7BBE58AA8E9809AE4BFA1E99B86E59BA2E6B996E58D97E69C89E99990E585ACE58FB8E794B5E5AD90E59586E58AA1E4B8ADE5BF8330820122300D06092A864886F70D01010105000382010F003082010A028201010092FCBC26F1107D35B966C0C098B1F31F8313103A29DC5C78A8056D1AFAC1E5BAA89F5AEE6D3E737D3554AE815040AD5A5D9BAF5BB5B4B45AA5622335E3B9F48D9C05DF29248DF944544D414513F37DFE7F618D0C62B24E1E90CFD1790791861E20FDEFC4288EA4BF906EE1A6B576D9FDE6108CF5A1ED01D1931F2C72AED5393AE9CB668BBF629B25A239E09C54D45B565FBA040963B011C7B80F7C93D4695F4568F6358D591FBCB1073C2583630EC858B7C21F5B7CBB8CC05431D93F4AC358F78A06D6E40D1C033083F403697C12A0A99FF6363D792F624B66FCA655E7057686BA5093A8F68597DB8F40865081A3EB43C1C52E91D35F83F9FF3BF40C9901D08B0203010001A381FC3081F930370603551D250101FF042D302B06082B0601050507030206082B0601050507030106096086480186F8420401060A2B0601040182370A0303301F0603551D23041830168014972A89BEE6A2AA3FA666133AE36A3FBA5D86697130710603551D1F046A30683066A02FA02D862B687474703A2F2F7777772E636D63612E6E65742F646F776E6C6F61642F63726C2F43524C3134322E63726CA233A431302F3120301E06035504030C17434D434120456E74657270726973652043415F32303438310B300906035504061302434E300B0603551D0F0404030200B1301D0603551D0E04160414F713DA66CF8A1F30440D83455C2AFC6560B040B6300D06092A864886F70D010105050003820101002AD8B85ABEEAE53C6002381120F7D6B23620464197FBBDB093D4BB98AF256E335F55A67E32BFDF165BB1E2DDA997A0727086D26F8427FE6B77F6F00C6168CE715E8D298D5EB47B7227CDB8094CB1047CBDEA3D911903DCAA57131DEE1CD0069BBF8C7940A3E68438CCCA1949FF045F7349AA865F7FAC7170B7D7339DEBC46930FF9B241200440EAFD280F868C13364D13ECEB04EA702D0479862F9590813AB94BE38DC84E366B884174731464111A1AFEB199E1D7D518651BB5F430926286CA2B5619CD54689F5851A231C1605146D4ACD6CCC54DB79CC49F992083D5DEACAFDDC2B8777B13B7D63FF7E35D1098BEFD6B550EC3328ECE0224F5AAD6C2B83FB85&hmac=00AAA308D1AA66B2E534D9177B408B55A0CC14F8B27ACE59F08150E66E51858F18070A0DD629FCBD47BB1A98A632DB3F492213A92F14885043EC68BD8180E58ED742D748B708B281693226A7D3BF326B812BA45E7E7D13B4D52498B3C6C83380827CD741A9EF0D99B1C96588C2BE56C79BDFC3813CA4E627142BA7717FFFB43BE53F9B3F2A6D3112D83A90C3D3859A8456BC892E6E960FE56F36F829FAFA7CAEF19D01D10C0BD2F2538827CFF4C642BBAA6423AED0CB8E0D980D7D0BDEB4B1A04B9DAC8867B724527423038D953603A07018BEC1980AD31EDD3E88977F72795DCFB0B3B674FA91A6443F9F0DB67B485B3F3AD5E48AA26F3641E44B64FC7B0920` + + resp, err := parseResponse(rawResponse) + if err != nil { + t.Fatalf("解析响应失败: %v", err) + } + + if resp.MerchantId != "888009941110054" { + t.Errorf("MerchantId不匹配: %s", resp.MerchantId) + } + if resp.PayNo != "205206151912517707" { + t.Errorf("PayNo不匹配: %s", resp.PayNo) + } + if resp.ReturnCode != "000000" { + t.Errorf("ReturnCode不匹配: %s", resp.ReturnCode) + } + if resp.Status != "SUCCESS" { + t.Errorf("Status不匹配: %s", resp.Status) + } + if resp.SignType != "RSA" { + t.Errorf("SignType不匹配: %s", resp.SignType) + } +} + +// TestErrorCode 测试错误码映射 +func TestErrorCode(t *testing.T) { + tests := []struct { + code string + expected string + }{ + {"000000", "未知错误码"}, + {"C01088", "退货金额域非法"}, + {"D22201", "订单不存在"}, + {"IPS0008", "签名不符,一般都是由于中文编码引起"}, + {"UNKNOWN", "未知错误码"}, + } + + for _, tt := range tests { + desc := GetErrorDescription(tt.code) + if desc != tt.expected { + t.Errorf("错误码 %s: 期望 %s, 实际 %s", tt.code, tt.expected, desc) + } + } +} + +// TestIsRefundSuccess 测试退款成功判断 +func TestIsRefundSuccess(t *testing.T) { + if !IsRefundRequestSuccess("000000") { + t.Error("000000 应该表示退款请求成功") + } + if !IsRefundRequestSuccess("MCG00000") { + t.Error("MCG00000 应该表示退款请求成功") + } + if IsRefundRequestSuccess("C01088") { + t.Error("C01088 不应该表示退款请求成功") + } + + if !IsRefundSuccess("SUCCESS") { + t.Error("SUCCESS 应该表示退款最终成功") + } + if IsRefundSuccess("FAILED") { + t.Error("FAILED 不应该表示退款最终成功") + } +} \ No newline at end of file