From 786c5e81759e151b863db63a9e1a306eda131174 Mon Sep 17 00:00:00 2001 From: line-6000 <154492442+line-6000@users.noreply.github.com> Date: Tue, 14 Apr 2026 18:43:20 +0800 Subject: [PATCH 1/2] update router for support epay GET and POST --- src/route/router.go | 48 +++++++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/src/route/router.go b/src/route/router.go index ee54b32..90114ff 100644 --- a/src/route/router.go +++ b/src/route/router.go @@ -75,25 +75,20 @@ func RegisterRoute(e *echo.Echo) { // epay v1 routes epayV1 := paymentRoute.Group("/epay/v1") - epayV1.GET("/order/create-transaction/submit.php", func(ctx echo.Context) error { - money := ctx.QueryParam("money") - name := ctx.QueryParam("name") - notifyURL := ctx.QueryParam("notify_url") - outTradeNo := ctx.QueryParam("out_trade_no") - returnURL := ctx.QueryParam("return_url") - signstr := ctx.QueryParam("sign") - // signType := ctx.QueryParam("sign_type") - - m := map[string]interface{}{ - "money": money, - "name": name, - "notify_url": notifyURL, - "out_trade_no": outTradeNo, - "pid": config.GetEpayPid(), // 注意:验签时需要包含 pid 参数 - "return_url": returnURL, + epayV1.Match([]string{http.MethodPost, http.MethodGet}, "/order/create-transaction/submit.php", func(ctx echo.Context) error { + params := make(map[string]interface{}) + for k, v := range ctx.QueryParams() { + params[k] = v[0] } - checkSignature, err := sign.Get(m, config.GetApiAuthToken()) + signstr := params["sign"].(string) + delete(params, "sign") + delete(params, "sign_type") + + // we need to add pid to params for signature verification + params["pid"] = config.GetEpayPid() + + checkSignature, err := sign.Get(params, config.GetApiAuthToken()) if err != nil { return constant.SignatureErr } @@ -101,6 +96,25 @@ func RegisterRoute(e *echo.Echo) { return constant.SignatureErr } + // safely get string value from map + getString := func(m map[string]interface{}, key string) string { + v, ok := m[key] + if !ok { + return "" + } + s, ok := v.(string) + if !ok { + return "" + } + return s + } + + money := getString(params, "money") + name := getString(params, "name") + notifyURL := getString(params, "notify_url") + outTradeNo := getString(params, "out_trade_no") + returnURL := getString(params, "return_url") + amountFloat, err := strconv.ParseFloat(money, 64) if err != nil { return comm.Ctrl.FailJson(ctx, fmt.Errorf("invalid money value: %s", money)) From 8cd816c35ef1e12e43f76acbaa4123635228d073 Mon Sep 17 00:00:00 2001 From: jiamovo Date: Tue, 14 Apr 2026 20:44:26 +0800 Subject: [PATCH 2/2] support epay submit form params --- src/route/router.go | 50 ++++++++++++++++++--------- src/route/router_test.go | 73 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 16 deletions(-) diff --git a/src/route/router.go b/src/route/router.go index 90114ff..552f98c 100644 --- a/src/route/router.go +++ b/src/route/router.go @@ -77,11 +77,42 @@ func RegisterRoute(e *echo.Echo) { epayV1 := paymentRoute.Group("/epay/v1") epayV1.Match([]string{http.MethodPost, http.MethodGet}, "/order/create-transaction/submit.php", func(ctx echo.Context) error { params := make(map[string]interface{}) - for k, v := range ctx.QueryParams() { - params[k] = v[0] + copyParams := func(values map[string][]string) { + for k, v := range values { + if len(v) == 0 { + continue + } + params[k] = v[0] + } + } + + copyParams(ctx.QueryParams()) + + formParams, err := ctx.FormParams() + if err != nil && ctx.Request().Method == http.MethodPost { + return comm.Ctrl.FailJson(ctx, fmt.Errorf("invalid epay form params: %w", err)) + } + if err == nil { + copyParams(formParams) + } + + getString := func(m map[string]interface{}, key string) string { + v, ok := m[key] + if !ok { + return "" + } + s, ok := v.(string) + if !ok { + return "" + } + return s + } + + signstr := getString(params, "sign") + if signstr == "" { + return constant.SignatureErr } - signstr := params["sign"].(string) delete(params, "sign") delete(params, "sign_type") @@ -96,19 +127,6 @@ func RegisterRoute(e *echo.Echo) { return constant.SignatureErr } - // safely get string value from map - getString := func(m map[string]interface{}, key string) string { - v, ok := m[key] - if !ok { - return "" - } - s, ok := v.(string) - if !ok { - return "" - } - return s - } - money := getString(params, "money") name := getString(params, "name") notifyURL := getString(params, "notify_url") diff --git a/src/route/router_test.go b/src/route/router_test.go index ddc0a5c..4de45c3 100644 --- a/src/route/router_test.go +++ b/src/route/router_test.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" "net/http/httptest" + "net/url" "os" "strings" "testing" @@ -28,6 +29,7 @@ func setupTestEnv(t *testing.T) *echo.Echo { viper.Reset() viper.Set("db_type", "sqlite") viper.Set("api_auth_token", testAPIToken) + viper.Set("epay_pid", 1) viper.Set("app_uri", "http://localhost:8080") viper.Set("order_expiration_time", 10) viper.Set("api_rate_url", "") @@ -78,6 +80,28 @@ func doPost(e *echo.Echo, path string, body map[string]interface{}) *httptest.Re return rec } +func signEpayValues(values url.Values) url.Values { + signParams := make(map[string]interface{}) + for key, items := range values { + if key == "sign" || key == "sign_type" || len(items) == 0 { + continue + } + signParams[key] = items[0] + } + sig, _ := sign.Get(signParams, testAPIToken) + values.Set("sign", sig) + values.Set("sign_type", "MD5") + return values +} + +func doFormPost(e *echo.Echo, path string, values url.Values) *httptest.ResponseRecorder { + req := httptest.NewRequest(http.MethodPost, path, strings.NewReader(values.Encode())) + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationForm) + rec := httptest.NewRecorder() + e.ServeHTTP(rec, req) + return rec +} + // TestCreateOrderEpusdtDefaultTron tests the epusdt compatibility route defaults to tron network. func TestCreateOrderEpusdtDefaultTron(t *testing.T) { e := setupTestEnv(t) @@ -401,3 +425,52 @@ func TestCreateOrderNetworkIsolation(t *testing.T) { t.Errorf("expected SolTestAddress001, got %v", data["receive_address"]) } } + +func TestEpaySubmitPhpGetCompatible(t *testing.T) { + e := setupTestEnv(t) + + values := signEpayValues(url.Values{ + "pid": {"1"}, + "name": {"epay-get-001"}, + "type": {"alipay"}, + "money": {"1.00"}, + "out_trade_no": {"epay-get-001"}, + "notify_url": {"http://localhost/notify"}, + "return_url": {"http://localhost/return"}, + }) + + req := httptest.NewRequest(http.MethodGet, "/payments/epay/v1/order/create-transaction/submit.php?"+values.Encode(), nil) + rec := httptest.NewRecorder() + e.ServeHTTP(rec, req) + + if rec.Code != http.StatusFound { + t.Fatalf("expected 302, got %d body=%s", rec.Code, rec.Body.String()) + } + if !strings.HasPrefix(rec.Header().Get("Location"), "/pay/checkout-counter/") { + t.Fatalf("expected checkout redirect, got %q", rec.Header().Get("Location")) + } +} + +func TestEpaySubmitPhpPostFormCompatible(t *testing.T) { + e := setupTestEnv(t) + + values := signEpayValues(url.Values{ + "pid": {"1"}, + "name": {"epay-post-001"}, + "type": {"alipay"}, + "money": {"1.00"}, + "out_trade_no": {"epay-post-001"}, + "notify_url": {"http://localhost/notify"}, + "return_url": {"http://localhost/return"}, + "sitename": {"example-shop"}, + }) + + rec := doFormPost(e, "/payments/epay/v1/order/create-transaction/submit.php", values) + + if rec.Code != http.StatusFound { + t.Fatalf("expected 302, got %d body=%s", rec.Code, rec.Body.String()) + } + if !strings.HasPrefix(rec.Header().Get("Location"), "/pay/checkout-counter/") { + t.Fatalf("expected checkout redirect, got %q", rec.Header().Get("Location")) + } +}