Commit Graph
865 Commits
Author SHA1 Message Date
erio c1d95da06a fix(ci): add missing signaturePool arg + gofmt alignment 2026-04-19 19:28:10 +08:00
erio 2df77c1604 feat(signature): add SignaturePool infrastructure (not yet wired)
Introduces the building blocks for the thinking-signature pool feature
without activating any runtime behavior. Nothing uses these new types
yet — Phase 3 will wire them into the retry loops.

New package internal/service/signature adds:
  - SignaturePool interface + Bucket helpers (oauth shared / apikey per-account)
  - ReplaceThinkingSignaturesInBody / ReplaceThinkingSignaturesInClaudeRequest
    pure functions that cycle through pool entries for M>N replacements
  - Harvester io.ReadCloser decorator for SSE + non-streaming JSON that
    extracts content_block.signature fields best-effort into the pool
  - 1h soft TTL constant for lazy expiry

New repository adapter internal/repository/signature_pool_cache.go
implements Redis ZSET storage with a single Lua script handling atomic
add + lazy expiry cleanup + capacity trim. Registered via
ProvideSignaturePool in the wire ProviderSet.

Settings extension: RectifierSettings gains a SignaturePoolSize int
field (0 = pool disabled / sticks with strip behavior; >0 = pool replace
is active). Threaded through service view, DTO, and handler GET/PUT
paths with bounds validation (max 1000).

ctxkey.IsSignatureRectifyRetry added so the harvester can later skip
ingesting signatures from retry requests we ourselves injected.
2026-04-19 12:43:11 +08:00
erio 5877cc9e6f fix(payment): frontend declares is_mobile in create-order request
Backend's UA heuristic (mobile|android|iphone|ipad|ipod) misidentifies
iPadOS 13+ (reports as Mac) and certain embedded browsers that strip
the "Mobile" keyword, so H5 users got a PC alipay.trade.page.pay URL.
Frontend then tried to window.location.href into it — navigation went
somewhere unexpected or the popup fallback left the user staring at
the paying-state frontend.

Let the frontend — which has navigator.userAgentData.mobile and better
context — declare is_mobile on the create-order request. Backend uses
the explicit value when present, falling back to UA detection only
when the client didn't send one (preserves old clients / direct API
callers).
2026-04-18 23:46:17 +08:00
erio cfd9566905 feat(tls-fingerprint): show binding count + fix randomized fingerprint visibility (v0.1.114.2)
## Bug fixes
- EditAccountModal: auto-generated profiles (__auto__:acc-*) are no longer
  hidden from the dropdown; accounts bound to their own auto profile can
  now see and keep the selection.
- AccountResponse DTO: emit tls_fingerprint_randomized so the "randomized"
  badge and reshuffle affordance render on subsequent edits.

## Feature
- TLS fingerprint profile list returns bound_account_count per profile,
  aggregated via a single grouped SQL query over accounts.extra.
- Dropdowns and admin management table surface the binding count so admins
  can judge whether a fingerprint is shared before editing or deleting it.

## Performance
- Migration 108 adds a partial + expression index on
  (extra->>'tls_fingerprint_profile_id') WHERE extra ? '...',
  enabling Index Only Scan + HashAggregate for the new query.
- Extraction uses (extra->>'...')::bigint so PostgreSQL performs the cast;
  Go-side parsing and NullString plumbing are removed.

## Backend
- AccountRepository: add CountByTLSFingerprintProfile; implement in ent
  repo via grouped raw SQL.
- TLSFingerprintProfileService: add ProfileWithBinding + ListWithBindingCount.
- Admin handler List now returns the enriched payload.
- AccountResponse DTO: add tls_fingerprint_randomized (optional, omitempty).
- Update all five AccountRepository test stubs for the new interface method
  (account_service_delete_test, gateway_multiplatform_test,
  gemini_multiplatform_test, ratelimit_session_window_test,
  server/api_contract_test).

## Frontend
- TLSFingerprintProfile type: add optional bound_account_count.
- EditAccountModal + CreateAccountModal: show " (N)" suffix on options
  with active bindings; drop the auto-profile filter.
- TLSFingerprintProfilesModal admin table: new "使用中 / In use" column
  with amber-highlighted count.
- i18n zh/en: add columns.boundAccounts.

