| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305 |
- package provider
- import (
- "context"
- "net/http"
- "net/http/httptest"
- "net/url"
- "strings"
- "testing"
- "unicode/utf8"
- "github.com/Wei-Shaw/sub2api/internal/payment"
- )
- func TestNormalizeEasyPayAPIBase(t *testing.T) {
- t.Parallel()
- tests := []struct {
- input string
- want string
- }{
- {input: "https://zpayz.cn", want: "https://zpayz.cn"},
- {input: "https://zpayz.cn/", want: "https://zpayz.cn"},
- {input: "https://zpayz.cn/mapi.php", want: "https://zpayz.cn"},
- {input: "https://zpayz.cn/submit.php", want: "https://zpayz.cn"},
- {input: "https://zpayz.cn/api.php", want: "https://zpayz.cn"},
- {input: "https://zpayz.cn/api.php?act=refund", want: "https://zpayz.cn"},
- }
- for _, tt := range tests {
- t.Run(tt.input, func(t *testing.T) {
- t.Parallel()
- if got := normalizeEasyPayAPIBase(tt.input); got != tt.want {
- t.Fatalf("normalizeEasyPayAPIBase(%q) = %q, want %q", tt.input, got, tt.want)
- }
- })
- }
- }
- func TestEasyPayRefundNormalizesAPIBaseAndSendsOutTradeNoOnly(t *testing.T) {
- t.Parallel()
- var gotPath string
- var gotQuery url.Values
- var gotForm url.Values
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- gotPath = r.URL.Path
- gotQuery = r.URL.Query()
- if err := r.ParseForm(); err != nil {
- t.Errorf("ParseForm: %v", err)
- }
- gotForm = r.PostForm
- w.Header().Set("Content-Type", "application/json")
- _, _ = w.Write([]byte(`{"code":1,"msg":"ok"}`))
- }))
- defer server.Close()
- provider := newTestEasyPay(t, server.URL+"/mapi.php")
- resp, err := provider.Refund(context.Background(), payment.RefundRequest{
- TradeNo: "trade-123",
- OrderID: "out-456",
- Amount: "1.50",
- })
- if err != nil {
- t.Fatalf("Refund returned error: %v", err)
- }
- if resp == nil || resp.Status != payment.ProviderStatusSuccess {
- t.Fatalf("Refund response = %+v, want success", resp)
- }
- if gotPath != "/api.php" {
- t.Fatalf("refund path = %q, want /api.php", gotPath)
- }
- if gotQuery.Get("act") != "refund" {
- t.Fatalf("refund act query = %q, want refund", gotQuery.Get("act"))
- }
- for key, want := range map[string]string{
- "pid": "pid-1",
- "key": "pkey-1",
- "out_trade_no": "out-456",
- "money": "1.50",
- } {
- if got := gotForm.Get(key); got != want {
- t.Fatalf("form[%s] = %q, want %q (form=%v)", key, got, want, gotForm)
- }
- }
- if got := gotForm.Get("trade_no"); got != "" {
- t.Fatalf("form[trade_no] = %q, want empty (form=%v)", got, gotForm)
- }
- }
- func TestEasyPayRefundRetriesWithTradeNoWhenOutTradeNoNotFound(t *testing.T) {
- t.Parallel()
- var gotForms []url.Values
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if r.URL.Path != "/api.php" {
- t.Errorf("refund path = %q, want /api.php", r.URL.Path)
- }
- if r.URL.Query().Get("act") != "refund" {
- t.Errorf("refund act query = %q, want refund", r.URL.Query().Get("act"))
- }
- if err := r.ParseForm(); err != nil {
- t.Errorf("ParseForm: %v", err)
- }
- gotForms = append(gotForms, r.PostForm)
- w.Header().Set("Content-Type", "application/json")
- if len(gotForms) == 1 {
- _, _ = w.Write([]byte(`{"code":0,"msg":"订单编号不存在!"}`))
- return
- }
- _, _ = w.Write([]byte(`{"code":1,"msg":"ok"}`))
- }))
- defer server.Close()
- provider := newTestEasyPay(t, server.URL+"/mapi.php")
- resp, err := provider.Refund(context.Background(), payment.RefundRequest{
- TradeNo: "trade-123",
- OrderID: "out-456",
- Amount: "1.50",
- })
- if err != nil {
- t.Fatalf("Refund returned error: %v", err)
- }
- if resp == nil || resp.Status != payment.ProviderStatusSuccess || resp.RefundID != "trade-123" {
- t.Fatalf("Refund response = %+v, want success with trade refund id", resp)
- }
- if len(gotForms) != 2 {
- t.Fatalf("refund attempts = %d, want 2", len(gotForms))
- }
- if got := gotForms[0].Get("out_trade_no"); got != "out-456" {
- t.Fatalf("first form[out_trade_no] = %q, want out-456 (form=%v)", got, gotForms[0])
- }
- if got := gotForms[0].Get("trade_no"); got != "" {
- t.Fatalf("first form[trade_no] = %q, want empty (form=%v)", got, gotForms[0])
- }
- if got := gotForms[1].Get("trade_no"); got != "trade-123" {
- t.Fatalf("second form[trade_no] = %q, want trade-123 (form=%v)", got, gotForms[1])
- }
- if got := gotForms[1].Get("out_trade_no"); got != "" {
- t.Fatalf("second form[out_trade_no] = %q, want empty (form=%v)", got, gotForms[1])
- }
- }
- func TestEasyPayRefundResponseErrors(t *testing.T) {
- t.Parallel()
- tests := []struct {
- name string
- statusCode int
- body string
- want string
- }{
- {name: "html response", statusCode: http.StatusOK, body: "<html>bad config</html>", want: "non-JSON response (HTTP 200): <html>bad config</html>"},
- {name: "non json response", statusCode: http.StatusOK, body: "not json", want: "non-JSON response (HTTP 200): not json"},
- {name: "non 2xx response", statusCode: http.StatusBadGateway, body: "bad gateway", want: "HTTP 502: bad gateway"},
- {name: "empty response", statusCode: http.StatusOK, body: "", want: "empty response (HTTP 200): <empty>"},
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- t.Parallel()
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
- w.WriteHeader(tt.statusCode)
- _, _ = w.Write([]byte(tt.body))
- }))
- defer server.Close()
- provider := newTestEasyPay(t, server.URL)
- _, err := provider.Refund(context.Background(), payment.RefundRequest{
- OrderID: "out-456",
- Amount: "1.50",
- })
- if err == nil {
- t.Fatal("Refund returned nil error")
- }
- if !strings.Contains(err.Error(), tt.want) {
- t.Fatalf("Refund error = %q, want substring %q", err.Error(), tt.want)
- }
- })
- }
- }
- func TestSummarizeEasyPayResponsePreservesUTF8(t *testing.T) {
- t.Parallel()
- summary := summarizeEasyPayResponse([]byte(strings.Repeat("错", 171)))
- if !utf8.ValidString(summary) {
- t.Fatalf("summarizeEasyPayResponse returned invalid UTF-8: %q", summary)
- }
- if !strings.HasSuffix(summary, "...") {
- t.Fatalf("summarizeEasyPayResponse() = %q, want truncated suffix", summary)
- }
- }
- func TestEasyPayCustomMethodsUseConfiguredUpstreamType(t *testing.T) {
- t.Parallel()
- provider, err := NewEasyPay("test-instance", map[string]string{
- "pid": "pid-1",
- "pkey": "pkey-1",
- "apiBase": "https://pay.example.com",
- "notifyUrl": "https://example.com/notify",
- "returnUrl": "https://example.com/return",
- "paymentMode": paymentModePopup,
- "customMethods": `[{"type":"ldc","upstreamType":"epay","displayName":"LDC"},{"type":"usdt_trc20","upstreamType":"usdt","displayName":"USDT-TRC20"}]`,
- })
- if err != nil {
- t.Fatalf("NewEasyPay: %v", err)
- }
- resp, err := provider.CreatePayment(context.Background(), payment.CreatePaymentRequest{
- OrderID: "sub2-custom-1",
- Amount: "1.00",
- PaymentType: "usdt_trc20",
- Subject: "Custom EasyPay",
- })
- if err != nil {
- t.Fatalf("CreatePayment: %v", err)
- }
- payURL, err := url.Parse(resp.PayURL)
- if err != nil {
- t.Fatalf("parse pay url: %v", err)
- }
- if got := payURL.Query().Get("type"); got != "usdt" {
- t.Fatalf("pay url type = %q, want usdt (%s)", got, resp.PayURL)
- }
- }
- func TestEasyPayCustomMethodsResolveCIDFromConfiguredUpstreamType(t *testing.T) {
- t.Parallel()
- provider, err := NewEasyPay("test-instance", map[string]string{
- "pid": "pid-1",
- "pkey": "pkey-1",
- "apiBase": "https://pay.example.com",
- "notifyUrl": "https://example.com/notify",
- "returnUrl": "https://example.com/return",
- "paymentMode": paymentModePopup,
- "cidAlipay": "cid-alipay",
- "cidWxpay": "cid-wxpay",
- "customMethods": `[{"type":"ldc","upstreamType":"alipay","displayName":"LDC"}]`,
- })
- if err != nil {
- t.Fatalf("NewEasyPay: %v", err)
- }
- resp, err := provider.CreatePayment(context.Background(), payment.CreatePaymentRequest{
- OrderID: "sub2-custom-cid",
- Amount: "1.00",
- PaymentType: "ldc",
- Subject: "Custom EasyPay CID",
- })
- if err != nil {
- t.Fatalf("CreatePayment: %v", err)
- }
- payURL, err := url.Parse(resp.PayURL)
- if err != nil {
- t.Fatalf("parse pay url: %v", err)
- }
- if got := payURL.Query().Get("type"); got != "alipay" {
- t.Fatalf("pay url type = %q, want alipay (%s)", got, resp.PayURL)
- }
- if got := payURL.Query().Get("cid"); got != "cid-alipay" {
- t.Fatalf("pay url cid = %q, want cid-alipay (%s)", got, resp.PayURL)
- }
- }
- func TestEasyPaySupportedTypesIncludeCustomMethods(t *testing.T) {
- t.Parallel()
- provider, err := NewEasyPay("test-instance", map[string]string{
- "pid": "pid-1",
- "pkey": "pkey-1",
- "apiBase": "https://pay.example.com",
- "notifyUrl": "https://example.com/notify",
- "returnUrl": "https://example.com/return",
- "customMethods": `[{"type":"ldc","upstreamType":"epay","displayName":"LDC"},{"type":"usdt_trc20","upstreamType":"usdt","displayName":"USDT-TRC20"}]`,
- })
- if err != nil {
- t.Fatalf("NewEasyPay: %v", err)
- }
- got := strings.Join(provider.SupportedTypes(), ",")
- for _, want := range []string{"alipay", "wxpay", "ldc", "usdt_trc20"} {
- if !strings.Contains(got, want) {
- t.Fatalf("SupportedTypes() = %q, want it to include %q", got, want)
- }
- }
- }
- func newTestEasyPay(t *testing.T, apiBase string) *EasyPay {
- t.Helper()
- provider, err := NewEasyPay("test-instance", map[string]string{
- "pid": "pid-1",
- "pkey": "pkey-1",
- "apiBase": apiBase,
- "notifyUrl": "https://example.com/notify",
- "returnUrl": "https://example.com/return",
- })
- if err != nil {
- t.Fatalf("NewEasyPay: %v", err)
- }
- return provider
- }
|