1. 17 Apr, 2026 2 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
      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
  2. 14 Apr, 2026 7 commits
    • 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
      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: gofmt formatting across all Go source files · 1e6912ea
      erio authored
      1e6912ea
    • erio's avatar
      fix(payment): critical audit fixes for security, idempotency and correctness · c738cfec
      erio authored
      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
      c738cfec
    • erio's avatar
      fix: audit fixes - magic strings to constants, frontend any/catch, LB tests · 56e4a9a9
      erio authored
      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
      56e4a9a9
    • erio's avatar
      test(payment): add unit tests for payment audit fixes + allow empty supported_types · 3c884f8e
      erio authored
      Tests (1033 new lines, 100% coverage on modified functions):
      - amount.go: YuanToFen/FenToYuan with precision edge cases
      - wxpay: mapWxState, wxSV, formatPEM, NewWxpay validation
      - alipay: isTradeNotExist, NewAlipay validation
      - webhook: writeSuccessResponse (wxpay JSON, stripe empty, others text)
      - config: validateProviderRequest, isSensitiveConfigField, joinTypes
      - fulfillment: resolveRedeemAction idempotency logic
      
      Business logic changes:
      - Allow empty supported_types on provider instances
      - Block removing payment types when instance has pending orders
      - Extract resolveRedeemAction as testable pure function
      3c884f8e
  3. 13 Apr, 2026 1 commit
    • erio's avatar
      fix(payment): fix Alipay/Wxpay direct provider type mapping and enable... · f498eb8f
      erio authored
      fix(payment): fix Alipay/Wxpay direct provider type mapping and enable cross-provider load balancing
      
      Two issues fixed:
      
      1. Alipay.SupportedTypes() returned ["alipay_direct"] and Wxpay returned
         ["wxpay_direct"], but the frontend sends payment_type="alipay"/"wxpay".
         The registry lookup failed with "payment method (alipay) is not
         configured". Fix: return the base types ["alipay"]/["wxpay"].
      
      2. When multiple providers support the same payment type (e.g. EasyPay
         and Alipay direct both handle "alipay"), only the last-registered
         provider's instances were reachable — the registry mapped one type to
         one provider key, and SelectInstance queried by that single key.
      
         Fix: bypass the registry in invokeProvider and let SelectInstance
         query across all providers when providerKey is empty. The selected
         instance's own ProviderKey (now included in InstanceSelection) is
         used to create the correct provider, enabling true cross-provider
         load balancing.
      
      Closes #1592
      f498eb8f
  4. 11 Apr, 2026 3 commits
    • erio's avatar
      feat(payment): add H5/mobile payment support · 75155903
      erio authored
      Backend:
      - Parse EasyPay `payurl2` field, prefer H5 link on mobile
      - Add `device=mobile` to EasyPay submit.php (popup) mode
      - Expand isMobile detection keywords (add ipad/ipod)
      
      Frontend:
      - Add `isMobileDevice()` utility (userAgentData + UA regex)
      - Mobile + pay_url: direct redirect instead of QR/popup
      - Popup blocked fallback: auto-redirect when window.open fails
      - Stripe WeChat Pay: dynamic client param (mobile_web vs web)
      75155903
    • erio's avatar
      refactor(payment): code standards fixes and regression repairs · e3a000e0
      erio authored
      Backend:
      - Split payment_order.go (546→314 lines) into payment_order_lifecycle.go
      - Replace magic strings with constants in factory, easypay, webhook handler
      - Add rate limit/validity unit constants in payment_order_lifecycle, payment_service
      - Fix critical regression: add PaymentEnabled to GetPublicSettings response
      - Add missing migration 099_fix_migrated_purchase_menu_label_icon.sql
      
      Frontend:
      - Fix StripePopupView.vue: replace `as any` with typed interface, use extractApiErrorMessage
      - Fix AdminOrderTable.vue: replace hardcoded column labels with i18n t() calls
      - Fix SubscriptionsView.vue: replace hardcoded Today/Tomorrow with i18n
      - Extract duplicate statusBadgeClass/canRefund/formatOrderDateTime to orderUtils.ts
      - Add missing i18n keys: common.today, common.tomorrow, payment.orders.orderType/actions
      - Remove dead PurchaseSubscriptionView.vue (replaced by PaymentView)
      e3a000e0
    • erio's avatar
      feat(payment): add complete payment system with multi-provider support · 63d1860d
      erio authored
      Add a full payment and subscription system supporting EasyPay (Alipay/WeChat),
      Stripe, and direct Alipay/WeChat Pay providers with multi-instance load balancing.
      63d1860d