Commit Graph
671 Commits
Author SHA1 Message Date
erio 20da7a8d07 perf: run balance/quota notification checks async
Move notifyBalanceLow and notifyAccountQuota to goroutines so the
threshold checking logic (DB settings reads, threshold calculation,
crossing detection) no longer blocks the request thread. Both are
fire-and-forget with panic recovery and log-only error handling.
2026-04-13 16:57:53 +08:00
erio 8183ce79a3 fix(frontend): quota notify UI improvements
- QuotaNotifyToggle: add $ or % suffix to threshold input based on type
- QuotaLimitCard: combine reset mode and notify toggle on same row
  to reduce vertical height for daily/weekly sections
- Remove redundant ml-4 indentation from QuotaNotifyToggle
2026-04-13 16:52:02 +08:00
erio 261ea08892 fix: correct account stats pricing priority order
Priority was wrong:
- Before: custom rules → LiteLLM (when ApplyPricingToAccountStats) → nil
- After:  custom rules → totalCost (when ApplyPricingToAccountStats) → LiteLLM → nil

When ApplyPricingToAccountStats is enabled, use the request's actual
client billing cost (before multiplier) as account_stats_cost, instead
of recalculating from LiteLLM per-token prices which produced incorrect
values for per-request billing mode.

LiteLLM model pricing is now the final fallback (priority 3), used only
when neither custom rules nor ApplyPricingToAccountStats apply.
2026-04-13 16:45:10 +08:00
erio c6a9da4ea9 debug: add notification path logging for beta investigation
Add slog.Info/Debug logs to notifyBalanceLow, notifyAccountQuota,
CheckBalanceAfterDeduction, and sendEmails to diagnose why balance
and quota notifications are not being sent in beta environment.
2026-04-13 16:20:07 +08:00
erio 9c9945071f fix: expose account_stats_cost in usage log API and fix frontend display
Backend:
- Add AccountStatsCost field to AdminUsageLog DTO
- Map AccountStatsCost in UsageLogFromServiceAdmin mapper
- Replace println with slog.Warn in api_key_auth_cache_impl.go

Frontend:
- Add account_stats_cost to AdminUsageLog TypeScript interface
- Usage tooltip and table: prefer account_stats_cost when available,
  fallback to total_cost * multiplier (mirrors SQL COALESCE logic)
- Excel export: same COALESCE fallback
2026-04-13 16:00:52 +08:00
erio 068407d884 fix: add missing AccountQuotaNotifyEnabled to admin settings API
The field was present in SystemSettings response DTO and service layer
but missing from:
- UpdateSettingsRequest (admin handler) - saves were silently ignored
- GET/PUT response mapping in admin handler
- UpdateSettingsRequest (non-admin dto)

This caused the toggle to always revert to off after saving.
2026-04-13 15:30:06 +08:00
erio c85272606d fix(frontend): hide quota notify toggle when global setting is disabled
QuotaLimitCard now requires quotaNotifyGlobalEnabled prop to control
visibility of QuotaNotifyToggle components. When the global account
quota notification is disabled in admin settings, per-account threshold
toggles are hidden in both Edit and Create account modals.
2026-04-13 15:13:56 +08:00
erio f7f2c82981 fix: use DB transaction return values for balance/quota notification crossing detection
Replace the stale Redis cache read + invalidation hack with direct DB
transaction RETURNING values:

- deductUsageBillingBalance: add RETURNING balance to get post-deduction balance
- incrementUsageBillingAccountQuota: extend RETURNING to include all 6 quota
  dimensions (daily/weekly/total used+limit)
- Expand UsageBillingApplyResult with NewBalance and QuotaState fields
- finalizePostUsageBilling uses result directly, no Redis read needed
- CheckAccountQuotaAfterIncrement accepts optional QuotaState to skip
  fetchFreshAccount when DB values are available

This eliminates the race condition where QueueDeductBalance async update
hadn't propagated to Redis before GetUserBalance read, causing oldBalance
to be inflated and threshold crossing detection to always fail.
2026-04-13 15:03:49 +08:00
erio c37cff6a86 fix: round 3 audit fixes - SMTP header sanitization and goroutine safety
- Move sanitizeEmailHeader to SendEmailWithConfig entry point, covering all
  email senders (verify code, password reset, ops alerts, notifications)