## Compatibility
- New fields are all optional (omitempty) — existing OAuth accounts and
  older frontends behave as before.
- No data migration required; empty extra entries are ignored by index
  and aggregation alike.
2026-04-18 21:45:17 +08:00
erio 6d0e056244 feat(fingerprint): Claude Code CLI fingerprint mimicry suite (v0.1.114.1)
Squash-merge feat/fingerprint-mimic into release/custom-0.1.114.

Functional changes:
- TLS ClientHello mimicry: 17→52 cipher suites, ALPN http/1.1 only,
  X25519MLKEM768, JA3/JA4 aligned with official CLI baseline
- HTTP layer: User-Agent 2.1.112 sdk-cli, 3 new anthropic-beta tokens,
  Stainless headers synced
- Sidecar traffic: usage poll + count_tokens injection + startup probe
  (three fire-and-forget channels with jittered intervals)
- Sticky session UUID: per-account metadata.user_id reuse via Redis,
  WithSessionHash propagation through gateway
- 429 no-switch: short-circuit on rate-limit instead of cross-account fail-over
- Admin UI: per-account "randomize fingerprint" button + ConfirmDialog +
  i18n (zh/en, 9 keys)

Engineering hardening (fixes carried over from hai/snapshot):
- safe.Go/Run package: panic-recovering goroutine helper with slog
- redis.Nil treated as cache miss in identity cache
- SOCKS5 dialer uses ContextDialer for ctx cancellation propagation
- Token refresh selects on stopCh during retry backoff
- Sidecar goroutines wrapped with safe.Go
- .gitattributes forces LF for *.json (fixes baseline file CRLF drift)

Refactoring (zero behavior change):
- sidecar probe constants centralized
- identity_service rewrite preflight extracted into helpers
- capture_fingerprint tool split from 894 lines into 9 files
- gofmt sweep on all hai-imported files

Tests:
- safe package: panic recovery + attrs propagation
- tlsfingerprint: SOCKS5 dialer + capture parity
- identity sticky-session preflight paths (early-return + cache-hit)
- sidecar probe: jittered interval + dry-run
- token refresh: graceful shutdown during backoff

Beta-validated on 0.1.112.21 (commit 565da163 of feat/fingerprint-mimic).
Bumps VERSION 0.1.112.20 -> 0.1.114.1.
2026-04-18 16:07:36 +08:00
erio 999f4e8cb1 fix: gofmt formatting and update API contract test for new payment fields 2026-04-15 01:40:41 +08:00
erio 8f892a6a7b feat(payment): add recharge fee rate setting and fix provider card UI
- Add recharge_fee_rate system setting (percentage fee on top of recharge amount)
- Full backend chain: config constant, PaymentConfig struct, update validation,
  read/write persistence, DTO, handler GET/PUT responses
- Frontend: settings input with preview, i18n (zh/en), API types
- Fix provider card toggle layout: labels above switches to save width
- Fix Chinese translation: "EasyPay" → "易支付" in provider description
2026-04-15 00:41:33 +08:00
erio 7288503a45 feat(payment): balance recharge multiplier and refund amount separation
- Add balance_recharge_multiplier system setting (e.g. 1.2 = charge 100 get 120)
- Separate order_amount (credited balance) from pay_amount (actual payment)
- Refund calculates gateway amount proportionally from pay_amount
- Frontend shows both amounts in order details, payment status, refund dialog
- Admin settings UI for configuring recharge multiplier
2026-04-15 00:16:07 +08:00
erio 9f9c3a9384 feat: websearch quota enhancements and balance notify hint
- QuotaLimit changed to *int64 (null=unlimited, >0=limited)
- Add reset-usage endpoint (POST /admin/settings/web-search-emulation/reset-usage)
- Show quota usage in header always (collapsed and expanded)
- Add reset quota button in expanded provider view
- Quota input: empty=unlimited with ∞ placeholder, must be >0 if set
- Add email verification hint on balance notify card
2026-04-14 08:07:29 +08:00
erio 63ae7365d6 fix: gofmt formatting across all Go source files 2026-04-14 07:43:08 +08:00
erio 9a820a1749 fix: show websearch API key visibility/copy buttons for saved providers
The buttons were hidden because v-if only checked provider.api_key,
which is always empty for saved providers (backend sanitizes it).
Now also checks api_key_configured. Copy button is disabled when
no actual key is available (only configured placeholder shown).
2026-04-14 07:35:24 +08:00
erio a232407486 fix: resolve remaining test failures after v0.1.112 merge
- Add missing balanceNotifyService param to NewGatewayService in warmup test
- Fix config_test YAML path escaping on Windows (use filepath.ToSlash)
- Update pricingRequestToService test: empty platform no longer defaults
2026-04-14 06:56:58 +08:00
erio 541dd3b467 fix: audit round-3 — proxy safety, intervals persistence, SMTP timeout, sort fix
- Skip websearch provider when ProxyID is set but proxy not found (prevent
  silent direct connection bypass)
