fix: merge 30 general improvements from release branch
Bug fixes: - Detached context for GetAccountConcurrencyBatch (prevent all-zero on request cancel) - Filter soft-deleted users in GetByGroupID - Stripe CSP policy (allow Stripe.js in script-src and frame-src) - WebSearch API key validation on save - RECHARGING status in payment result success check - Windows test fixes (logger Sync deadlock, config path escaping) Feature enhancements: - Webhook multi-instance dispatch (extractOutTradeNo + GetWebhookProvider) - EasyPay mobile H5 payment (device param + PayURL2) - SSE error propagation in WebSearch emulation - AccountStatsCost DTO field for admin usage logs - Plans sort by sort_order instead of created_at - UsageMapHook for streaming response usage data - apicompat Instructions field passthrough - EffectiveLoadFactor for ops concurrency/metrics - Usage billing RETURNING balance for notify system - BulkUpdate mixed channel warning with details - println to slog migration in auth cache - Wire ProviderSet cleanup - CI cache-dependency-path optimization Frontend: - Refund eligibility check per provider (canRequestRefund) - Plan sort_order editing - Dead code cleanup (simulate_claude_max, client_affinity) - GroupsView platform switch guard - channels features_config API type - UsageView account_stats_cost export
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,9 +73,13 @@ func (h *PaymentWebhookHandler) handleNotify(c *gin.Context, providerKey string)
|
||||
rawBody = string(body)
|
||||
}
|
||||
|
||||
provider, err := h.registry.GetProviderByKey(providerKey)
|
||||
// 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.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
|
||||
}
|
||||
@@ -111,19 +116,40 @@ 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"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// WeChat Pay webhook success response constants.
|
||||
const (
|
||||
wxpaySuccessCode = "SUCCESS"
|
||||
wxpaySuccessMessage = "成功"
|
||||
)
|
||||
|
||||
// writeSuccessResponse sends the provider-specific success response.
|
||||
// WeChat Pay requires JSON {"code":"SUCCESS","message":"成功"};
|
||||
// Stripe expects an empty 200; others accept plain text "success".
|
||||
func writeSuccessResponse(c *gin.Context, providerKey string) {
|
||||
switch providerKey {
|
||||
case payment.TypeWxpay:
|
||||
c.JSON(http.StatusOK, wxpaySuccessResponse{Code: "SUCCESS", Message: "成功"})
|
||||
c.JSON(http.StatusOK, wxpaySuccessResponse{Code: wxpaySuccessCode, Message: wxpaySuccessMessage})
|
||||
case payment.TypeStripe:
|
||||
c.String(http.StatusOK, "")
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user