Upstream proxy double-encodes GET query params. url.ParseQuery only
decodes once, leaving values like %E5%BF%AB instead of actual UTF-8.
Add fullyDecodeURL to repeatedly decode until stable before signing.
RawQuery from GET callbacks can be double-URL-encoded by upstream
proxies. Use Query().Encode() to rebuild from fully decoded params,
ensuring VerifyNotification computes the signature on correct values.
- After opening pay_url in new window, navigate to order status page
with countdown timer and order polling (reuses QRCode view)
- QRCode view now shows "pay in new window" UI when no QR code present
- Add verbose logging for webhook signature verification failures
enhanceCSPPolicy now adds https://*.stripe.com to script-src and
frame-src when not already present, ensuring Stripe.js loads even
when the CSP policy comes from database settings.
- Add https://*.stripe.com to script-src and frame-src in default CSP
policy so Stripe.js can load in the browser
- Show upstream payment gateway error details instead of generic
"temporarily unavailable" message
- EasyPay redirect passes all configured CIDs (cidAlipay, cidWxpay) comma-separated
- Payment redirect opens in new window instead of current window redirect
- Order action column uses styled icon buttons with tooltip
- Pending order limit error returns i18n-friendly message with metadata
- Cancel order rate limit feature (ported from sub2apipay) with rolling/fixed window modes
- Admin settings UI for cancel rate limit configuration
- Fix provider dialog triggering unintended settings save on open
- Provider save now correctly calls saveSettings after provider update
- API client interceptor forwards reason and metadata fields for structured error handling
Root cause: invokeProvider used the registry's provider (created once
at startup from an arbitrary instance) instead of the selected
instance's config. This meant payments always used one instance's
credentials regardless of which instance was selected.
Fix: create a fresh provider from the selected instance's decrypted
config for each payment call, ensuring correct credentials (PID, PKey,
CID, Stripe keys, etc.).
Also fix SelectInstance for Stripe: when paymentType equals providerKey
(e.g. "stripe"), all instances of that provider are candidates. The
DB stores sub-types (card,alipay,wxpay,link) not "stripe" itself.
Backend request/response structs used camelCase JSON tags while the
rest of the codebase uses snake_case (from Ent ORM entities). This
caused field name mismatches — notably the admin refund deduct_balance
field was silently ignored.
Backend: changed all camelCase JSON tags in payment handler/service
structs to snake_case (CreateOrderRequest, CreateOrderResponse,
CreatePlanRequest, UpdatePlanRequest, CreateProviderInstanceRequest,
UpdateProviderInstanceRequest, AdminProcessRefundRequest, RefundResult).
Frontend: updated all API call payloads and response field accesses
to use snake_case, removed manual camelCase conversion in
buildPlanPayload, toggleForSale, provider dialog emit, etc.
- EasyPay VerifyNotification was not parsing the 'money' field, causing
Amount=0 in PaymentNotification → amount mismatch error on callback
- Cancel button on QR page now shows "取消订单" instead of generic "取消"
- EasyPay webhook: add GET route + read params from URL query (fix 404)
- Stripe: expose as single "stripe" method to users, sub-types (card/link/
alipay/wxpay) passed to PaymentIntent internally via instance config
- QR code page: use order expiresAt for countdown, add cancel button
- InstanceSelection carries SupportedTypes for provider-specific routing
- Fix CreateOrderRequest field name mismatch (payment_type → paymentType)
- Payment page now discovers available types from providers (not global config)
- Backend GetLimits returns map keyed by payment type
- EasyPay shows 3 buttons (跳转/支付宝/微信), Stripe shows card/alipay/wxpay/link
- Auto-select first method after limits loaded
- Skip UpdatePaymentConfig when no payment fields provided (prevents wipe)
- Remove unused defaultMinRechargeAmount/defaultMaxRechargeAmount constants
- Fix mergeConfig comment to match actual behavior
- Add payment fields to UpdateSettingsRequest TypeScript interface
- Remove duplicate payment_enabled_types assignment in saveSettings
- Remove all (payload as any) casts for payment fields
- Remove unused parseTypes function from providerConfig.ts
- Backend API returns/accepts supported_types as string[] (converts to/from DB comma string)
- Frontend ProviderInstance.supported_types is string[], no more parseTypes
- Remove all split/join conversions for supported_types
- payment_enabled_types also uses string[] consistently
- Backend: payment fields added to GET/PUT /admin/settings (full replace)
- Frontend: single API call for all settings (no separate payment config API)
- Payment page: show "充值未开放" when no payment methods available
- Pending order check when disabling provider
- Backend: PENDING_ORDERS error uses reason+metadata per CLAUDE.md spec
- Block disabling provider when it has pending orders
- ProviderCard: remove bottom hint area, use opacity + title tooltip instead
- min/max/daily = 0 means no limit (no enforcement)
- Quick amounts: show all when no limit set
- Amount validation: skip check when limit is 0
- Placeholder: adaptive (shows range, ≥min, ≤max, or "输入金额")
- Payment config update is now full-replace (not patch): all fields sent every time
- 0 values for min/max/daily = clear (use default: min=1, max=unlimited)
- Payment page: provider-level limits override global, proper fallback chain
- Fix quick amounts disappearing when global min/max is empty