- Fix sortByStableRandomWeight: pair factors with items so sort.Slice swap
  keeps weights aligned
- Allow empty platform in account_stats_pricing_rules (wildcard matching),
  only force anthropic default for main model_pricing
- Add channel_account_stats_pricing_intervals table and repo layer support
  for interval-based pricing in account stats rules
- calculateTokenStatsCost now uses interval pricing when available
- Replace smtp.SendMail/tls.Dial with net.Dialer timeout (10s dial, 20s IO)
  to prevent goroutine leak on SMTP hang
- Fix gofmt formatting issues
- Web Search label: black text with red warning hint
2026-04-14 01:10:46 +08:00
erio e5e26d73d6 fix: round-2 audit fixes — security, code quality, and UI improvements
Security (HIGH):
- Normalize all Redis cache keys to lowercase (verifyCode, passwordReset)
- Fix verify code TTL renewal on failed attempts: use remaining TTL via
  ExpiresAt field instead of resetting to full 15-minute window
- Add 3 missing fields to diffSettings audit log (promo_code, invitation_code,
  custom_endpoints)

Code quality (MEDIUM):
- Extract filterVerifiedEmails shared helper (balance_notify_service.go)
- Add Pricing array non-empty validation for channel pricing rules
- Add platform token semantics comment in gateway_service.go
- Complete validatePlanPatch test coverage (+10 test cases)
- Replace string types with QuotaThresholdType/QuotaResetMode across frontend
- Remove duplicate getPlatformTextColor/getRateBadgeClass in ChannelsView
- Return EMAIL_NOT_FOUND error on RemoveNotifyEmail miss

UI improvements:
- Reorder cost tooltip: user billing above separator, account billing below
- Add NaN guard to accountBilled function
- Move timezone selector inline into reset-mode row (no longer standalone)
2026-04-14 00:26:20 +08:00
erio 931fcb50f8 fix: address audit findings for websearch, email verification, and pricing
- Fix websearch provider failover: proxy error from provider-specific proxy
  now continues to next provider instead of aborting the entire loop
- Fix SMTP failure locking users out: send email first, then write cache
  and increment rate counter
- Fix notify email cache key case sensitivity: normalize to lowercase
- Add OriginalPrice validation to validatePlanPatch and validatePlanRequired
- Add empty scope validation for channel pricing rules (group_ids/account_ids)
- Add platform color to account search dropdown in channel pricing rules
2026-04-13 23:35:59 +08:00
erio 939902f998 fix: batch 2 audit fixes — diffSettings notify fields, slog migration, frontend constants
H5: diffSettings now tracks 5 balance/quota notify fields in audit log
M15: log.Printf audit log migrated to slog.Info, removed "log" import
M14: New frontend/src/constants/account.ts with shared constants
     QuotaNotifyToggle.vue uses QUOTA_THRESHOLD_TYPE_FIXED/PERCENTAGE
L2: UsageTable.vue uses BILLING_MODE_TOKEN/IMAGE from billingMode.ts
2026-04-13 21:54:01 +08:00
erio 563339dedb fix: batch 1 audit fixes — quota SQL fixed mode, public recharge URL, WebSearch bool fallback, UpdatePlan validation
H1: incrementUsageBillingAccountQuota now uses shared dailyExpiredExpr/weeklyExpiredExpr
    constants (supporting fixed reset mode) instead of hardcoded '24 hours'/'168 hours'
