fix(payment): restore double URL decode for EasyPay webhook values

EasyPay callbacks arrive with double-encoded query values (e.g. %25E5 instead
of %E5) due to redirect chains. url.ParseQuery decodes once; decodeURLValue
applies a second safe decode so the sign matches what EasyPay computed.
Also removes temporary debug logging.
This commit is contained in:
erio
2026-04-10 14:27:44 +08:00
parent 72f7677815
commit 9b38e44e1d
3 changed files with 7 additions and 12 deletions
@@ -95,7 +95,8 @@ func (h *PaymentWebhookHandler) handleNotify(c *gin.Context, providerKey string)
if len(truncatedBody) > webhookLogTruncateLen {
truncatedBody = truncatedBody[:webhookLogTruncateLen] + "...(truncated)"
}
slog.Error("[Payment Webhook] verify failed", "provider", providerKey, "error", err, "method", c.Request.Method, "bodyLen", len(rawBody), "rawBody", truncatedBody)
slog.Error("[Payment Webhook] verify failed", "provider", providerKey, "error", err, "method", c.Request.Method, "bodyLen", len(rawBody))
slog.Debug("[Payment Webhook] verify failed body", "provider", providerKey, "rawBody", truncatedBody)
c.String(http.StatusBadRequest, "verify failed")
return
}
+4 -5
View File
@@ -9,7 +9,6 @@ import (
"encoding/json"
"fmt"
"io"
"log/slog"
"net/http"
"net/url"
"sort"
@@ -177,18 +176,18 @@ func (e *EasyPay) VerifyNotification(_ context.Context, rawBody string, _ map[st
if err != nil {
return nil, fmt.Errorf("parse notify: %w", err)
}
// url.ParseQuery already decodes values — no additional decode needed.
// EasyPay callbacks may arrive with double-encoded values (e.g. via redirect
// chains), so url.ParseQuery decodes once, then decodeURLValue decodes again.
// If a value is NOT double-encoded, QueryUnescape returns it unchanged.
params := make(map[string]string)
for k := range values {
params[k] = values.Get(k)
params[k] = decodeURLValue(values.Get(k))
}
sign := params["sign"]
if sign == "" {
return nil, fmt.Errorf("missing sign")
}
if !easyPayVerifySign(params, e.config["pkey"], sign) {
expected := easyPaySign(params, e.config["pkey"])
slog.Error("[EasyPay] sign mismatch", "expected", expected, "got", sign, "pkeyLen", len(e.config["pkey"]), "instanceID", e.instanceID, "paramCount", len(params))
return nil, fmt.Errorf("invalid signature")
}
status := payment.ProviderStatusFailed
+1 -6
View File
@@ -229,15 +229,10 @@ func (s *PaymentService) GetWebhookProvider(ctx context.Context, providerKey, ou
if err == nil {
p, pErr := s.getOrderProvider(ctx, order)
if pErr == nil {
slog.Info("[Webhook] using order's provider instance", "outTradeNo", outTradeNo, "instanceID", order.ProviderInstanceID, "providerKey", p.ProviderKey())
return p, nil
}
slog.Warn("[Webhook] order provider creation failed, falling back to registry", "outTradeNo", outTradeNo, "instanceID", order.ProviderInstanceID, "error", pErr)
} else {
slog.Warn("[Webhook] order lookup failed", "outTradeNo", outTradeNo, "error", err)
slog.Warn("[Webhook] order provider creation failed, falling back to registry", "outTradeNo", outTradeNo, "error", pErr)
}
} else {
slog.Info("[Webhook] no out_trade_no extracted, using registry", "providerKey", providerKey)
}
s.EnsureProviders(ctx)
return s.registry.GetProviderByKey(providerKey)