- 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
- 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
- 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
- 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
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 "取消"
- Fix plan create/update: send camelCase keys (groupId, validityDays,
forSale, etc.) matching backend JSON tags instead of snake_case
- Show GroupBadge with platform color instead of raw group ID in table
- Replace checkbox with toggle switch for for_sale, support quick
toggle directly from the plan list
- Parse features string to array when loading plans (both admin and user)
- Fix price display to use ¥ (CNY) instead of $
- 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
- 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
- Add help_image_url and help_text to PaymentConfig, UpdateRequest, read/write logic
- Add these fields to frontend paymentPayload save and load
- Fix provider config update: merge new config with existing (preserves sensitive
fields that frontend skips as ••••••••, prevents data loss on edit)
- Remove card/link from global allPaymentTypes (they belong inside Stripe provider)
- Resolve i18n labels for Stripe sub-methods in provider dialog and card
- Split callback URLs into two independent inputs (notify/return each with own base URL + fixed path)
- Add eye icon toggle for sensitive fields (PKey etc.) to reveal/hide values
- Bump version to 0.1.108.35