H4: public settings endpoint now maps balance_low_notify_recharge_url
H6: GetWebSearchEmulationMode tolerates legacy bool values (true→enabled)
H7: UpdatePlan validates non-nil patch fields (rejects negative price, empty name, etc.)
H8: UsageTable accountBilled() helper with total_cost ?? 0 null guard
H9: AdminUsageLog TS type adds channel_id + billing_tier
M2: account.go "fixed" literals replaced with thresholdTypeFixed constant
M13: SystemSettings TS type adds web_search_emulation_enabled
UI: QuotaLimitCard title labels now use flex-1 to align with flex-1 input boxes
2026-04-13 21:41:02 +08:00
erio b0305fef11 fix: audit findings - PUT response rechargeURL, NaN guard, debug logs
- Add BalanceLowNotifyRechargeURL to admin PUT response (fixes save-then-stale)
- Add ?? 1 guard for account_rate_multiplier in UsageTable else branch
- Downgrade high-frequency notify logs from Info to Debug
- Extract "Sub2API" magic string to defaultSiteName constant
2026-04-13 19:45:45 +08:00
erio 6307aff1d3 fix(notify): add recharge URL to admin settings GET response 2026-04-13 19:02:40 +08:00
erio 6c2c17d17e feat(notify): add platform/ID to quota alert email, add recharge URL to balance alert
- Quota alert email now shows account ID and platform
- Balance low email includes a "Top Up Now" button when recharge URL is configured
- New setting: balance_low_notify_recharge_url in admin settings
2026-04-13 18:39:45 +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 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 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 ac4876646b feat(notify): convert email lists to NotifyEmailEntry struct with toggle support
- Change balance_notify_extra_emails and account_quota_notify_emails
  from []string to []NotifyEmailEntry{email, disabled, verified}
- Add per-email enable/disable toggle for both user and admin notifications
- Add PUT /user/notify-email/toggle API endpoint
- Fix critical bug: API key auth cache snapshot missing balance notify
  fields (Email, Username, BalanceNotifyEnabled, etc.), causing
  notifications to never fire on cached request paths
- Bump cache snapshot version 3→4 to invalidate stale entries
- Add SQL migration 104 to convert old format data
- Backward compatible: parseNotifyEmails auto-detects old/new format
- User balance notify: max 3 emails (primary + 2 extra)
- Admin quota notify: unlimited emails, each with toggle
2026-04-13 00:52:42 +08:00
erio db264c4a12 feat(payment): add per-provider allow_user_refund control
- Add allow_user_refund field to payment_provider_instances (migration 103)
- Backend: validateRefundRequest checks allow_user_refund for user requests
- Backend: PrepareRefund checks refund_enabled for admin refunds
- Legacy orders (no provider_instance_id) default to blocking refund
- Cascade: disabling refund_enabled auto-disables allow_user_refund
- Frontend: ProviderCard/Dialog show allow_user_refund toggle when refund_enabled
- Frontend: UserOrdersView checks eligible providers before showing refund button
- New API: GET /payment/orders/refund-eligible-providers
2026-04-12 21:38:34 +08:00
erio 456df20d45 feat(notify): improve balance notify card UX
- Show system default threshold as placeholder in custom threshold input
- Display user's primary email with "Primary" badge
- Support adding multiple pending emails before verification
- Each pending email has independent send/verify/resend flow
- Expose balance_low_notify_threshold in PublicSettings API
- Clean up timers on unmount to prevent leaks
2026-04-12 20:29:26 +08:00
erio 8a629e1085 fix(notify): add balance/quota notify flags to PublicSettings DTO and handler
The service layer correctly populated BalanceLowNotifyEnabled and
AccountQuotaNotifyEnabled in PublicSettings, but the handler-to-DTO
mapping was missing. Users could not see the balance notify card because
the public settings API never returned these flags.
2026-04-12 20:10:24 +08:00
erio 877e681afd fix(notify): remove percentage threshold from balance notification
Balance low notification only supports fixed USD amount threshold.
Percentage threshold is a quota concept, not applicable to balance.
Reverted threshold_type from admin settings, user profile, and all
backend/frontend layers. DB fields (balance_notify_threshold_type,
total_recharged) retained for potential future quota use.
2026-04-12 15:01:10 +08:00
erio 72c836141e feat(notify): add percentage threshold type for balance low notification
- Add threshold_type field (fixed/percentage) to system and user settings
- Add total_recharged field to users table, auto-incremented on balance credit
- Percentage mode: effective threshold = total_recharged × percentage / 100
- User-level threshold_type inherits from system default when not set
- Update admin settings UI with radio selector (fixed amount / percentage)
- Migration: 102_add_balance_notify_threshold_type.sql
2026-04-12 13:53:02 +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 21e6d68925 feat(websearch): settings UI overhaul and quota improvements
- Remove Priority field, auto load-balance by quota remaining
- Replace QuotaRefreshInterval (daily/weekly/monthly) with SubscribedAt
  (subscription date, monthly lazy refresh via Redis TTL)
