- Fix features field sent as raw string causing character-by-character iteration
- Redesign SubscriptionPlanCard with gradient accent bar, checkmark feature list,
discount badge, and polished hover/button effects
- Adaptive plan grid layout (1/2/3 columns based on plan count)
- Improve subscription confirm dialog with better price display
Backend: new GET /payment/checkout-info returns methods (with limits),
global_min/max, plans (with platform), balance_disabled, help_text,
and stripe_publishable_key in one response.
Frontend: PaymentView now calls getCheckoutInfo() once instead of
fetchConfig + getLimits + fetchPlans separately. Removed plansLoading
state and loadPlans watcher. Reduced from 317 to 301 lines.
Backend:
- Add ProviderStatus* constants (pending/paid/success/failed/refunded)
- Add DefaultLoadBalanceStrategy, ConfigKeyPublishableKey constants
- GetBasePaymentType: use Type* constants instead of raw strings
- Webhook handler: use payment.Type* for provider keys, extract
webhookLogTruncateLen constant
- All 4 providers: replace status string literals with ProviderStatus*
Frontend:
- All catch blocks: add `: unknown` type annotation
- stores/payment.ts: replace `any` with proper type for plan parsing
- PaymentProviderDialog: replace `as any` with `as SelectOption[]`
Root cause: Stripe instance had supported_types="card,alipay,link,wxpay"
but only card/link were mapped to "stripe" group. Stripe's alipay/wxpay
leaked into independent groups, and since Stripe had no limits configured,
it triggered the "any unlimited → all zeros" early return, making ALL
method limits show as zero.
Fix: pcGroupByPaymentType now routes ALL types from Stripe provider
instances to the "stripe" group (by checking ProviderKey, not sub-type).
Frontend: Stripe provider dialog shows single "Stripe" limits entry
instead of per-sub-type entries.
Backend: change limits aggregation from intersection (most restrictive) to
union (least restrictive) across provider instances. Since the load balancer
can route to any instance, users should see the widest possible range.
Add global_min/global_max precomputed by backend for quick amount buttons.
Frontend: auto-disable payment methods that can't handle the entered amount,
auto-switch to first available method, show warning when no method fits.
Refactor: extract ChannelLimits named type, unionFloat helper to eliminate
repeated min/max/daily logic, split limits code to payment_config_limits.go,
replace magic strings with payment.Type* constants.
- Payment result page is now a standalone page without business layout
- Support EasyPay return_url params (out_trade_no, trade_status)
- Payment methods sorted: EasyPay → Alipay → WeChat → Stripe
- Alipay/WeChat use official brand SVG icons
- QR code displays Alipay/WeChat logo in center with scan prompt
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.
- Plan table: group column shows GroupBadge with platform color instead
of raw ID, column header changed to "分组"
- Plan dialog: group info preview uses GroupBadge, removes redundant
platform/rate fields
- Action buttons: all payment admin tables (plans, orders, providers)
now use vertically stacked icon+text buttons matching the app style
- Added i18n keys: payment.admin.group, common.view