- Don't treat refund_amount as "already refunded" in REFUND_REQUESTED status
(it's the requested amount, not actually refunded)
- Pre-fill refund amount with user's requested amount
- Show "already refunded" only for PARTIALLY_REFUNDED/REFUNDED orders
- Change action buttons from stacked icon+text to compact inline style
Admin order list:
- Add REFUND_REQUESTED "approve refund" button with amount badge
- Add REFUND_FAILED "retry refund" button
- Add missing status filters (REFUND_REQUESTED, REFUND_FAILED)
- Order detail dialog: show refund request info + audit logs
Admin refund dialog:
- Show refund request info card (violet) when user requested
- Display user balance with insufficient balance warning
- Add "no deduction" info when deduct_balance unchecked
- Add force refund checkbox (shown when requireForce=true)
- Add warning display prop
- Pre-fill reason from user's refund request
- Default deduct_balance to true
chore: bump version to 0.1.108.141
Backend:
- Define OrderTypeBalance/Subscription, EntityStatusActive, DeductionType*,
NotificationStatus* constants in payment/types.go
- Replace all magic strings in payment_order, payment_fulfillment, payment_refund
- Add local constants in easypay.go (tradeStatusSuccess, signTypeMD5)
- Add 27 unit tests for load balancer (filterByLimits, pickLeastAmount,
getInstanceChannelLimits, startOfDay)
Frontend:
- Remove all `any` types in SettingsView.vue (18 catch blocks + 1 payload)
- Fix bare catch blocks in PaymentResultView, PaymentView
- Add `unknown` type annotation to all catch blocks
chore: bump version to 0.1.108.140
- Replace all ¥ / ¥ with $ across all payment views and components
- Add out_trade_no column to admin order list table
- Add order number field to admin order detail dialog
chore: bump version to 0.1.108.138
- Call EnsureProviders before webhook provider lookup to fix
"provider not registered" error after restart or config changes
- Display out_trade_no instead of numeric ID in payment result pages
- Remove "at least one payment type required" validation, allow
saving providers with no payment types selected
- Auto-fill current domain for empty notify/return URL on save
(trim whitespace, fall back to window.location.origin)
- Add type="button" to all buttons inside the settings form that
are not submit buttons (edit, delete, refresh, create provider)
to prevent accidental form submission triggering main settings save
All Stripe methods now use confirmPayment({redirect: 'if_required'}):
- Card: confirms inline, shows success page
- WeChat: Stripe shows QR modal overlay on same page, no popup
- Alipay: Stripe redirects to Alipay page, returns to result page
Removes POPUP_METHODS, StripePopupView postMessage handshake, and
onStripeRedirect handler. Eliminates double-popup issue for WeChat
(3 windows → 1) and simplifies Alipay (2 windows → 1 redirect).
Stripe card payment now shows a success page with order details and a
manual confirm button, matching the QR code payment success flow.
Previously it auto-returned to the recharge page after 1.5 seconds.
EasyPay popup mode relies on notify_url (webhook) to update order status,
but if the callback is missed, orders stay PENDING forever. This adds:
- POST /api/v1/payment/orders/verify endpoint that actively queries
the upstream provider to check payment status
- Frontend PaymentResultView calls verify when receiving EasyPay
return_url params (out_trade_no)
- Fix checkPaid to fall back to OutTradeNo when PaymentTradeNo is empty
(popup mode doesn't have trade_no until the notify callback)
- Remove saveSettings() call from handleSaveProvider - editing a
provider should not save the entire settings form
- Change order ID format from sub2_5 to sub2_20250409_5_aB3kX9mQ
(prefix + date + dbID + 8-char alphanumeric random) to avoid
collisions with external payment providers
- parseOrderID supports both legacy and new format
Backend returns null for unconfigured fields. Object.assign overwrites
form defaults (e.g. payment_enabled_types: []) with null, causing
"Cannot read properties of null" errors. Now only non-null values
are applied, preserving safe defaults for unconfigured environments.
Add alipay_direct and wxpay_direct to METHOD_ORDER so they appear
in the correct position when enabled. The checkout API already returns
these types from direct provider instances; the method selector's
icon/style matching (includes('alipay')) already handles them.
Create StripePopupView.vue - a standalone minimal page (no AppLayout)
that receives clientSecret/publishableKey via postMessage from parent:
- Alipay: auto-calls confirmAlipayPayment(), shows "正在跳转..."
- WeChat: calls confirmWechatPayPayment(), shows QR code inline
- Much faster than loading full StripePaymentView (no API calls needed)
- Add easypay back to enabled provider toggles (alongside alipay, wxpay, stripe)
- Disabling a provider toggle now also disables all matching provider instances
- Simplify enabledProviderKeyOptions - direct mapping instead of special cases
- Label: "启用的服务商" with hint about instance auto-disable
- ExpireTimedOutOrders now cancels upstream payment (Stripe PaymentIntent)
before marking order as expired
- Active subscriptions moved below plan list with compact inline style
- StripePaymentView uses minimal layout (no AppLayout) in popup mode
- Fix misleading "服务商" label → "支持的支付方式" for payment type toggles
Instead of opening a popup that renders the full Payment Element again
(forcing users to re-select payment method), pass the selected method
via query param. The popup now directly calls:
- confirmAlipayPayment() for Alipay (redirects to Alipay page)
- confirmWechatPayPayment() for WeChat (displays QR code)
Card and Google Pay remain inline on the main page.
The EasyPay provider reads paymentMode from e.config["paymentMode"] to decide
between redirect (submit.php) and API (mapi.php) paths. However, paymentMode
is stored as a separate DB column, not inside the encrypted config JSON.
SelectInstance() returned it in the InstanceSelection struct but never injected
it into the Config map, so the provider always fell through to the API/QR path.
Also includes:
- Subscription success flow: show "订阅成功" and refresh on return
- Active subscription card: platform colors, rate/quota display
- Subscription plan card: compact layout to save height
- Provider list: drag-and-drop reorder via sort_order
Create utils/platformColors.ts as single source of truth for all
platform color classes (badge, border, accent, text, icon, button,
discount). Refactor SubscriptionPlanCard to use it.
Extend checkout API to return group name, rate multiplier, daily/weekly/monthly
limits and supported model scopes. Redesign SubscriptionPlanCard to display
platform badge, group quota info, and model scope tags.
- EasyPay: remove redirect mode, keep only qrcode and popup
- Stripe: remove mode config entirely, always use inline Payment Element
- Subscription: replace dialog with inline confirm UI (plan summary +
method selector + fee breakdown), same flow as top-up
- Move PaymentStatusPanel/StripePaymentInline to shared section for
both recharge and subscription payments
- Hide tabs and help card during payment/subscription confirm phases
When user selects a redirect-based payment method (e.g. Alipay) in the
inline Stripe Element and clicks pay, the payment page opens in a new
window instead of navigating the current page away. The main page
switches to waiting/polling mode. Inline methods (card, WeChat) still
complete directly on the page.
Stripe payment now shows the Payment Element directly on the purchase
page. User selects payment method (Alipay/WeChat/Card) inline, then
confirms. Redirect-based methods (Alipay) navigate to the payment
page; non-redirect methods (Card/WeChat) complete inline.
Quick amount buttons and custom input always use the global min/max
range (union of all methods). Payment method availability is
determined by whether the selected amount fits each method's limits.
Payment waiting/QR/success states now render inline on the page
instead of as a modal overlay dialog. The page content switches
between selection mode and payment status mode.
Backend:
- Extract YuanToFen/FenToYuan to payment/amount.go using shopspring/decimal
- Require alipay publicKey in config validation
- Fix wxpay webhook response to return JSON per V3 spec
- Remove wxpay certSerial fallback to publicKeyId
- Define magic strings as named constants in wxpay/alipay providers
- Add slog warning for wxpay H5→Native payment downgrade
- Make EncryptionKey validation return error on invalid (non-empty) key
- Make decryptConfig propagate errors instead of returning nil
- Add idempotency check in doBalance to prevent stuck FAILED retries
Frontend:
- Fix dashboard currency symbol from $ to ¥
- Fix AdminPaymentPlansView any type to proper SubscriptionPlan type
- Make quick amount buttons follow selected payment method limits
- Center help image with larger height and text below
Instead of navigating the main page away during payment, open Stripe
and EasyPay redirect payments in a popup window while showing a
waiting dialog on the purchase page. Extract POPUP_WINDOW_FEATURES
constant to providerConfig.ts.
QR code payments now display in an inline dialog on the payment page
with countdown and status polling. Success is shown directly without
page navigation. Redirect mode (pay_url only) still navigates to the
polling page as before.