- Add panic recovery to UpdateBalance goroutine
- Fix stale comment in getAccountQuotaNotifyEmails (email="" no longer used)
- Log error instead of silently discarding verifyNotifyCode cache update failure
2026-04-13 14:21:37 +08:00
erio ad351d4252 fix: audit fixes for websearch, notifications, and channel pricing
P0: fix wildcard matching test assertion (config order, not longest prefix)
P0: add TotalRecharged to auth cache snapshot (v5) for percentage threshold
P1: move pricing rules into per-platform sections in ChannelsView
P1: populate account name cache when editing existing channel rules
P1: sanitize email subject headers to prevent SMTP injection
P1: make Redis INCR+EXPIRE idempotent for rate limiting
P1: deep copy FeaturesConfig in Channel.Clone()
P2: clean up stale email="" placeholder comments
P2: replace log.Printf with slog in email_service.go
2026-04-13 13:59:35 +08:00
erio ff66bb5c36 chore: bump version to 0.1.110.19 2026-04-13 12:41:37 +08:00
erio 68f546e3ec chore: bump version to 0.1.110.18 2026-04-13 12:07:09 +08:00
erio ae9ea2e766 chore: bump version to 0.1.110.17 2026-04-13 11:37:08 +08:00
erio 834ba46da9 feat: WebSearch tri-state, account stats pricing fix, quota cache fix, usage tooltip
WebSearch tri-state switch:
- Account-level web_search_emulation changed from bool to tri-state
  string: "default" (follow channel) / "enabled" / "disabled"
- shouldEmulateWebSearch checks channel config when account is "default"
- SQL migration converts old bool values
- Frontend select replaces toggle in Edit/CreateAccountModal

Account stats pricing:
- resolveAccountStatsCost uses upstream model (post-mapping) for matching
- Priority: custom rules → model pricing file (when toggle on) → default
- Custom rules always configurable, independent of toggle
- Account ID field changed to searchable selector filtered by platform
- Description updated to reflect new behavior

Quota notification cache fix:
- CheckAccountQuotaAfterIncrement fetches real-time account from DB
- Reconstructs pre-increment usage for accurate threshold crossing detection
- New AccountQuotaReader interface (minimal: GetByID only)

Usage tooltip:
- Per-request/image billing shows per-request price instead of $0 token price
- Token billing continues to show input/output price per million tokens
2026-04-13 11:37:08 +08:00
erio 4f4267d646 chore: bump version to 0.1.110.16 2026-04-13 02:28:31 +08:00
erio e85ea0694b chore: bump version to 0.1.110.15 2026-04-13 01:40:13 +08:00
erio 621f767e29 chore: bump version to 0.1.110.14 2026-04-13 01:29:22 +08:00
erio 92ccda5859 chore: bump version to 0.1.110.13 2026-04-13 00:52:56 +08:00
erio adc5fa3ee1 chore: bump version to 0.1.110.12 2026-04-12 18:12:13 +08:00
erio 3ac935f047 fix(websearch): improve settings UI and hide config when globally disabled
- API Key show/copy buttons moved inside input field (inline icons)
- Proxy selector and test button on same row to save vertical space
- Test opens a dialog modal instead of inline display
- Hide all websearch config in channels/accounts when global toggle is off
2026-04-12 15:59:45 +08:00
erio a3da2465b7 chore: bump version to 0.1.110.10 2026-04-12 15:01:26 +08:00
erio 08235a462c chore: bump version to 0.1.110.9 2026-04-12 14:48:26 +08:00
erio 8fe7110d7f fix: address audit findings for websearch and balance notification
- Fix GetByKeyForAuth not selecting balance notify fields (notifications
  never triggered in gateway path)
- Fix provider-level ProxyURL never resolved: inject ProxyRepository into
  SettingService, resolve proxy URLs when building Manager
