package leshua import ( "strings" "testing" ) func TestSignUsesASCIISortedNonEmptyParams(t *testing.T) { params := map[string]string{ "service": "get_tdcode", "merchant_id": "1234567890", "third_order_id": "NO1", "amount": "100", "nonce_str": "abc", "empty": "", "sign": "ignored", } got := Sign(params, "secret", SignOptions{}) want := "1E7892034AA77899E8DD609C4FA09E76" if got != want { t.Fatalf("Sign() = %s, want %s", got, want) } baseString := SignBaseString(params, SignOptions{}) wantBaseString := "amount=100&merchant_id=1234567890&nonce_str=abc&service=get_tdcode&third_order_id=NO1" if baseString != wantBaseString { t.Fatalf("SignBaseString() = %s, want %s", baseString, wantBaseString) } } func TestVerifyNotifyIncludesEmptyAndExcludesErrorCode(t *testing.T) { client := NewClient(Config{NotifyKey: "notify-secret"}) params := map[string]string{ "merchant_id": "1234567890", "third_order_id": "NO1", "leshua_order_id": "LS1", "amount": "100", "status": "2", "attach": "", "leshua": "", "error_code": "-20001", } params["sign"] = Sign(params, "notify-secret", SignOptions{ IncludeEmpty: true, ExcludeKeys: []string{"error_code", "leshua", "sign"}, }) if !client.VerifyNotify(params) { t.Fatal("VerifyNotify() = false, want true") } params["amount"] = "101" if client.VerifyNotify(params) { t.Fatal("VerifyNotify() = true after amount changed, want false") } } func TestVerifyNotifyDoesNotFallBackToSignKey(t *testing.T) { client := NewClient(Config{ NotifyKey: "wrong-notify-secret", SignKey: "sign-secret", }) params := map[string]string{ "merchant_id": "1234567890", "third_order_id": "NO1", "leshua_order_id": "LS1", "amount": "100", "status": "2", } params["sign"] = Sign(params, "sign-secret", SignOptions{ IncludeEmpty: true, ExcludeKeys: []string{"error_code", "sign"}, }) result := client.VerifyNotifyDetail(params) if result.OK { t.Fatal("VerifyNotifyDetail().OK = true, want false") } if _, ok := result.Expected["sign_key"]; ok { t.Fatal("VerifyNotifyDetail() unexpectedly used sign_key fallback") } } func TestVerifyNotifyUsesDocumentedNotifySignature(t *testing.T) { client := NewClient(Config{NotifyKey: "notify-secret"}) params := map[string]string{ "merchant_id": "1234567890", "third_order_id": "NO1", "leshua_order_id": "LS1", "amount": "100", "status": "2", "sign_type": "MD5", } params["sign"] = Sign(params, "notify-secret", SignOptions{ IncludeEmpty: true, ExcludeKeys: []string{"error_code", "leshua", "sign"}, }) result := client.VerifyNotifyDetail(params) if !result.OK { t.Fatal("VerifyNotifyDetail().OK = false, want true") } if result.MatchedKey != "notify_key" { t.Fatalf("MatchedKey = %s, want notify_key", result.MatchedKey) } } func TestVerifyNotifyKeepsEmptyXMLFieldsInSignature(t *testing.T) { client := NewClient(Config{NotifyKey: "notify-secret"}) params, err := ParsePayload([]byte(` 100 1234567890 MD5 2 NO1 `)) if err != nil { t.Fatalf("ParsePayload(xml) error = %v", err) } params["sign"] = Sign(params, "notify-secret", SignOptions{ IncludeEmpty: true, ExcludeKeys: []string{"error_code", "leshua", "sign"}, }) result := client.VerifyNotifyDetail(params) if !result.OK { t.Fatal("VerifyNotifyDetail().OK = false, want true") } baseString := result.BaseString[result.MatchedKey] if !strings.Contains(baseString, "goods_tag=") { t.Fatalf("baseString = %s, want goods_tag included", baseString) } } func TestParsePayloadSupportsFormAndXML(t *testing.T) { form, err := ParsePayload([]byte("third_order_id=NO1&status=2&amount=100")) if err != nil { t.Fatalf("ParsePayload(form) error = %v", err) } if form["third_order_id"] != "NO1" || form["status"] != "2" || form["amount"] != "100" { t.Fatalf("ParsePayload(form) = %#v", form) } xml, err := ParsePayload([]byte("NO26")) if err != nil { t.Fatalf("ParsePayload(xml) error = %v", err) } if xml["third_order_id"] != "NO2" || xml["status"] != "6" { t.Fatalf("ParsePayload(xml) = %#v", xml) } if value, ok := xml["goods_tag"]; !ok || value != "" { t.Fatalf("ParsePayload(xml).goods_tag = %q, exists=%v; want empty value", value, ok) } if value, ok := xml["coupon"]; !ok || value != "" { t.Fatalf("ParsePayload(xml).coupon = %q, exists=%v; want empty value", value, ok) } } func TestSignRefundUsesSameAlgorithm(t *testing.T) { params := map[string]string{ "service": "unified_refund", "merchant_id": "1234567890", "merchant_refund_id": "REF001", "refund_amount": "100", "leshua_order_id": "LS1", "nonce_str": "abc", "sign": "ignored", } got := Sign(params, "secret", SignOptions{}) baseString := SignBaseString(params, SignOptions{}) wantBaseString := "leshua_order_id=LS1&merchant_id=1234567890&merchant_refund_id=REF001&nonce_str=abc&refund_amount=100&service=unified_refund" if baseString != wantBaseString { t.Fatalf("SignBaseString() = %s, want %s", baseString, wantBaseString) } want := Sign(map[string]string{ "service": "unified_refund", "merchant_id": "1234567890", "merchant_refund_id": "REF001", "refund_amount": "100", "leshua_order_id": "LS1", "nonce_str": "abc", }, "secret", SignOptions{}) if got != want { t.Fatalf("Sign() = %s, want %s", got, want) } } func TestVerifyRefundNotify(t *testing.T) { client := NewClient(Config{NotifyKey: "notify-secret"}) params := map[string]string{ "merchant_id": "1234567890", "third_order_id": "NO1", "leshua_order_id": "LS1", "merchant_refund_id": "REF001", "leshua_refund_id": "LREF001", "refund_amount": "100", "total_amount": "200", "status": "11", "attach": "", } params["sign"] = Sign(params, "notify-secret", SignOptions{ IncludeEmpty: true, ExcludeKeys: []string{"error_code", "leshua", "sign"}, }) if !client.VerifyNotify(params) { t.Fatal("VerifyNotify() = false, want true for refund notify") } params["status"] = "12" if client.VerifyNotify(params) { t.Fatal("VerifyNotify() = true after status changed without re-sign, want false") } }