mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-21 13:37:58 +00:00
fix(payment): webhook uses order's provider instance for signature verification
When multiple provider instances exist (e.g. 3 EasyPay accounts), the webhook handler now extracts out_trade_no from the callback, looks up the order, and uses the order's original provider instance for verification instead of picking an arbitrary instance from the registry.
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/payment"
|
||||
@@ -72,12 +73,13 @@ func (h *PaymentWebhookHandler) handleNotify(c *gin.Context, providerKey string)
|
||||
rawBody = string(body)
|
||||
}
|
||||
|
||||
// Ensure providers are loaded (registry may be empty after restart)
|
||||
h.paymentService.EnsureProviders(c.Request.Context())
|
||||
// Extract out_trade_no to look up the order's specific provider instance.
|
||||
// This is needed when multiple instances of the same provider exist (e.g. multiple EasyPay accounts).
|
||||
outTradeNo := extractOutTradeNo(rawBody, providerKey)
|
||||
|
||||
provider, err := h.registry.GetProviderByKey(providerKey)
|
||||
provider, err := h.paymentService.GetWebhookProvider(c.Request.Context(), providerKey, outTradeNo)
|
||||
if err != nil {
|
||||
slog.Warn("[Payment Webhook] provider not registered", "provider", providerKey, "error", err)
|
||||
slog.Warn("[Payment Webhook] provider not found", "provider", providerKey, "outTradeNo", outTradeNo, "error", err)
|
||||
writeSuccessResponse(c, providerKey)
|
||||
return
|
||||
}
|
||||
@@ -114,6 +116,21 @@ func (h *PaymentWebhookHandler) handleNotify(c *gin.Context, providerKey string)
|
||||
writeSuccessResponse(c, providerKey)
|
||||
}
|
||||
|
||||
// extractOutTradeNo parses the webhook body to find the out_trade_no.
|
||||
// This allows looking up the correct provider instance before verification.
|
||||
func extractOutTradeNo(rawBody, providerKey string) string {
|
||||
switch providerKey {
|
||||
case payment.TypeEasyPay:
|
||||
values, err := url.ParseQuery(rawBody)
|
||||
if err == nil {
|
||||
return values.Get("out_trade_no")
|
||||
}
|
||||
}
|
||||
// For other providers (Stripe, Alipay direct, WxPay direct), the registry
|
||||
// typically has only one instance, so no instance lookup is needed.
|
||||
return ""
|
||||
}
|
||||
|
||||
// wxpaySuccessResponse is the JSON response expected by WeChat Pay webhook.
|
||||
type wxpaySuccessResponse struct {
|
||||
Code string `json:"code"`
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
dbent "github.com/Wei-Shaw/sub2api/ent"
|
||||
"github.com/Wei-Shaw/sub2api/ent/paymentorder"
|
||||
"github.com/Wei-Shaw/sub2api/ent/paymentproviderinstance"
|
||||
"github.com/Wei-Shaw/sub2api/internal/payment"
|
||||
"github.com/Wei-Shaw/sub2api/internal/payment/provider"
|
||||
@@ -218,6 +219,25 @@ func (s *PaymentService) loadProviders(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetWebhookProvider returns the provider instance that should verify a webhook.
|
||||
// It extracts out_trade_no from the raw body, looks up the order to find the
|
||||
// original provider instance, and creates a provider with that instance's credentials.
|
||||
// Falls back to the registry provider when the order cannot be found.
|
||||
func (s *PaymentService) GetWebhookProvider(ctx context.Context, providerKey, outTradeNo string) (payment.Provider, error) {
|
||||
if outTradeNo != "" {
|
||||
order, err := s.entClient.PaymentOrder.Query().Where(paymentorder.OutTradeNo(outTradeNo)).Only(ctx)
|
||||
if err == nil {
|
||||
p, pErr := s.getOrderProvider(ctx, order)
|
||||
if pErr == nil {
|
||||
return p, nil
|
||||
}
|
||||
slog.Warn("[Webhook] order provider creation failed, falling back to registry", "outTradeNo", outTradeNo, "error", pErr)
|
||||
}
|
||||
}
|
||||
s.EnsureProviders(ctx)
|
||||
return s.registry.GetProviderByKey(providerKey)
|
||||
}
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
func psIsRefundStatus(s string) bool {
|
||||
|
||||
Reference in New Issue
Block a user