From e76e0499d75b6359abadda4d0ec787854d976960 Mon Sep 17 00:00:00 2001 From: MoRanHuiShou-pug <131538457+MoRanHuiShou-pug@users.noreply.github.com> Date: Sun, 5 Jul 2026 03:55:40 +0800 Subject: [PATCH] fix: sanitize payment response NUL bytes --- backend/internal/service/payment_order.go | 17 +++++++++ .../service/payment_order_result_test.go | 35 +++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/backend/internal/service/payment_order.go b/backend/internal/service/payment_order.go index 29fe40b1b6..154159b932 100644 --- a/backend/internal/service/payment_order.go +++ b/backend/internal/service/payment_order.go @@ -453,6 +453,7 @@ func (s *PaymentService) invokeProvider(ctx context.Context, order *dbent.Paymen } return nil, classifyCreatePaymentError(req, sel.ProviderKey, err) } + sanitizeCreatePaymentResponseDetails(pr) _, err = s.entClient.PaymentOrder.UpdateOneID(order.ID). SetNillablePaymentTradeNo(psNilIfEmpty(pr.TradeNo)). SetNillablePayURL(psNilIfEmpty(pr.PayURL)). @@ -480,6 +481,22 @@ func (s *PaymentService) invokeProvider(ctx context.Context, order *dbent.Paymen return resp, nil } +func sanitizeCreatePaymentResponseDetails(pr *payment.CreatePaymentResponse) { + if pr == nil { + return + } + pr.TradeNo = removePostgresTextNUL(pr.TradeNo) + pr.PayURL = removePostgresTextNUL(pr.PayURL) + pr.QRCode = removePostgresTextNUL(pr.QRCode) +} + +func removePostgresTextNUL(value string) string { + if !strings.ContainsRune(value, 0) { + return value + } + return strings.ReplaceAll(value, "\x00", "") +} + func buildProviderCreatePaymentRequest(req CreateOrderRequest, sel *payment.InstanceSelection, orderID, amount, subject string) payment.CreatePaymentRequest { return payment.CreatePaymentRequest{ OrderID: orderID, diff --git a/backend/internal/service/payment_order_result_test.go b/backend/internal/service/payment_order_result_test.go index b7545ee45d..14192bd6cd 100644 --- a/backend/internal/service/payment_order_result_test.go +++ b/backend/internal/service/payment_order_result_test.go @@ -91,6 +91,41 @@ func TestBuildCreateOrderResponseCopiesJSAPIPayload(t *testing.T) { } } +func TestSanitizeCreatePaymentResponseDetailsRemovesNULBytes(t *testing.T) { + t.Parallel() + + resp := &payment.CreatePaymentResponse{ + TradeNo: "trade\x00-no", + PayURL: "https://pay.example.com/\x00checkout", + QRCode: "wxp://payment-token\x00", + ClientSecret: "secret\x00unchanged", + } + + sanitizeCreatePaymentResponseDetails(resp) + + if strings.ContainsRune(resp.TradeNo, 0) { + t.Fatalf("trade_no still contains NUL: %q", resp.TradeNo) + } + if strings.ContainsRune(resp.PayURL, 0) { + t.Fatalf("pay_url still contains NUL: %q", resp.PayURL) + } + if strings.ContainsRune(resp.QRCode, 0) { + t.Fatalf("qr_code still contains NUL: %q", resp.QRCode) + } + if resp.TradeNo != "trade-no" { + t.Fatalf("trade_no = %q, want trade-no", resp.TradeNo) + } + if resp.PayURL != "https://pay.example.com/checkout" { + t.Fatalf("pay_url = %q, want sanitized URL", resp.PayURL) + } + if resp.QRCode != "wxp://payment-token" { + t.Fatalf("qr_code = %q, want sanitized QR code", resp.QRCode) + } + if resp.ClientSecret != "secret\x00unchanged" { + t.Fatalf("client_secret = %q, should not be touched by payment detail sanitization", resp.ClientSecret) + } +} + func TestValidateSelectedCreateOrderAmountCurrencyRejectsFractionalZeroDecimal(t *testing.T) { t.Parallel()