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
- Batch-query daily usage in one SQL (GroupBy) instead of N queries
- Include PENDING orders in daily usage to prevent over-committing
- Check remaining capacity (used + orderAmount > limit) not just used >= limit
- Check SingleMin/SingleMax per-channel limits
- Pre-attach usage to candidates, reuse across filter and strategy pick
- Fallback to full list with warn log when all instances exhausted
chore: bump version to 0.1.108.139
- 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
Check used + orderAmount > dailyLimit (not just used >= limit) and
also filter by SingleMin/SingleMax per-channel limits.
chore: bump version to 0.1.108.137
Previously SelectInstance always used round-robin regardless of the
configured strategy. Now it reads the strategy from payment config
and selects the instance with the lowest daily transaction amount
when "least-amount" is configured.
chore: bump version to 0.1.108.136
- 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
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)
Order ID format changed from sub2_5 to sub2_20250409aB3kX9mQ
(prefix + date + 8-char random, no DB ID embedded).
- Add out_trade_no column to payment_orders table
- Store generated external ID on order creation
- Webhook handler looks up order by out_trade_no instead of parsing DB ID
- Fallback to legacy sub2_N format for existing orders
- 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
- 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
createRedirectPayment was missing the cid parameter (channel ID) that
createAPIPayment already included via resolveCID(). This caused EasyPay
to use a default channel instead of the configured one (e.g. 13256/13264).
Also trim trailing slash from apiBase to prevent double-slash in URLs.
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
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.