- Add collapsible provider cards, API key show/copy, usage progress bar
- Add test endpoint (POST /web-search-emulation/test) bypassing quota
- Wire WebSearchManagerBuilder on startup (was never called before)
- Fix nextMonthlyReset day-of-month overflow (Jan 31 → Feb 28)
- Fix non-deterministic sort in selectByQuotaWeight
- Map ProxyID in builder for provider-level proxy tracking
- Fix frontend timezone drift in subscribed_at date picker
- Fix provider deletion index shift for expandedProviders state
2026-04-12 13:11:46 +08:00
erio 48488652f2 fix(notify): per-recipient timeout and return user on email removal
- Use per-recipient context timeout in sendEmails to prevent later
  recipients from failing due to shared timeout exhaustion
- Return updated user object from RemoveNotifyEmail handler for
  frontend state consistency (matching VerifyNotifyEmail pattern)
2026-04-12 12:50:27 +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 0622fc71ee merge: integrate websearch emulation feature from worktree branch
Merge worktree-feature+websearch_support into release/custom-0.1.110.
Resolved conflicts in channel repo/service/handler/frontend (both
features_config and account_stats_pricing fields kept).
Fixed ProxySelector missing proxies prop in SettingsView.
2026-04-12 01:00:14 +08:00
erio 4d9d48fade feat(channels): add custom account stats pricing rules
Allow channels to configure independent model pricing for account
statistics cost calculation, decoupled from user billing.

Backend:
- Migration 101: channels.apply_pricing_to_account_stats toggle,
  channel_account_stats_pricing_rules/model_pricing tables,
  usage_logs.account_stats_cost column
- resolveAccountStatsCost: match rules by group/account, then channel
  pricing, fallback to original formula when unconfigured
- Integrate into both GatewayService.recordUsageCore and
  OpenAIGatewayService.RecordUsage
- Update 8 account stats SQL queries to use
  COALESCE(account_stats_cost, total_cost) * account_rate_multiplier
- 23 unit tests for matching, pricing lookup, and cost calculation

Frontend:
- Channel edit dialog: toggle + custom rules UI with group/account
  multi-select and pricing entry cards
- API types and i18n (zh/en)
2026-04-12 00:31:57 +08:00
erio c2deb0939d feat(gateway): add web search emulation for Anthropic API Key accounts
Inject web search capability for Claude Console (API Key) accounts that
don't natively support Anthropic's web_search tool. When a pure
web_search request is detected, the gateway calls Brave Search or Tavily
API directly and constructs an Anthropic-protocol-compliant SSE/JSON
response without forwarding to upstream.

Backend:
- New `pkg/websearch/` SDK: Brave and Tavily provider implementations
  with io.LimitReader, proxy support, and Redis-based quota tracking
  (Lua atomic INCR + TTL, DECR rollback on failure)
- Global config via `settings.web_search_emulation_config` (JSON) with
  in-process cache + singleflight, input validation, API key merge on
  save, and sanitized API responses
- Channel-level toggle via `channels.features_config` JSONB column
  (DB migration 101)
- Account-level toggle via `accounts.extra.web_search_emulation`
- Request interception in `Forward()` with SSE streaming response
  construction using json.Marshal (no manual string concatenation)
- Manager hot-reload: `RebuildWebSearchManager()` called on config save
  and startup via `SetWebSearchRedisClient()`
- 70 unit tests covering providers, manager, config validation,
  sanitization, tool detection, query extraction, and response building

Frontend:
- Settings → Gateway tab: Web Search Emulation config card with global
  toggle, provider list (add/remove, API key, priority, quota, proxy)
- Channels → Anthropic tab: web search emulation toggle with global
  state linkage (disabled when global off)
- Account Create/Edit modals: web search emulation toggle for API Key
  type with Toggle component
