Replace Go startup code with SQL migration 096 to migrate
purchase_subscription_url to custom_menu_items. Database migrations
run exactly once and are tracked by the migration system.
- Restore MigrateLegacyPurchaseURL auto-call on startup: converts old
purchase_subscription_url into a custom menu item (id: migrated_purchase_subscription)
and clears the legacy settings
- Delete PurchaseRouter.vue and PurchaseSubscriptionView.vue (replaced
by existing CustomPageView.vue via custom menu system)
- /purchase route now exclusively serves the new built-in payment system
- Remove purchase_subscription_enabled from sidebar, router, settings UI,
TypeScript types, and i18n keys
- Legacy purchase URL users will see their link as a custom menu entry
after upgrade, editable in admin custom menu settings
- Add PurchaseRouter.vue to dynamically render PaymentView or
PurchaseSubscriptionView based on payment_enabled vs
purchase_subscription_enabled settings
- Fix route guard to allow access when either payment system is enabled
- Split sidebar menu: /purchase shows for both systems, /orders only
for built-in payment
- Remove auto-migration that forcibly disabled legacy purchase system
on first startup (MigrateLegacyPurchaseURL)
- Add payment_enabled to PublicSettings API response so frontend can
check both systems from a single endpoint
- Restore legacy purchase link config UI controls in admin Payment tab
- Split payment settings save to use dedicated PUT /admin/payment/config
API instead of mixing into general settings endpoint
- Add admin payment config API functions (getConfig/updateConfig)
- payment_service: split GetDashboardStats (131→35 lines) into 4 sub-functions,
extract applyPagination helper, replace bubble sort with sort.Slice,
use sync.Once for EnsureProviders, define magic number constants
- providers: merge duplicate PC/Mobile functions in alipay/wxpay,
extract loadKeyPair/queryOrderTotalFen in wxpay, add centsToYuan in stripe,
define constants for success codes, currencies, event types
- handlers: extract requireAuth and parseIDParam helpers to eliminate
repeated auth/ID-parsing boilerplate
- registry: remove dead seen map in GetProviderByKey
- types: centralize order status constants in payment package
- load_balancer+config_service: unify duplicated containsType into
exported InstanceSupportsType function
Critical:
- Refund idempotency keys now include UnixNano timestamp (alipay/wxpay)
- PaymentView passes qr/pay_url params to QR code page
- Fix dead code branch in order result handling
Quality:
- Currency symbol $ → ¥ in admin stats and refund dialog
- StripePaymentView setTimeout cleanup on unmount
- Webhook body size limited to 1MB (io.LimitReader)
- SubscriptionPlan features type documented
Antigravity groups were incorrectly matching pricing and model mapping
entries from anthropic/gemini platform tabs. Each platform should be
strictly isolated — antigravity groups only use antigravity-tagged pricing.
Restore gateway_service.go, setting_handler.go, routes/admin.go,
dto/settings.go, group_repo.go, api_key_repo.go, wire_gen.go to
upstream/main versions and surgically remove only Sora references.
This preserves upstream-only features (RequireOauthOnly, RequirePrivacySet,
GroupResolution, etc.) that were missing when using release branch versions.
C1: Add EnsureProviders() lazy initialization to populate Registry
from PaymentProviderInstance DB rows at first use
C2: Delete stale payment/provider_factory.go dead code
C3: Add PaymentOrderExpiryService background ticker (60s interval)
C4: Expand DashboardStats with daily_series, payment_methods, top_users
C5+C6: Add 152 payment i18n keys (en + zh) for admin and settings pages
C7: Fix Stripe global key race — use per-instance client.API
I1: Remove old purchase_subscription_enabled/url from settings form
I2: Add MigrateLegacyPurchaseURL to auto-convert old URL to custom menu
I3: Add migration 092 no-op placeholder for numbering continuity
When a channel has no model mapping for the requested model, ChannelMappedModel
equals OriginalModel (the user's arbitrary input). Combined with the default
BillingModelSource="channel_mapped", this incorrectly overrides the BillingModel
set by the OpenAI format conversion layer (e.g., gpt-5.4 from DefaultMappedModel)
back to the unmapped original model (e.g., glm) which has no pricing — resulting
in zero-cost billing.
Add guard condition so the channel_mapped override only fires when the channel
actually changed the model (ChannelMappedModel != OriginalModel).
- Remove media_type column from all INSERT/SELECT/SCAN in usage_log_repo
- Remove media_type mock arg from request_type and integration tests
- Adjust scan stub value arrays from 47 to 46 elements
- Run gofmt on user schema, config test, group handler
- Remove unused mergeGroupIDs function
- Restore shared test helpers (newJSONResponse, queuedHTTPUpstream)
that were in deleted Sora test file
Antigravity groups were incorrectly matching pricing and model mapping
entries from anthropic/gemini platform tabs. Each platform should be
strictly isolated — antigravity groups only use antigravity-tagged pricing.
- Remove media_type column from all INSERT/SELECT/SCAN in usage_log_repo
- Remove media_type mock arg from request_type and integration tests
- Adjust scan stub value arrays from 47 to 46 elements