From bb624acae7015d8bbcbab25b2305ec6c0491fdf7 Mon Sep 17 00:00:00 2001 From: yml Date: Sat, 6 Jun 2026 23:21:59 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8B=89=E5=8D=A1=E6=8B=89=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E4=B8=8D=E9=99=90=E5=88=B6=E6=94=AF=E4=BB=98=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integrations/payment/lakala/client.go | 15 ++++- .../payment/lakala/client_test.go | 61 +++++++++++++++++++ .../admin/components/PaymentConfigDialog.vue | 16 ++++- 3 files changed, 88 insertions(+), 4 deletions(-) diff --git a/backend/internal/integrations/payment/lakala/client.go b/backend/internal/integrations/payment/lakala/client.go index 26777fc..79a09a1 100644 --- a/backend/internal/integrations/payment/lakala/client.go +++ b/backend/internal/integrations/payment/lakala/client.go @@ -169,7 +169,7 @@ func (c *Client) CreatePayment(ctx context.Context, req CreatePaymentRequest) (* return nil, err } payWay := firstNonEmpty(req.PayWay, c.cfg.PayWay, "ZFBZF") - payMode := firstNonEmpty(c.cfg.PayMode, payModeFromPayWay(payWay), "ALIPAY") + payMode := normalizeCounterPayMode(c.cfg.PayMode) expireMinutes := c.cfg.OrderExpireMinutes if expireMinutes <= 0 { expireMinutes = 15 @@ -183,9 +183,11 @@ func (c *Client) CreatePayment(ctx context.Context, req CreatePaymentRequest) (* "support_refund": 1, "support_repeat_pay": 1, "support_cancel": 0, - "counter_param": fmt.Sprintf(`{"pay_mode":"%s"}`, payMode), "order_info": sanitizeText(req.Body, 128), } + if payMode != "" { + reqData["counter_param"] = fmt.Sprintf(`{"pay_mode":"%s"}`, payMode) + } if c.cfg.TermNo != "" { reqData["term_no"] = c.cfg.TermNo } @@ -710,6 +712,15 @@ func payModeFromPayWay(payWay string) string { } } +func normalizeCounterPayMode(value string) string { + switch strings.ToUpper(strings.TrimSpace(value)) { + case "", "AUTO", "ALL", "NONE", "UNLIMITED": + return "" + default: + return strings.ToUpper(strings.TrimSpace(value)) + } +} + func sanitizeText(value string, maxRunes int) string { value = strings.TrimSpace(value) if value == "" || maxRunes <= 0 { diff --git a/backend/internal/integrations/payment/lakala/client_test.go b/backend/internal/integrations/payment/lakala/client_test.go index f330c3c..ed6273f 100644 --- a/backend/internal/integrations/payment/lakala/client_test.go +++ b/backend/internal/integrations/payment/lakala/client_test.go @@ -1,10 +1,14 @@ package lakala import ( + "context" "crypto/rand" "crypto/rsa" "crypto/x509" + "encoding/json" "encoding/pem" + "net/http" + "net/http/httptest" "strings" "testing" ) @@ -70,6 +74,22 @@ func TestParsePayloadNormalizesAliases(t *testing.T) { } } +func TestCreatePaymentOmitsCounterParamWhenPayModeBlank(t *testing.T) { + body := captureCreatePaymentBody(t, "") + reqData := body["req_data"].(map[string]any) + if _, ok := reqData["counter_param"]; ok { + t.Fatalf("counter_param exists when pay_mode is blank: %#v", reqData["counter_param"]) + } +} + +func TestCreatePaymentSetsCounterParamWhenPayModeConfigured(t *testing.T) { + body := captureCreatePaymentBody(t, "WECHAT") + reqData := body["req_data"].(map[string]any) + if reqData["counter_param"] != `{"pay_mode":"WECHAT"}` { + t.Fatalf("counter_param = %#v, want WECHAT", reqData["counter_param"]) + } +} + func testKeyPairPEM(t *testing.T, key *rsa.PrivateKey) (string, string) { t.Helper() privateDER := x509.MarshalPKCS1PrivateKey(key) @@ -88,3 +108,44 @@ func testKeyPairPEM(t *testing.T, key *rsa.PrivateKey) (string, string) { })) return privatePEM, certPEM } + +func captureCreatePaymentBody(t *testing.T, payMode string) map[string]any { + t.Helper() + var captured map[string]any + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != endpointCounterCreate { + t.Fatalf("path = %s, want %s", r.URL.Path, endpointCounterCreate) + } + if err := json.NewDecoder(r.Body).Decode(&captured); err != nil { + t.Fatalf("decode request: %v", err) + } + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"resp_code":"0","result_code":"0","pay_order_no":"LK1"}`)) + })) + defer server.Close() + + key, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatalf("generate key: %v", err) + } + privatePEM, certPEM := testKeyPairPEM(t, key) + client := NewClient(Config{ + GatewayURL: server.URL, + AppID: "app-1", + SerialNo: "serial-1", + MerchantID: "merchant-1", + PrivateKey: privatePEM, + NotifyCert: certPEM, + PayMode: payMode, + }) + _, err = client.CreatePayment(context.Background(), CreatePaymentRequest{ + ThirdOrderID: "ORDER1", + AmountCent: 100, + NotifyURL: "https://example.com/notify", + Body: "测试订单", + }) + if err != nil { + t.Fatalf("CreatePayment error = %v", err) + } + return captured +} diff --git a/frontend/src/features/admin/components/PaymentConfigDialog.vue b/frontend/src/features/admin/components/PaymentConfigDialog.vue index c7ea846..1a618cb 100644 --- a/frontend/src/features/admin/components/PaymentConfigDialog.vue +++ b/frontend/src/features/admin/components/PaymentConfigDialog.vue @@ -42,7 +42,7 @@ function defaultExtraConfig(provider: string) { serial_no: '', term_no: '', lakala_cert: '', - pay_mode: 'ALIPAY', + pay_mode: 'AUTO', order_expire_minutes: 15, } } @@ -93,6 +93,17 @@ const extraConfig = computed>(() => { return formData.value.extra_config }) +function normalizeExtraConfig(provider: string, extraConfig?: Record | null) { + const extra = { + ...defaultExtraConfig(provider), + ...(extraConfig || {}), + } + if (provider === 'lakala' && !extra.pay_mode) { + extra.pay_mode = 'AUTO' + } + return extra +} + watch( () => props.modelValue, (val) => { @@ -107,7 +118,7 @@ watch( pay_way: props.config.pay_way, jspay_flag: props.config.jspay_flag, sign_type: props.config.sign_type, - extra_config: props.config.extra_config || defaultExtraConfig(props.config.provider), + extra_config: normalizeExtraConfig(props.config.provider, props.config.extra_config), is_default: props.config.is_default, status: props.config.status, environment: props.config.environment, @@ -313,6 +324,7 @@ function handleClose() { +