- Full i18n coverage (zh + en)
2026-04-12 00:02:26 +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 d60015f1d1 revert(payment): remove active upstream sync, keep webhook-only approach
Reverts the sync polling mechanism that queried upstream providers on
each frontend poll. Keep the Stripe expiresAt countdown fix.
2026-04-11 16:10:09 +08:00
erio 576c34bbf7 fix(payment): add active upstream sync to polling and fix Stripe countdown
- Add POST /payment/orders/:id/sync endpoint that queries upstream
  provider on each poll, complementing webhooks for timely payment
  detection when webhooks are delayed or unreachable
- Fix Stripe payment countdown: pass expires_at to PaymentStatusPanel
  instead of empty string (was falling back to hardcoded 30 minutes)
- Idempotent: toPaid uses atomic UPDATE WHERE status=PENDING, so
  concurrent webhook + sync calls won't double-credit
2026-04-11 15:46:28 +08:00
erio b5b5e35757 refactor(payment): code standards fixes and file organization
Cherry-picked from PR branch (feat/payment-system-v2).
- Use payment.Type* constants instead of magic strings in factory
- Add wxpay success response constants
- Add validity unit constants (week/month)
- Add constants for easypay popup mode
- Split payment_order.go into payment_order_lifecycle.go
- Extract shared order utilities to orderUtils.ts
- Improve admin order components to use shared utilities
- Removed duplicate migration (097 already exists)
2026-04-11 13:24:17 +08:00
erio f4e4b1539b feat(payment): add H5/mobile payment support
Cherry-picked from PR branch (feat/payment-system-v2).
- EasyPay: parse payurl2 for H5 mobile links, prefer on mobile
- EasyPay: add device=mobile for popup mode on mobile
- Backend: expand isMobile() to detect iPad/iPod
- Frontend: auto-redirect on mobile instead of popup
- Frontend: fallback to redirect when popup blocked
- Stripe: use mobile_web client for WeChat Pay on mobile
- StripePopup: typed interface, extractApiErrorMessage
2026-04-11 13:22:12 +08:00
erio 9b38e44e1d fix(payment): restore double URL decode for EasyPay webhook values
EasyPay callbacks arrive with double-encoded query values (e.g. %25E5 instead
of %E5) due to redirect chains. url.ParseQuery decodes once; decodeURLValue
applies a second safe decode so the sign matches what EasyPay computed.
Also removes temporary debug logging.
2026-04-10 14:27:44 +08:00
erio 72f7677815 debug: log webhook raw body and sign comparison 2026-04-10 14:17:39 +08:00
erio d642a16f79 fix(payment): webhook uses order's provider instance for signature verification
When multiple provider instances exist (e.g. 3 EasyPay accounts), the webhook
handler now extracts out_trade_no from the callback, looks up the order, and
uses the order's original provider instance for verification instead of picking
an arbitrary instance from the registry.
2026-04-10 14:03:59 +08:00
IanShaw027 1312405966 fix(settings): 补齐公开设置中的表格分页字段返回 2026-04-10 09:18:48 +08:00
erio db139191e3 fix(payment): critical audit fixes for security, idempotency and correctness
Backend fixes:
- #1: doSub subscription idempotency via audit log check
- #2: markFailed only when status=RECHARGING (prevents overwriting COMPLETED)
- #3: ExpireTimedOutOrders checks upstream payment before expiring
- #4: Public verify endpoint for payment result page (no auth required)
- #5: EasyPay QueryOrder returns amount, confirmPayment handles zero amount
- #6: WxPay notifyUrl priority: request-first, config-fallback
- #7: EasyPay remove double URL decode in VerifyNotification
- #8: checkPaid/cancelUpstreamPayment use order's provider instance
- #9: Amount NaN/Inf/negative validation in order creation and refund
- #10: Refund amount comparison uses tolerance instead of float64 ==
- #11: Skip balance deduction on retry when previous rollback failed
- #12: checkPaid logs fulfillment errors instead of silently ignoring
- #13: WxPay certSerial added to required config fields

Frontend fixes:
- Payment result page no longer requires authentication
- Public verify API fallback for expired sessions
2026-04-10 02:23:19 +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 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 69f885eb5f style: fix gofmt formatting in payment_handler.go 2026-04-09 22:52:11 +08:00