From 4a0d615926dd84ab3a9e21aa25da27f41e3400da Mon Sep 17 00:00:00 2001 From: yml Date: Mon, 15 Jun 2026 00:53:05 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=94=AF=E4=BB=98=E7=BD=91?= =?UTF-8?q?=E5=85=B3=E5=9C=B0=E5=9D=80=E7=A9=BA=E7=99=BD=E5=AD=97=E7=AC=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integrations/payment/lakala/client.go | 2 +- .../payment/lakala/client_test.go | 16 ++++- .../integrations/payment/leshua/client.go | 4 +- .../modules/paymentconfig/mutation.go | 66 ++++++++++++++++++- 4 files changed, 80 insertions(+), 8 deletions(-) diff --git a/backend/internal/integrations/payment/lakala/client.go b/backend/internal/integrations/payment/lakala/client.go index 1780193..b4ddab4 100644 --- a/backend/internal/integrations/payment/lakala/client.go +++ b/backend/internal/integrations/payment/lakala/client.go @@ -374,7 +374,7 @@ func (c *Client) post(ctx context.Context, endpoint string, reqData map[string]a if err != nil { return nil, err } - url := strings.TrimRight(c.cfg.GatewayURL, "/") + endpoint + url := strings.TrimRight(strings.TrimSpace(c.cfg.GatewayURL), "/") + endpoint req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body)) if err != nil { return nil, err diff --git a/backend/internal/integrations/payment/lakala/client_test.go b/backend/internal/integrations/payment/lakala/client_test.go index 9604399..28a2bf6 100644 --- a/backend/internal/integrations/payment/lakala/client_test.go +++ b/backend/internal/integrations/payment/lakala/client_test.go @@ -125,6 +125,14 @@ func TestCreatePaymentSetsCounterParamWhenPayModeConfigured(t *testing.T) { } } +func TestCreatePaymentTrimsGatewayURLWhitespace(t *testing.T) { + body := captureCreatePaymentBody(t, "", "\t") + reqData := body["req_data"].(map[string]any) + if reqData["out_order_no"] != "ORDER1" { + t.Fatalf("out_order_no = %#v, want ORDER1", reqData["out_order_no"]) + } +} + func TestCreatePaymentUsesShanghaiTimeWhenLocalIsUTC(t *testing.T) { oldLocal := time.Local time.Local = time.UTC @@ -179,7 +187,7 @@ func testKeyPairPEM(t *testing.T, key *rsa.PrivateKey) (string, string) { return privatePEM, certPEM } -func captureCreatePaymentBody(t *testing.T, payMode string) map[string]any { +func captureCreatePaymentBody(t *testing.T, payMode string, gatewaySuffix ...string) map[string]any { t.Helper() var captured map[string]any server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -199,8 +207,12 @@ func captureCreatePaymentBody(t *testing.T, payMode string) map[string]any { t.Fatalf("generate key: %v", err) } privatePEM, certPEM := testKeyPairPEM(t, key) + gatewayURL := server.URL + if len(gatewaySuffix) > 0 { + gatewayURL += gatewaySuffix[0] + } client := NewClient(Config{ - GatewayURL: server.URL, + GatewayURL: gatewayURL, AppID: "app-1", SerialNo: "serial-1", MerchantID: "merchant-1", diff --git a/backend/internal/integrations/payment/leshua/client.go b/backend/internal/integrations/payment/leshua/client.go index f9fc343..55e49c9 100644 --- a/backend/internal/integrations/payment/leshua/client.go +++ b/backend/internal/integrations/payment/leshua/client.go @@ -395,7 +395,7 @@ func notifyParamKeys(params map[string]string) []string { } func (c *Client) validate() error { - if c.cfg.GatewayURL == "" || c.cfg.MerchantID == "" || c.cfg.SignKey == "" { + if strings.TrimSpace(c.cfg.GatewayURL) == "" || c.cfg.MerchantID == "" || c.cfg.SignKey == "" { return ErrConfigIncomplete } if c.cfg.SignType != "" && !strings.EqualFold(c.cfg.SignType, "MD5") { @@ -409,7 +409,7 @@ func (c *Client) post(ctx context.Context, params map[string]string) (map[string for key, value := range params { values.Set(key, value) } - httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, c.cfg.GatewayURL, strings.NewReader(values.Encode())) + httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, strings.TrimSpace(c.cfg.GatewayURL), strings.NewReader(values.Encode())) if err != nil { return nil, err } diff --git a/backend/internal/modules/paymentconfig/mutation.go b/backend/internal/modules/paymentconfig/mutation.go index db20572..464fcec 100644 --- a/backend/internal/modules/paymentconfig/mutation.go +++ b/backend/internal/modules/paymentconfig/mutation.go @@ -3,6 +3,7 @@ package paymentconfig import ( "context" "errors" + "strings" "hfb_sys/backend/internal/model" @@ -12,6 +13,7 @@ import ( // Create 创建配置 func (r *Repository) Create(ctx context.Context, req CreateRequest, actorID uint64, meta AuditMeta) (*ConfigDTO, error) { + req = normalizeCreateRequest(req) // 验证必填字段 if err := r.validateCreateRequest(req); err != nil { return nil, err @@ -91,10 +93,9 @@ func (r *Repository) Create(ctx context.Context, req CreateRequest, actorID uint return &dto, nil } -// Update 更新配置 - // Update 更新配置 func (r *Repository) Update(ctx context.Context, id uint64, req UpdateRequest, actorID uint64, meta AuditMeta) (*ConfigDTO, error) { + req = normalizeUpdateRequest(req) if req.SignType != nil && *req.SignType != "" && !isValidSignType(*req.SignType) { return nil, ErrInvalidSignType } @@ -222,7 +223,66 @@ func (r *Repository) Update(ctx context.Context, id uint64, req UpdateRequest, a return &dto, nil } -// Delete 删除配置 +// normalizeCreateRequest 清理后台创建配置时常见的复制粘贴空白,避免隐藏字符进入渠道请求。 +func normalizeCreateRequest(req CreateRequest) CreateRequest { + req.Name = strings.TrimSpace(req.Name) + req.Provider = strings.TrimSpace(req.Provider) + req.MerchantID = strings.TrimSpace(req.MerchantID) + req.GatewayURL = strings.TrimSpace(req.GatewayURL) + req.SignKey = strings.TrimSpace(req.SignKey) + req.NotifyKey = strings.TrimSpace(req.NotifyKey) + req.NotifyURL = strings.TrimSpace(req.NotifyURL) + req.JumpURL = strings.TrimSpace(req.JumpURL) + req.PayWay = strings.TrimSpace(req.PayWay) + req.JSPayFlag = strings.TrimSpace(req.JSPayFlag) + req.SignType = strings.TrimSpace(req.SignType) + req.Status = strings.TrimSpace(req.Status) + req.Environment = strings.TrimSpace(req.Environment) + req.ExtraConfig = trimExtraConfig(req.ExtraConfig) + return req +} + +// normalizeUpdateRequest 清理后台更新配置时提交的字符串字段。 +func normalizeUpdateRequest(req UpdateRequest) UpdateRequest { + trimStringPtr(req.Name) + trimStringPtr(req.MerchantID) + trimStringPtr(req.GatewayURL) + trimStringPtr(req.SignKey) + trimStringPtr(req.NotifyKey) + trimStringPtr(req.NotifyURL) + trimStringPtr(req.JumpURL) + trimStringPtr(req.PayWay) + trimStringPtr(req.JSPayFlag) + trimStringPtr(req.SignType) + trimStringPtr(req.Status) + trimStringPtr(req.Environment) + req.ExtraConfig = trimExtraConfig(req.ExtraConfig) + return req +} + +// trimStringPtr 原地清理可选字符串字段。 +func trimStringPtr(value *string) { + if value == nil { + return + } + *value = strings.TrimSpace(*value) +} + +// trimExtraConfig 清理扩展配置里的字符串值,证书和私钥内部换行会被保留。 +func trimExtraConfig(config map[string]any) map[string]any { + if config == nil { + return nil + } + trimmed := make(map[string]any, len(config)) + for key, value := range config { + if text, ok := value.(string); ok { + trimmed[key] = strings.TrimSpace(text) + continue + } + trimmed[key] = value + } + return trimmed +} // Delete 删除配置 func (r *Repository) Delete(ctx context.Context, id uint64, actorID uint64, meta AuditMeta) error {