1. 20 Apr, 2026 1 commit
    • erio's avatar
      fix(openai): 移除已下线 Codex 模型并修复归一化兜底副作用 · bbc4aed3
      erio authored
      - backend: 删除 gpt-5 / 5.1 / 5.1-codex / 5.1-codex-max / 5.1-codex-mini / 5.2-codex / 5.4-nano 的内置映射与 DefaultModels 条目
      - backend: normalizeCodexModel 默认兜底由 gpt-5.1 改为 gpt-5.4,gpt-5.3-codex-spark 独立保留映射
      - backend: 修复 isOpenAIGPT54Model 与 shouldAutoInjectPromptCacheKeyForCompat 对 claude / gpt-4o 的误判(之前依赖 gpt-5.1 作为非 GPT 族的隐式 sentinel,改后需要显式前缀守卫)
      - backend: 清理 billing_service 中已不可达的 fallback 价格与 switch 分支
      - frontend: 从白名单、OpenCode 配置、预设映射中移除已下线模型
      - 同步更新所有相关单测
      
      Refs: #1758, parallels upstream #1759 but adds downstream guard fixes
      bbc4aed3
  2. 19 Apr, 2026 3 commits
  3. 18 Apr, 2026 2 commits
    • erio's avatar
      feat(payment): redact provider secrets in admin config API · 235f7108
      erio authored
      Admin GET /api/v1/admin/payment/providers previously returned every
      config value — including privateKey / apiV3Key / secretKey etc. —
      verbatim. Any future XSS on the admin UI would hand attackers the
      full set of production payment credentials, and the plaintext values
      sat unnecessarily in browser memory for every operator.
      
      Treat those fields as write-only from the admin surface:
      
      - decryptAndMaskConfig() strips sensitive keys from the GET response.
        The authoritative list is an explicit per-provider registry that
        mirrors the frontend's PROVIDER_CONFIG_FIELDS sensitive flag:
          alipay   → privateKey, publicKey, alipayPublicKey
          wxpay    → privateKey, apiV3Key, publicKey
          stripe   → secretKey, webhookSecret (publishableKey stays plain)
          easypay  → pkey
        Payment runtime still reads the full config via decryptConfig, so
        nothing at the gateway changes.
      
      - mergeConfig() treats an empty value for a sensitive key as "leave
        unchanged" — the admin UI omits unchanged secrets so operators can
        tweak non-sensitive settings without re-entering credentials.
      
      - Admin dialog (PaymentProviderDialog.vue):
        * secret inputs get autocomplete="new-password", data-1p-ignore,
          data-lpignore and data-bwignore so password managers do not
          offer to save provider credentials
        * in edit mode the required-field check skips sensitive fields
          (empty is the "keep existing" signal) and the placeholder shows
          "leave empty to keep" instead of the default example value
        * create mode still requires every non-optional field, including
          secrets, since there is nothing to preserve
      
      - Unit test renamed to TestIsSensitiveProviderConfigField, covers
        the per-provider registry and specifically asserts that Stripe's
        publishableKey is NOT treated as a secret.
      235f7108
    • erio's avatar
      fix(payment): alipay redirect-only flow, H5 detection and popup sizing · c3cb0280
      erio authored
      The native Alipay provider previously tried to embed the payment page
      URL into a QR code on the client — the URL is not a scannable payload
      so the QR never worked. Merchants also hit a H5 detection mismatch
      whenever the backend UA sniffer missed iPadOS 13+ or embedded browsers,
      and the popup window was too small for Alipay's standard checkout
      layout (QR + account-login panel on the right), forcing the user to
      scroll horizontally and vertically.
      
      Changes:
      
      Backend
      - alipay.go: drop QR-on-URL path. Use redirect-only flow —
        alipay.trade.page.pay for PC (returns a gateway URL the browser
        opens in a new window) and alipay.trade.wap.pay for H5 (returns a
        URL the browser jumps to). Both flows produce pages on
        openapi.alipaydev.com / excashier.alipay.com; the client never
        renders a QR itself.
      - payment_handler.go: add optional is_mobile bool to
        CreateOrderRequest so the frontend can declare the device
        explicitly. Server still falls back to UA sniffing when absent.
      
      Frontend
      - types/payment.ts, PaymentView.vue: declare is_mobile in
        CreateOrderRequest and pass the computed isMobileDevice() value.
      - providerConfig.ts: replace the two fixed POPUP_WINDOW_FEATURES
        constants with getPaymentPopupFeatures(), which prefers 1250×900
        (Alipay's checkout footprint), clamps to window.screen.avail* and
        centers the popup so it never overflows on smaller laptops.
      - PaymentQRDialog.vue, PaymentStatusPanel.vue, StripePaymentInline.vue,
        PaymentView.vue: use the new helper at all popup call sites.
      c3cb0280
  4. 17 Apr, 2026 7 commits
    • erio's avatar
      chore(payment): mark legacy AES ciphertext fallback as deprecated · 61a008f7
      erio authored
      明文 JSON 已经是新写入的默认格式;保留 AES 密文读取仅为兼容迁移期间的旧
      记录,一旦所有部署通过管理后台重存过一次即可删除。标记为 deprecated 并加
      TODO,几个版本后统一清理掉:payment.Encrypt / payment.Decrypt、两处
      decryptConfig 的 AES 分支、PaymentConfigService.encryptionKey 和
      DefaultLoadBalancer.encryptionKey 字段。
      61a008f7
    • erio's avatar
      feat(gateway): raise upstream response read limit 8MB -> 128MB (configurable) · bf0bbe0b
      erio authored
      图片生成 API 返回的 base64 内联图响应经常超过 8MB 单次读取上限,被
      ReadUpstreamResponseBody 拦截成 502 upstream_error。
      
      单张 4K PNG base64 最坏约 67MB,多张候选图或 imageSize=4K 的 image_generation
      一次请求能轻松到 30MB+。把默认上限提到 128MB 能覆盖 2-3 张 4K 图,相对
      请求体上限 256MB 仍有缓冲;同时抽出 config.DefaultUpstreamResponseReadMaxBytes
      共享常量,viper 默认值和 service 层兜底共用,消除两处同步魔法数字。
      
      仍可通过 gateway.upstream_response_read_max_bytes 配置项覆盖。
      bf0bbe0b
    • erio's avatar
      fix(billing): reject rate_multiplier <= 0 on save; clamp negatives to 0 in compute · df57d277
      erio authored
      分组倍率和用户专属倍率在保存时没有校验,0 会触发计费层的 `<=0 → 1.0`
      防御条款,结果订阅/余额分组按标准价扣费;完全是沉默地绕过了业务规则。
      
      - 保存校验(admin_service):CreateGroup / UpdateGroup / BatchSetGroupRateMultipliers /
        UpdateUser.SyncUserGroupRates 全部要求 > 0
      - 计算层(billing_service):三处 `<=0 → 1.0` 改为 `<0 → 0`;负数按 0 结算,
        避免配置异常被静默按 1x 收费
      - 前端:分组倍率 / 用户专属倍率输入 min 统一到 0.001
      - 删除未使用的 IsFreeSubscription 方法
      
      测试:新增 billing_service_rate_multiplier_test.go 端到端验证;更新原有锁定
      旧 `<=0 → 1.0` 行为的测试。
      df57d277
    • erio's avatar
      fix(usage): subscription billing honours group rate multiplier · 44cdef79
      erio authored
      Subscription-mode billing was consuming quota at TotalCost (raw) instead of
      ActualCost (TotalCost * RateMultiplier), so per-group rate multipliers —
      including free subscriptions (multiplier = 0) — were silently ignored.
      Switch the three subscription cost writes in buildUsageBillingCommand,
      finalizePostUsageBilling, and the legacy postUsageBilling fallback to
      ActualCost, and add a table-driven test covering 2x / 0.5x / free multipliers
      plus a balance-mode regression check.
      44cdef79
    • erio's avatar
      fix(payment): store provider config as plaintext JSON with legacy ciphertext fallback · fd0c9a13
      erio authored
      Without TOTP_ENCRYPTION_KEY, saved payment configs were lost on restart because
      the AES round-trip failed silently. Write new records as plaintext JSON; read
      path tries JSON first, falls back to legacy AES decrypt when a key is present,
      and treats unreadable values as empty so admins can re-enter them via the UI.
      fd0c9a13
    • shaw's avatar
      5d586a9f
    • shaw's avatar
      feat: 支持opus-4.7 · a789c8c4
      shaw authored
      a789c8c4
  5. 16 Apr, 2026 3 commits
  6. 15 Apr, 2026 6 commits
    • erio's avatar
      refactor: extract ReadUpstreamResponseBody to deduplicate upstream response... · 10699eeb
      erio authored
      refactor: extract ReadUpstreamResponseBody to deduplicate upstream response read + too-large error handling
      
      Consolidates 9 call sites of resolveUpstreamResponseReadLimit + readUpstreamResponseBodyLimited + ErrUpstreamResponseBodyTooLarge error handling into a single ReadUpstreamResponseBody function with TooLargeWriter callback for API-format-specific error responses (Anthropic, OpenAI, countTokens).
      10699eeb
    • fjl5's avatar
      6c89d8d3
    • erio's avatar
      feat(usage): add account cost to breakdown sub-table and admin usage log · db27e8f0
      erio authored
      - UserBreakdownItem: add AccountCost field + SQL aggregation
      - UserBreakdownSubTable: add orange account cost column
      - Admin usage table: add account_cost column (after cost, default visible)
      - Column settings: add account_cost toggle option
      db27e8f0
    • Wesley Liddick's avatar
    • erio's avatar
      test(usage): add unit tests for account_cost and fix gofmt · 22680dc6
      erio authored
      - Fix mock for GetModelStatsWithFilters: add account_cost column
      - Add assertion: GetStatsWithFilters always returns TotalAccountCost
      - New test: GetModelStatsAccountCostColumn verifies scan of AccountCost
      - New test: GetGroupStatsAccountCostColumn verifies scan of AccountCost
      - New test: GetStatsWithFiltersAlwaysReturnsAccountCost (no AccountID filter)
      - Integration test: add TotalAccountCost/TodayAccountCost assertions
      - Fix gofmt alignment in usage_log_types.go
      22680dc6
    • erio's avatar
      feat(usage): add account cost display to admin dashboard and usage pages · 6ade6d30
      erio authored
      - Add account_cost column to dashboard aggregation tables (migration 107)
      - DashboardStats: add TotalAccountCost/TodayAccountCost fields
      - ModelStat/GroupStat: add AccountCost field with SQL aggregation
      - GetStatsWithFilters: always return TotalAccountCost (remove accountID filter)
      - Dashboard Token cards: show user(green)/cost(orange)/standard(gray)
      - Usage stats card: show account cost and standard below main value
      - Model/Group distribution tables: add orange cost column
      6ade6d30
  7. 14 Apr, 2026 18 commits
    • erio's avatar
      fix: gofmt payment_service.go and payment_order.go · c2108421
      erio authored
      c2108421
    • erio's avatar
      fix(payment): use original recharge amount in product name, not pay_amount · 342dbd2e
      erio authored
      Product name (e.g. "快代码科技工作室 100 元") should show the user's
      original recharge amount (limitAmount), not the fee-inclusive pay amount.
      The gateway receives payAmount separately for actual charging.
      342dbd2e
    • erio's avatar
      fix: gofmt formatting and update API contract test for new fields · 60614e6f
      erio authored
      - Fix gofmt alignment in setting_handler.go, settings.go, payment_config_service.go
      - Add payment_balance_recharge_multiplier and payment_recharge_fee_rate
        to API contract test expected JSON
      60614e6f
    • erio's avatar
      fix(payment): enhance fee rate input validation and UI · d149dbc9
      erio authored
      Backend:
      - Validate recharge_fee_rate: 0 ≤ rate ≤ 100, max 2 decimal places
      
      Frontend settings:
      - Add % suffix icon to fee rate input
      - Enforce max=100, min=0, step=0.01 with 2 decimal precision
      d149dbc9
    • erio's avatar
      fix(payment): integrate recharge fee rate in order flow and fix UI display · e761d38f
      erio authored
      Backend:
      - Use cfg.RechargeFeeRate in order creation instead of hardcoded 0
      - Remove dead getFeeRate stub method
      - All amounts computed server-side: order_amount, pay_amount, fee_rate
      
      Frontend - PaymentView:
      - Read recharge_fee_rate from checkout-info API (not per-method)
      - Show fee breakdown only when fee_rate > 0
      - Show credited amount only when multiplier ≠ 1
      
      Frontend - Order display (user + admin):
      - Fix fee_rate * 100 bug (fee_rate is already a percentage)
      - OrderTable: show pay_amount as primary, fee/credited as sub-lines
      - AdminOrderDetail: full breakdown (base/fee/paid/credited)
      - AdminRefundDialog: label "到账金额" for clarity
      - PaymentResultView: show pay_amount with fee info
      
      Types + i18n:
      - Add recharge_fee_rate to CheckoutInfoResponse
      - Add fee_rate to CreateOrderResult
      - Add translations: creditedAmount, fee, baseAmount, includedInPayAmount
      e761d38f
    • erio's avatar
      feat(payment): add recharge fee rate setting and fix provider card UI · 98140f6c
      erio authored
      - 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
      98140f6c
    • erio's avatar
      feat(payment): balance recharge multiplier and refund amount separation · 60a4b931
      erio authored
      - 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
      60a4b931
    • erio's avatar
      fix: Messages() routing refactor and subscription group test coverage · 8548a130
      erio authored
      - Refactor OpenAI Messages() routing: pre-compute dispatch model using
        resolveOpenAIMessagesDispatchMappedModel + NormalizeOpenAICompatRequestedModel
        instead of try-fail-retry pattern with gin context passing
      - Remove openai_messages_fallback_model context anti-pattern
      - Use effectiveMappedModel directly for forward default mapped model
      - Add 3 subscription group tests covering all branch paths:
        _Blocked (no active subscription → SUBSCRIPTION_REQUIRED),
        _RequiresRepo (nil repo → SUBSCRIPTION_REPOSITORY_UNAVAILABLE),
        _AllowsActiveSubscription (valid subscription → success)
      8548a130
    • erio's avatar
      fix: flaky WebSocket test, usage request queue, and test improvements · 3fa5b8bc
      erio authored
      - Fix flaky WebSocket passthrough test: allow StatusNormalClosure after
        client close instead of requiring NoError (race condition fix)
      - Fix ratelimit 401 test: use PlatformOpenAI instead of PlatformGemini
        for OAuth token cache invalidation scenario (more accurate)
      - Add usageLoadQueue: Anthropic OAuth/setup-token accounts sharing the
        same proxy exit are serialized with 1-2s jitter to prevent upstream 429
      - AccountUsageCell: add module-level usage cache (5min TTL), unmounted
        safety guard, and integrate enqueueUsageRequest for throttled fetching
      3fa5b8bc
    • erio's avatar
      fix: merge general improvements from release branch · 63f539b3
      erio authored
      Backend:
      - gateway_handler: pass subject.UserID instead of int64(0) for user-level routing
      - setting_handler: add missing BalanceLowNotifyRechargeURL to UpdateSettings response
      - openai_gateway_service: use applyAccountStatsCost for account stats pricing integration
      - embed_on: add local file override (data/public/) for embedded frontend assets
      
      Frontend:
      - useTableSelection: add batchUpdate method for batch operations
      - AccountsView: virtual scrolling params, Set-based isSelected, swipe virtualization
      - ProxiesView: add batchUpdate to selection and swipe-select
      - BulkEditAccountModal: fix submit handler to prevent event object as argument
      - SettingsView: move payload construction outside try block
      - i18n: add general translation keys (saved, deleted, view, validation, allowUserRefund)
      - api/client: reorder error fields for consistency
      - stores/payment: clarify pollOrderStatus JSDoc
      63f539b3
    • erio's avatar
      fix: resolve 3 code review issues in allow_user_refund · c14d7393
      erio authored
      1. PrepareRefund: block refund on provider instance lookup failure
         instead of silently skipping permission check (medium severity)
      
      2. UpdateProviderInstance: allow enabling refund_enabled and
         allow_user_refund in the same request by checking req.RefundEnabled
         value before falling back to DB read
      
      3. ExecuteRefund: only revoke subscription on ErrAdjustWouldExpire,
         abort on other errors (DB failure, not found) instead of
         unconditionally revoking
      c14d7393
    • erio's avatar
      fix: merge 5 PR-related improvements · 58677dd5
      erio authored
      - gateway_handler: pass ParsedRequest to RecordUsage + set in gin.Context
      - channel_handler: add FeaturesConfig to CRUD (WebSearch channel toggle)
      - channel_repo: features_config JSONB persistence (Create/Get/Update/List)
      - security_headers: add Stripe CSP domains (script-src + frame-src)
      58677dd5
    • erio's avatar
      fix: merge 30 general improvements from release branch · 6ac8ccde
      erio authored
      Bug fixes:
      - Detached context for GetAccountConcurrencyBatch (prevent all-zero on request cancel)
      - Filter soft-deleted users in GetByGroupID
      - Stripe CSP policy (allow Stripe.js in script-src and frame-src)
      - WebSearch API key validation on save
      - RECHARGING status in payment result success check
      - Windows test fixes (logger Sync deadlock, config path escaping)
      
      Feature enhancements:
      - Webhook multi-instance dispatch (extractOutTradeNo + GetWebhookProvider)
      - EasyPay mobile H5 payment (device param + PayURL2)
      - SSE error propagation in WebSearch emulation
      - AccountStatsCost DTO field for admin usage logs
      - Plans sort by sort_order instead of created_at
      - UsageMapHook for streaming response usage data
      - apicompat Instructions field passthrough
      - EffectiveLoadFactor for ops concurrency/metrics
      - Usage billing RETURNING balance for notify system
      - BulkUpdate mixed channel warning with details
      - println to slog migration in auth cache
      - Wire ProviderSet cleanup
      - CI cache-dependency-path optimization
      
      Frontend:
      - Refund eligibility check per provider (canRequestRefund)
      - Plan sort_order editing
      - Dead code cleanup (simulate_claude_max, client_affinity)
      - GroupsView platform switch guard
      - channels features_config API type
      - UsageView account_stats_cost export
      6ac8ccde
    • erio's avatar
      feat: add per-provider allow_user_refund control and align wildcard matching · f1297a36
      erio authored
      allow_user_refund:
      - Add allow_user_refund field to PaymentProviderInstance ent schema
      - Migration 103: ALTER TABLE payment_provider_instances ADD COLUMN
      - Cascade logic: disabling refund_enabled auto-disables allow_user_refund
      - User refund validation: check provider instance allows user refund
      - Admin refund validation: check provider instance allows admin refund
      - Subscription refund: deduct days on refund, rollback on failure
      - New endpoint: GET /payment/orders/refund-eligible-providers
      - Frontend: ToggleSwitch in ProviderCard/Dialog, cascade in SettingsView
      
      Wildcard matching:
      - Change findPricingForModel from "longest prefix wins" to "config order
        priority (first match wins)", aligning with channel service behavior
      f1297a36
    • erio's avatar
      fix: resolve remaining lint errors for upstream CI · e8ee400a
      erio authored
      - Fix errcheck: brave.go resp.Body.Close, manager_test.go Encode
      - Fix gofmt: payment_config_service.go
      - Fix unused: use shouldFallbackGeminiModel (with modelName param) in handler
      e8ee400a
    • erio's avatar
      fix: resolve upstream CI failures (lint, test, gofmt) · 6a08efee
      erio authored
      - Fix errcheck: handle Write/Encode return values in brave_test.go
      - Fix errcheck: defer resp.Body.Close() with _ assignment in tavily.go
      - Fix gofmt: payment.go, channel.go, payment_config_providers.go
      - Fix unused: remove dead decodeURLValue in easypay.go
      - Restore shouldFallbackGeminiModel function (deleted during cherry-pick)
      - Add missing balanceNotifyService param to NewGatewayService in test
      - Fix platform default test expectation (empty stays empty)
      - Fix wildcard pricing test (longest prefix wins, not config order)
      - Fix subscription group test (SUBSCRIPTION_REPOSITORY_UNAVAILABLE)
      6a08efee
    • erio's avatar
      fix: Stripe payment type matching in load balancer · 4aa0070e
      erio authored
      Checkout page aggregates Stripe sub-types (card,link,alipay,wxpay) under
      "stripe", but SelectInstance matched against supported_types literally,
      which doesn't contain "stripe". Now matches by provider_key for Stripe.
      4aa0070e
    • erio's avatar
      fix: resolve test compilation errors and restore upstream VERSION · b42f34c3
      erio authored
      - Add missing interface methods to test stubs (RemoveGroupFromUserAllowedGroups,
        GetNotifyCodeUserRate, IncrNotifyCodeUserRate, UpdateGroupIDByUserAndGroup)
      - Fix NewUserService call signatures (add 4th param)
      - Fix GetAccountCount return signature (3 values)
      - Update api_contract_test.go snapshots for balance_notify fields
      - Restore resolveOpenAIMessagesDispatchMappedModel function
      - Reset VERSION to upstream 0.1.112
      b42f34c3