1. 20 Apr, 2026 2 commits
    • erio's avatar
      feat(payment): i18n payment error codes and label localization · 40d4e167
      erio authored
      Pairs with the backend structured payment errors (reason + metadata). The
      frontend now maps reason codes to localized messages with metadata as
      interpolation variables, and automatically localizes raw config-field names
      (e.g. "certSerial" → "证书序列号") using the existing UI-label i18n
      namespace.
      
      - frontend/src/utils/apiError.ts
        - extractApiErrorCode now prefers the string `reason` over the numeric HTTP
          `code`; reason is granular enough to drive i18n lookup, HTTP code is not.
        - New extractApiErrorMetadata to pull interpolation params off the error.
        - New extractI18nErrorMessage(err, t, namespace, fallback): looks up
          `<namespace>.<REASON>` in i18n and substitutes metadata. Before
          substitution, `metadata.key` and `metadata.keys` (slash-joined) are
          re-translated through `admin.settings.payment.field_<key>` so users see
          "缺少必填项:证书序列号" instead of "缺少必填项:certSerial".
      
      - frontend/src/i18n/locales/{zh,en}.ts
        - Add payment.errors entries for every structured reason code returned by
          the backend (PAYMENT_DISABLED, INVALID_AMOUNT, TOO_MANY_PENDING,
          DAILY_LIMIT_EXCEEDED, NO_AVAILABLE_INSTANCE, PAYMENT_PROVIDER_MISCONFIGURED,
          WXPAY_CONFIG_MISSING_KEY / INVALID_KEY_LENGTH / INVALID_KEY, NOT_FOUND,
          FORBIDDEN, CONFLICT, INVALID_ORDER_TYPE, INVALID_STATUS,
          BALANCE_NOT_ENOUGH, REFUND_AMOUNT_EXCEEDED, REFUND_FAILED, and more),
          with placeholders for template variables.
      
      - 13 payment-related Vue files
        - Migrate catch-block error reporting from extractApiErrorMessage to
          extractI18nErrorMessage(err, t, 'payment.errors', fallback).
        - Remove the ad-hoc paymentErrorMap computed in SettingsView.vue, which the
          new helper supersedes (it reads i18n directly via t).
      
      - frontend/src/components/payment/providerConfig.ts
        - wxpay: publicKey and publicKeyId are now required (was optional), matching
          the pubkey-only verifier direction; certSerial is already required.
      
      This PR is drop-in safe: reason-preferring extractApiErrorCode is backward
      compatible with callers that pass their own i18nMap, and error codes missing
      from i18n fall back to the existing message-based path.
      40d4e167
    • erio's avatar
      feat(payment): harden wxpay config validation with structured errors · 79192cf6
      erio authored
      Motivation: platform-certificate mode is being phased out by WeChat (2024-10+,
      newly-provisioned merchants already cannot download platform certificates at
      all), and wxpay config errors currently surface only when an order is being
      created — admins have no feedback at save time. Also, errors were returned as
      natural-language strings, leaving the frontend no way to localize them.
      
      Changes:
      
      - backend/internal/payment/provider/wxpay.go
        - Replace fmt.Errorf with structured infraerrors.BadRequest errors:
          - WXPAY_CONFIG_MISSING_KEY    (metadata: key)
          - WXPAY_CONFIG_INVALID_KEY_LENGTH  (metadata: key, expected, actual)
          - WXPAY_CONFIG_INVALID_KEY    (metadata: key) for malformed PEMs
        - Parse privateKey and publicKey PEMs in NewWxpay so malformed keys fail
          at save time instead of at order creation.
        - Keep the pubkey verifier (NewSHA256WithRSAPubkeyVerifier) as the single
          supported verifier; no more loadKeyPair helper.
      
      - backend/internal/service/payment_order.go invokeProvider
        - If CreateProvider or CreatePayment returns a structured ApplicationError,
          pass it through (optionally enriching metadata with provider/instance_id)
          instead of wrapping it as generic PAYMENT_GATEWAY_ERROR — so clients see
          the actual reason code (e.g. WXPAY_CONFIG_MISSING_KEY) and can localize.
        - Simplify a few messages (TOO_MANY_PENDING, DAILY_LIMIT_EXCEEDED,
          PAYMENT_GATEWAY_ERROR, NO_AVAILABLE_INSTANCE) to keyword form with
          metadata for template variables.
      
      - backend/internal/service/payment_config_providers.go
        - New helper validateProviderConfig calls provider.CreateProvider at save
          time. Enabled instances are validated on both Create and Update so admins
          see config errors immediately in the dialog, not later at order creation.
        - Disabled instances are not validated (half-filled drafts are allowed).
      
      - backend/internal/payment/provider/wxpay_test.go
        - Add generateTestKeyPair helper that produces valid RSA-2048 PKCS8/PKIX
          PEMs per test, used by the valid-config baseline (prior fake strings no
          longer pass the eager PEM check).
        - Cover each structured-error branch (missing/invalid-length/malformed PEM).
      79192cf6
  2. 19 Apr, 2026 8 commits
  3. 18 Apr, 2026 6 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
    • Wesley Liddick's avatar
      Merge pull request #1734 from touwaeriol/docs/payment-recommend-kyren-topup · 6c73b621
      Wesley Liddick authored
      docs(payment): add Kyren Topup as international EasyPay provider option
      6c73b621
    • erio's avatar
      0c538a58
    • erio's avatar
      docs: use 易支付 in Chinese coexistence note · 6ae1cc8f
      erio authored
      6ae1cc8f
    • erio's avatar
      docs(payment): add Kyren Topup as international EasyPay provider option · 37123cef
      erio authored
      Restructure the EasyPay recommendation block to present two options side
      by side so users can pick by funding channel and settlement currency:
      
      - Domestic / CNY — ZPay: official Alipay/WeChat API with 1.6% fee and
        T+1 automatic settlement (existing recommendation, expanded with fee
        and settlement details).
      - International / USDT or USD — Kyren Topup (https://kyren.top): global
        payment stack supporting WeChat Pay and Alipay with local-currency
        checkout, USD settlement, and USDT/USD withdrawal. Fees: WeChat 2%,
        Alipay 2.5%, withdrawal 0.1% ($40 min / $150 max). Fills the gap for
        users who cannot use domestic Chinese channels or tolerate Stripe's
        6%+ fees.
      
      Both recommendations share a single disclaimer at the end. The Chinese
      heading uses "易支付" while the English one keeps "EasyPay".
      37123cef
  4. 17 Apr, 2026 12 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(admin): prevent browser password manager from autofilling account API key · 948d8e6d
      erio authored
      Chrome's password manager matched the apikey-type account's Base URL + API Key
      inputs as a login form and autofilled the last saved password by domain, so
      editing a Gemini account could overwrite its apikey with a Claude key that
      shared the same Base URL. Add autocomplete="new-password" plus data-*-ignore
      attributes for 1Password / LastPass / Bitwarden to opt the field out of every
      major password manager's autofill.
      948d8e6d
    • 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
    • github-actions[bot]'s avatar
      6cfdf4ec
    • Wesley Liddick's avatar
      Merge pull request #1683 from FjlI5/dev-main · 358ff6a6
      Wesley Liddick authored
      fix:修复上游账号为OpenAI API key时Claude Code调用缓存率低的问题
      358ff6a6
    • Wesley Liddick's avatar
      Merge pull request #1687 from touwaeriol/refactor/upstream-response-limit-dedup · 41fbdba1
      Wesley Liddick authored
      refactor: extract ReadUpstreamResponseBody to deduplicate response read + too-large handling
      41fbdba1
    • Wesley Liddick's avatar
      Merge pull request #1702 from StarryKira/fix/outbox-watermark-context-dedup-1691 · c22d11ce
      Wesley Liddick authored
      fix: fix outbox watermark context expiry and add in-batch group rebuild dedup
      c22d11ce
    • shaw's avatar
      5d586a9f
    • shaw's avatar
      feat: 支持opus-4.7 · a789c8c4
      shaw authored
      a789c8c4
  5. 16 Apr, 2026 7 commits
  6. 15 Apr, 2026 5 commits