- Fix admin manual balance adjustment not updating total_recharged
- Add threshold_type input validation (reject invalid values)
- Fix user threshold_type inheritance: custom threshold defaults to "fixed"
  instead of inheriting global type (prevents $5 being treated as 5%)
- Add try-catch for clipboard.writeText (fails on non-HTTPS)
- Add SetTotalRecharged to user Update for admin balance operations
2026-04-12 14:43:12 +08:00
erio f059ad02be chore: bump version to 0.1.110.8 2026-04-12 13:18:30 +08:00
erio 9d3376cbdb Merge branch 'worktree-feature+balance_notify' into release/custom-0.1.110
# Conflicts:
#	backend/internal/handler/admin/setting_handler.go
#	backend/internal/service/domain_constants.go
#	backend/internal/service/setting_service.go
#	backend/internal/service/settings_view.go
2026-04-12 13:17:28 +08:00
erio 8f93b69584 feat(notify): add balance low & account quota notification system
- User balance low notification: email alert when balance drops below
  configurable threshold (user email + verified extra emails)
- Account quota notification: broadcast email to admin-configured
  recipients when daily/weekly/total quota usage exceeds alert threshold
- Admin settings: global enable/disable, default threshold, quota
  notification email list (Email Settings tab)
- User profile: enable/disable, custom threshold, add/remove extra
  notification emails with verification code flow
- Account quota: per-dimension alert toggle and threshold in quota
  control card
- Trigger logic: first-crossing only (old >= threshold && new < threshold
  for balance; old < threshold && new >= threshold for quota), naturally
  prevents duplicate notifications without Redis dedup
2026-04-12 02:48:57 +08:00
erio 6f24322ee6 chore: bump version to 0.1.110.7 2026-04-12 02:27:19 +08:00
erio 6986546d06 chore: bump version to 0.1.110.6 2026-04-12 01:06:31 +08:00
erio 473746a09d chore: bump version to 0.1.110.5 2026-04-11 20:58:12 +08:00
erio 6c96aaec8c chore: bump version to 0.1.110.4 2026-04-11 19:03:34 +08:00
erio 989c5faad5 Merge remote-tracking branch 'upstream/main' into release/custom-0.1.110
# Conflicts:
#	.github/audit-exceptions.yml
#	backend/cmd/server/VERSION
#	backend/go.sum
#	backend/internal/handler/admin/setting_handler.go
#	backend/internal/handler/dto/settings.go
#	backend/internal/repository/channel_repo.go
#	backend/internal/service/channel_service.go
#	backend/internal/service/setting_service.go
#	backend/internal/service/settings_view.go
#	frontend/src/api/admin/settings.ts
#	frontend/src/stores/app.ts
#	frontend/src/types/index.ts
#	frontend/src/views/admin/SettingsView.vue
2026-04-11 18:41:54 +08:00
erio f18d4ebbba chore: remove all sora dead code and fork-specific sora_client_enabled
Upstream removed sora feature (090_drop_sora.sql) but left i18n keys
and wire.go references. Clean up:
- Remove entire sora i18n block from en.ts and zh.ts (~190 lines)
- Remove sora nav key and unused 'data' settings tab key
- Remove sora_client_enabled from settings (fork-specific)
- Remove SoraMediaCleanupService from wire.go
2026-04-11 18:13:38 +08:00
erio a5a5d93de7 chore: bump version to 0.1.110.3 2026-04-11 16:10:09 +08:00
erio 017f23bfa3 chore: bump version to 0.1.110.2 2026-04-11 15:46:43 +08:00
erio b3d4ea5aa6 chore: bump version to 0.1.110.1 2026-04-11 14:49:36 +08:00
erio 4a3383ecdd fix(payment): widen popup window to 1000px for Alipay+ checkout
Alipay+ checkout page needs ~960px to display QR code and order details
side by side. Previous 680px forced users to manually resize.
2026-04-10 16:45:47 +08:00
erio 9d4b02688f Revert "fix(payment): restore decodeURLValue in EasyPay webhook verification"
This reverts commit 1430c82078.
2026-04-10 16:43:21 +08:00
erio 1430c82078 fix(payment): restore decodeURLValue in EasyPay webhook verification
The db139191 audit incorrectly removed the second URL decode pass.
EasyPay providers send values that need decoding after url.ParseQuery,
causing signature verification to fail on .147+.
Production .146 worked because it still had decodeURLValue.
2026-04-10 16:40:42 +08:00
erio 7547ea9f4e fix(payment): include RECHARGING in result page success states
The PaymentResultView only checked COMPLETED and PAID status, but orders
in RECHARGING state (balance being applied after payment) were incorrectly
shown as failed.
2026-04-10 16:23:23 +08:00
erio 0113bf60fd feat(payment): renewal modal, clean confirm card, platform color refresh
- Replace gradient header in subscription confirm with clean card layout
  matching sub2apipay design (platform accent text instead of full gradient)
- Add renewal plan selection modal: when group has multiple plans show
  picker, single plan skips directly to payment method selection
- Restyle SubscriptionsView with platform-specific colors (badge, border,
  button) instead of hardcoded purple
- Update platformColors to match sub2apipay style (transparent badges,
  subtle borders with /20 opacity)
2026-04-10 16:12:28 +08:00
erio 9df96a45c7 feat(payment): redesign Stripe popup to match sub2apipay clean card style
Replace purple gradient header with clean white card layout, method-specific
colored amount text and spinner, larger card and spinner sizes.
2026-04-10 15:50:28 +08:00
erio 0f32416d5d chore: bump version to 0.1.108.148 2026-04-10 14:04:19 +08:00
erio a92c0eabbc chore: bump version to 0.1.108.147 2026-04-10 10:42:30 +08:00
erio 72b77306da fix(admin): reload provider list after save to get correct data format
UpdateProvider API returns raw DB entity (encrypted config, string types),
but frontend needs ProviderInstanceResponse (decrypted, array types).
Reload full list after save to ensure correct data and sort order.
2026-04-10 01:28:54 +08:00
erio e45c774ab9 fix(payment): refresh provider registry on config changes
- Call RefreshProviders after Create/Update/Delete provider instance
- Call RefreshProviders after saving payment settings
- Auto-save settings when provider dialog saves
- Fixes webhook signature verification using stale pkey
2026-04-10 00:50:12 +08:00
erio 62e9453ddc refactor(frontend): extract shared OrderTable component
- Create OrderTable.vue with shared cell rendering (ID, order number,
  amount, payment method, status badge, created time)
- Accepts showUser prop to conditionally show user_email column
- Actions column uses scoped slot for view-specific buttons
- UserOrdersView and AdminOrdersView both use OrderTable
- Removes duplicated DataTable cell templates from both views

chore: bump version to 0.1.108.144
2026-04-09 23:49:47 +08:00
erio 753a87dee3 feat(admin): add user info and keyword search to admin order list
Backend:
- Add Keyword field to OrderListParams
- AdminListOrders supports keyword search on out_trade_no, user_email, user_name
- PaymentOrder already has user_email/user_name/user_notes fields (no join needed)

Frontend:
- Replace inline status badge with OrderStatusBadge component
- Replace user_id column with user_email (shows email, fallback to username, with notes)
- Keyword search matches order number and user info

chore: bump version to 0.1.108.143
2026-04-09 23:41:59 +08:00
erio e9aa1b1330 fix(payment): use order's original provider instance for refund
Previously gwRefund used registry.GetProvider(paymentType) which returns
an arbitrary instance for that type. When multiple instances share the
same payment type (e.g., two EasyPay merchants both supporting alipay),
the refund would be sent to the wrong merchant.

Now getRefundProvider() reads the order's ProviderInstanceID, loads that
instance's config, and creates the correct provider. Falls back to
registry lookup for legacy orders without an instance ID.

chore: bump version to 0.1.108.142
2026-04-09 23:21:38 +08:00
erio cc858c3cc9 feat(admin): enhance refund workflow to match sub2apipay
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
2026-04-09 21:47:39 +08:00
erio ceee2d0584 fix: audit fixes - magic strings to constants, frontend any/catch, LB tests
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
2026-04-09 21:29:49 +08:00