Commit Graph
4033 Commits
Author SHA1 Message Date
erio a9fb750ed0 fix(openai): 移除已下线 Codex 模型并修复归一化兜底副作用
- 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
2026-04-20 22:09:17 +08:00
erio c8a3aa5cb6 chore: bump version to 0.1.114.21 2026-04-20 20:52:09 +08:00
erio c089277f36 feat(monitor): admin channel monitor MVP with SSRF protection and batch aggregation
新增 admin「渠道监控」模块(参考 BingZi-233/check-cx),独立于现有 Channel 体系。
admin 配置 + 后台定时调用上游 LLM chat completions 健康检查 + 所有登录用户只读可见。

后端:
- ent: channel_monitor + channel_monitor_history(AES-256-GCM 加密 api_key)
- service 按职责拆分:service/aggregator/validate/checker/runner/ssrf
- provider strategy map 替代 switch(openai/anthropic/gemini)
- repository batch 聚合(ListLatestForMonitorIDs + ComputeAvailabilityForMonitors)消除 N+1
- runner: ticker(5s) + pond worker pool(5) + inFlight 防并发 + TrySubmit 防雪崩
  + 凌晨 3 点 cron 清理 30 天历史
- SSRF 防护:强制 https + 私网/loopback/云元数据 IP 拒绝(127/8、10/8、172.16/12、
  192.168/16、169.254/16、100.64/10、::1、fc00::/7、fe80::/10)+ DialContext
  在 socket 层防 DNS rebinding
- API key sanitize:擦除 url.Error 与上游响应 body 中的 sk-/sk-ant-/AIza/JWT 模式
- APIKeyDecryptFailed 标志位 + 单 monitor 路径检测,避免空 key 调用上游

handler:
- admin: CRUD + 手动触发 + 历史接口(api_key 脱敏)
- user: 只读列表 + 状态详情(去除 api_key/endpoint)
- ParseChannelMonitorID 共用 + dto.ChannelMonitorExtraModelStatus 共用

前端:
- 路由 /admin/channels/{pricing,monitor} + /monitor(用户只读)
- AppSidebar 父项 expandOnly 支持
- ChannelMonitorView 拆为 8 个子组件 + ChannelStatusView 拆出 detail dialog
- composables/useChannelMonitorFormat + constants/channelMonitor 共享
- i18n monitorCommon namespace 消除 admin/user 两 view 重复

合规:所有文件符合 CLAUDE.md(Go ≤ 500 行 / Vue ≤ 300 行 / 函数 ≤ 30 行)
CI: go build / gofmt / golangci-lint(0 issues) / make test-unit / pnpm build 全绿
2026-04-20 20:21:02 +08:00
erio 73c9b78279 chore: bump version to 0.1.114.20 2026-04-20 19:35:41 +08:00
erio fa00b9b79e feat(payment): wxpay pubkey mode only, eager PEM validation
微信从 2024-10 起强推公钥验签,新商户无法下载平台证书,老商户也陆续被迁移。日志里刚确认 beta 商户被微信 404 拒绝平台证书下载。彻底废弃老模式:

- wxpay.go:
  - publicKey / publicKeyId 加入 required 列表
  - NewWxpay 在保存时主动 parse PEM(privateKey 和 publicKey),格式错立即报 WXPAY_CONFIG_INVALID_KEY 带 {key}
  - ensureClient 只走新公钥验签(NewSHA256WithRSAPubkeyVerifier),移除 buildVerifier 和 downloader 分支
  - 清掉 crypto/rsa、auth、downloader 等不再使用的 import

- providerConfig.ts: publicKey / publicKeyId 两个字段去掉 optional:true → UI 加红星必填

- wxpay_test.go:
  - 引入 generateTestKeyPair 在每次用例动态生成有效 RSA 2048 PKCS8/PKIX PEM
  - 替换原先的 "fake-private-key" 字面量,现在 NewWxpay 会真实解析
  - 移除老模式相关用例(legacy/leftover/pair)
  - 新增 malformed PEM 用例覆盖 WXPAY_CONFIG_INVALID_KEY 分支

- i18n locales: 去掉 WXPAY_CONFIG_PAIR_VIOLATION;新增 WXPAY_CONFIG_INVALID_KEY「{key} 格式错误」中英文
2026-04-20 19:35:12 +08:00
erio c289d2da58 chore: bump version to 0.1.114.19 2026-04-20 19:16:08 +08:00
erio 9ed977dba2 feat(payment): validate provider config at save time + loosen wxpay pair rule
**保存时校验** — admin 保存 enabled 的 provider 时,调用 provider.CreateProvider 做一次构造器校验(只在 enabled 时),把原本只在创建订单才暴露的配置错误提前到保存那一刻。已有的结构化错误(WXPAY_CONFIG_MISSING_KEY 等)直接给前端 i18n 用。

**wxpay 校验放宽** — 之前 publicKey/publicKeyId "必须成对"的规则过于严格:DB 里可能残留历史 publicKey(没 publicKeyId),用户想走老平台证书模式但被 pair violation 拦住。改为:
- publicKeyId 非空 → 必须有 publicKey(报 WXPAY_CONFIG_MISSING_KEY key=publicKey)
- publicKeyId 空 → publicKey 视为历史残留,不使用也不报错

移除 WXPAY_CONFIG_PAIR_VIOLATION 错误码。

- payment_config_providers.go: CreateProviderInstance 和 UpdateProviderInstance 新增 validateProviderConfig;Update 要先取旧记录决定 finalEnabled 和 merged config
- wxpay.go: 去掉成对校验,换成"publicKeyId 非空才要求 publicKey"
- wxpay_test.go: 更新用例反映新语义
2026-04-20 19:15:38 +08:00
erio 44d521e2d2 chore: bump version to 0.1.114.18 2026-04-20 18:52:33 +08:00
erio 6dd578fe80 feat(payment): localize config-field names in error messages
之前错误提示里直接显示原始字段名(certSerial / apiV3Key),用户看不懂。

在 extractI18nErrorMessage 内加 localizeMetadata:自动把 metadata.key 和 metadata.keys(斜杠分隔)对应到 admin.settings.payment.field_<key> 的 i18n 标签,查不到才回退原文。

- 「微信支付配置缺少必填项:certSerial」 → 「微信支付配置缺少必填项:证书序列号」
- 「publicKey/publicKeyId must be provided together」 → 「公钥 / 公钥 ID 必须同时配置」
2026-04-20 18:52:20 +08:00
erio 89f1fe0b4e chore: bump version to 0.1.114.17 2026-04-20 18:45:55 +08:00
erio f2c6687cbf feat(payment): structured error codes + frontend i18n mapping
后端返回结构化错误码(reason + metadata 关键词),前端按 reason 查 i18n 文案并用 metadata 填充占位符,用户看到本地化提示而不是英文原始错误。

Backend:
- wxpay.go: 校验错误从 fmt.Errorf 改为 infraerrors.BadRequest 结构化,新增 reason 码:
  - WXPAY_CONFIG_MISSING_KEY (metadata: key)
  - WXPAY_CONFIG_INVALID_KEY_LENGTH (metadata: key, expected, actual)
  - WXPAY_CONFIG_PAIR_VIOLATION (metadata: keys)
- payment_order.go: invokeProvider 用 errors.As 识别 ApplicationError 透传(保留 wxpay 的 reason/metadata),只有非结构化 err 才 fallback 到 PAYMENT_PROVIDER_MISCONFIGURED;其他几个用户可见错误(TOO_MANY_PENDING / DAILY_LIMIT_EXCEEDED / PAYMENT_GATEWAY_ERROR / NO_AVAILABLE_INSTANCE)message 简化为关键词,参数放 metadata
- wxpay_test.go: 更新断言匹配新的 reason code

Frontend:
- apiError.ts: extractApiErrorCode 优先返回 reason(字符串错误码)而非 HTTP 数字;新增 extractI18nErrorMessage 按 namespace.reason 查 i18n 并以 metadata 作模板变量
- 12 个支付相关 Vue 文件(用户/管理/组件)统一改用 extractI18nErrorMessage('payment.errors', ...)
- i18n zh/en locales 下的 payment.errors 添加完整错误码文案(PAYMENT_DISABLED / INVALID_AMOUNT / TOO_MANY_PENDING / DAILY_LIMIT_EXCEEDED / WXPAY_CONFIG_* 等共 20+ 条,支持 {max}/{remaining}/{key} 等占位符)
- 移除 SettingsView 中已失效的 paymentErrorMap computed
2026-04-20 18:45:30 +08:00
erio 14bb922a92 chore: bump version to 0.1.114.16 2026-04-20 18:20:31 +08:00
erio d635b66c29 feat(payment): support wxpay legacy platform-certificate verifier mode
之前的改动只保留了微信支付"新公钥验签"路径,对仍在用平台证书的老商户不可用。

- wxpay.go: publicKeyId 从必填移除;buildVerifier 根据是否配置 publicKeyId 自动切换:
  - 有 → 新公钥方式(NewSHA256WithRSAPubkeyVerifier + 配置的 publicKey)
  - 无 → 平台证书方式(downloader 自动下载平台证书 + NewSHA256WithRSAVerifier)
  publicKey/publicKeyId 必须成对提供,单独一个视为配置错误
- providerConfig.ts: publicKey/publicKeyId 标记为 optional,certSerial 保留必填
- wxpay_test.go: 补 legacy 模式成功用例和成对校验错误用例
2026-04-20 18:19:56 +08:00
erio 7207c5553b chore: bump version to 0.1.114.15 2026-04-20 18:07:46 +08:00
erio 0130ee2422 fix(payment): mark wxpay publicKeyId/certSerial required, surface provider misconfig error
前端把 wxpay 的 publicKeyId 和 certSerial 标成 optional,但后端 NewWxpay 校验时两者都是必填,导致缺配置的实例在创建订单时落入 CreateProvider 失败分支,对外只抛出模糊的 "payment method is temporarily unavailable"。

- providerConfig.ts: 去掉两个字段的 optional: true,表单必填校验生效
- payment_order.go: CreateProvider 失败时改用 PAYMENT_PROVIDER_MISCONFIGURED 错误码,带底层原因和 provider/instance_id metadata,并加 slog.Error 便于定位
2026-04-20 18:07:37 +08:00
erio 2be60ac63a fix: suppress errcheck on recover() in harvester Read path 2026-04-20 01:29:23 +08:00
erio 089d14a2e3 fix(signature): isolate pool buckets by platform
OAuth/setup-token accounts now use per-platform buckets
(oauth:anthropic, oauth:antigravity) instead of a single shared
"oauth" bucket. Prevents cross-contamination between platforms
whose thinking signatures are incompatible.

BucketFor() now takes a platform parameter. All call sites
(harvester, rectifier factory, gateway service) updated.
2026-04-20 00:47:03 +08:00
erio 53b88213f0 feat(signature): add structured logging for pool signature replacement
Log when signatures are replaced from the pool during rectification,
including account_id, bucket, pool_size, and replacement count.
Helps operators verify the pool-replace strategy is working.
2026-04-20 00:24:27 +08:00
erio ade6684077 chore: remove temporary harvester diagnostic logging 2026-04-20 00:08:55 +08:00
erio 0f9c9e8b75 debug: add harvester diagnostic logging 2026-04-19 23:58:15 +08:00
erio 40976d5f08 fix(signature): async harvester with signature_delta support
Rewrite harvester to be fully decoupled from the main read path:
- Read() copies chunks to a buffered channel (non-blocking)
- Background goroutine parses for signatures independently
- sync.Once prevents double-close panic
- defer recover() in Read() guards send-on-closed-channel
- ctx.Done() arm prevents goroutine leaks on abandoned responses
- Panic recovery with slog.Warn logging

Fix signature extraction: the Anthropic API sends signatures via
content_block_delta with delta.type="signature_delta", not in
content_block_start (which has an empty signature field). Support
both shapes for compatibility.

Add 17 comprehensive tests including signature_delta, panic
isolation, double-close, context cancellation, text containing
"signature" word, and no-thinking streams.
2026-04-19 23:25:31 +08:00
erio 0ff3bfe5fa fix(signature): harvest from signature_delta events, not content_block_start
The Anthropic API sends thinking signatures via content_block_delta
with delta.type="signature_delta", not in the content_block_start
event (which has an empty signature field). The harvester was only
checking content_block_start, so it never captured any signatures
and the pool remained permanently empty.

Now handles both content_block_start (legacy compat) and
content_block_delta/signature_delta (current API behavior).
2026-04-19 23:05:10 +08:00
erio fb9c1323d0 debug: add temporary harvester/factory logging to diagnose empty signature pool 2026-04-19 22:56:30 +08:00
erio 26e7d969e8 fix(tlsfingerprint): realign TLS+headers to Claude Code 2.1.114 baseline
Merge hai/snapshot fingerprint update into release branch, preserving
our architectural optimizations (SOCKS5 ContextDialer, identity_service
refactoring, slog migration, capture_fingerprint multi-file structure).

TLS defaults: 52→17 ciphers, 5→3 curves (drop MLKEM768/P521),
2→1 key share (X25519 only), 3→1 point format, 26→9 sig algs.
New probabilistic ECH GREASE + padding (~50% per handshake).
Extension order realigned: server_name first, encrypt_then_mac removed,
status_request/SCT added.

Headers: UA 2.1.114, Runtime v24.3.0 (bundled Node), Timeout 600.
New BetaStructuredOutputs20251215 for Haiku title-sidecar requests.
2026-04-19 22:21:51 +08:00
erio e20a57f53e refactor(scheduled-test): switch to minimal PR 1753 approach
Replace the 4-file service-layer cleanup with the upstream PR's simpler
transaction-level DELETE in accountRepository.Delete.

Bump version to 0.1.114.13.
2026-04-19 20:44:19 +08:00
erio 91db3aabfe Merge remote-tracking branch 'origin/feat/fix-orphaned-scheduled-tests' into release/custom-0.1.114 2026-04-19 20:30:31 +08:00
erio f68894191b chore: bump version to 0.1.114.12 2026-04-19 20:30:01 +08:00
erio b51f20ea85 fix: add missing ScheduledTestPlanRepository arg in test 2026-04-19 20:29:01 +08:00
erio edf20829ba Merge remote-tracking branch 'origin/feat/fix-orphaned-scheduled-tests' into release/custom-0.1.114 2026-04-19 20:26:49 +08:00
erio 79e100a1cf fix: delete scheduled test plans when account is deleted
Accounts use soft-delete (setting deleted_at), so PostgreSQL's
ON DELETE CASCADE on scheduled_test_plans.account_id never fires.
This leaves orphaned plans that continue executing and cannot be
managed from the frontend since the account no longer exists.

Closes Wei-Shaw/sub2api#1728
2026-04-19 20:22:18 +08:00
erio cae8ed99fa test(tlsfingerprint): update expectations to Claude Code CLI default (X25519MLKEM768 + X25519) 2026-04-19 19:41:44 +08:00
erio c1d95da06a fix(ci): add missing signaturePool arg + gofmt alignment 2026-04-19 19:28:10 +08:00
erio a4209ab4f8 chore: bump version to 0.1.114.11 2026-04-19 19:14:59 +08:00
erio 44666b1282 Merge remote-tracking branch 'origin/fix/quota-exceeded-scheduling' into release/custom-0.1.114 2026-04-19 19:14:10 +08:00
erio 258fd145ff fix(account): prevent quota-exceeded API key/Bedrock accounts from being scheduled
Add quota exceeded check to IsSchedulable() and refactor
shouldClearStickySession to delegate to IsSchedulable(), eliminating
duplicated logic and fixing missed overload/rate-limit/expired checks.
Frontend displays quota exceeded status independently via quota fields.
2026-04-19 18:45:04 +08:00
erio 39d41e921b Merge remote-tracking branch 'origin/fix/xhigh-reasoning-effort' into release/custom-0.1.114
# Conflicts:
#	backend/cmd/server/VERSION
2026-04-19 18:32:30 +08:00
erio 6530776a62 fix: support xhigh reasoning effort in usage records for Claude Messages API
Closes #1732
2026-04-19 18:05:25 +08:00
erio 5e9cbd26b9 fix(signature): address code review findings + expand test coverage
Review fixes (P0/P1/P2):
- P0: move nil receiver guards before stage check in PoolClaudeRectifier
  and PoolAntigravityRectifier to prevent panic on nil receiver
- P1: add explicit bedrock/upstream cases to BucketFor for self-documenting
  intent (return empty bucket = no pool participation)
- P2: replace harvester emit de-dupe from O(N²) [][]byte scan to O(1) map
- P2: add lineBufCap (256KB) to SSE line accumulator to prevent unbounded
  growth from malformed upstream streams without newlines

New test cases (29 → 39 total in signature package):
- MultipleAssistantMessages: verify replacement across user/assistant mix
- MalformedContentSkipped: partial JSON doesn't panic or corrupt counter
- SSE_SignatureSplitAcrossReads: line split across multiple Read calls
- SSE_LineBufCapTruncatesHugeLine: lineBuf cap prevents OOM
- NilRequestNoop: PoolAntigravityRectifier with nil Request
- NilReceiverNoPanic: PoolClaudeRectifier nil receiver
- StripAntigravityRectifier_BothStages: verify both stages call strip func
2026-04-19 17:22:43 +08:00
erio 0e6d5817ba test(signature): add Redis pool unit tests with miniredis
Covers the Lua script logic (ZADD + lazy TTL expiry + capacity trim)
via in-process miniredis — no Docker required, runs under `go test
-tags unit`.

10 cases: basic add/topN, fewer-than-N, empty bucket, capacity
trim to newest 3, lazy expiry on Add, TopN-does-not-filter-by-TTL
(design rule: stale sigs survive until next Add), duplicate score
update, empty-input noop, zero-N guard, Size accuracy.

Also keeps the integration test file (build tag `integration`) for
environments with Docker + testcontainers.
2026-04-19 17:10:24 +08:00
erio 35bdd1400e chore: bump version to 0.1.114.10 2026-04-19 15:18:14 +08:00
erio 91ead361b7 test(signature): add unit tests + fix gjson ForEach indexing bug
Covers:
  - ReplaceThinkingSignaturesInBody / InClaudeRequest:
    M>N cycling, empty pool, no thinking blocks, empty signature
    preservation, string-content messages, nil pool guard
  - Strip / Pool rectifier strategies:
    Strip stage-1 unconditional, stage-2 gated on tool error;
    Pool empty→proceed=false (rule A), no replacements→abort,
    one-shot semantics (stage-2 always declines), pool error
    treated as empty
  - BucketFor: oauth/setup-token share, apikey per-id, unknown empty
  - Harvester SSE: content_block_start extraction, per-response
    dedupe, Skip callback, bucket/capacity guards
  - Harvester non-streaming: buffers until Close then parses once

Along the way caught and fixed a path-construction bug in
ReplaceThinkingSignaturesInBody: it used gjson.ForEach's key.Raw
which is empty for array indices, producing malformed sjson paths
like "messages..content..signature". Switched to manual index
counters so paths are always well-formed integers.
2026-04-19 14:00:01 +08:00
erio c8e4753ada feat(signature): frontend UI + i18n for signature pool size
Adds a Signature Pool Size numeric input to the Rectifier settings
section with a hint explaining the 0-vs-positive semantics. When
pool size > 0, the Thinking Signature and API Key Signature toggle
hints dynamically switch from "strip signatures and retry" to
"replace with pooled signatures (pass through when pool is empty)",
matching the runtime strategy swap.

Includes both zh and en locale keys (poolSize, poolSizeHint,
poolSizeUnit, thinkingSignatureHintPool, apikeySignatureHintPool)
plus the signature_pool_size field in the TypeScript RectifierSettings
interface and save payload.
2026-04-19 13:54:24 +08:00
erio 78de54b693 feat(signature): activate PoolRectifier + harvester hookup
Introduces the pool-replace retry strategy end-to-end. When
RectifierSettings has SignaturePoolSize>0 and the account's per-type
sub-switch is on, the factory returns a PoolClaudeRectifier /
PoolAntigravityRectifier that fetches the freshest signatures from the
Redis pool and cycles them through the request's thinking blocks
(M>N cycling). Rule A: an empty pool transparently passes the
original upstream error back — no fallback to strip.

Key pieces:
  - PoolClaudeRectifier / PoolAntigravityRectifier in internal/service/signature
  - signatureRectifierFactory in the service package selects strategy per
    request via shouldUsePool(ctx, account) which implements the agreed
    decision table (Enabled + SignaturePoolSize>0 + per-type sub-switch)
  - WrapResponseBody helper on the factory is invoked at each Claude-native
    DoWithTLS entry point (main Forward, Anthropic passthrough, Bedrock) to
    run the Harvester over the upstream body; no-op when pool disabled
  - ctxkey.IsSignatureRectifyRetry is set on all retry contexts (Claude
    two-stage, count_tokens, Antigravity signature retries) so the harvester
    skips ingesting signatures from retry responses and does not pollute the
    pool with values we ourselves injected
  - NewGatewayService / NewAntigravityGatewayService now accept
    signature.SignaturePool; wire_gen.go updated accordingly

Antigravity responses are raw Gemini format so WrapResponseBody is not
called there — harvesting stays Claude-only as designed. Antigravity can
still *read* from the shared OAuth pool on retry; whether cross-ecosystem
signatures verify upstream is the question the future PoC will answer.
2026-04-19 13:51:22 +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 84adf1d6de refactor(signature): extract retry body-transform into Rectifier strategy
Introduce internal/service/signature package with ClaudeRectifier and
AntigravityRectifier interfaces plus StripClaudeRectifier /
StripAntigravityRectifier implementations that wrap the legacy
FilterThinkingBlocksForRetry / FilterSignatureSensitiveBlocksForRetry
and stripThinkingFromClaudeRequest / stripSignatureSensitiveBlocksFromClaudeRequest
functions. Refactor the Claude and Antigravity retry loops
(including count_tokens) to delegate body transformation to the
rectifier, keeping all surrounding scaffolding (ops events, logging,
time budget checks, HTTP plumbing) unchanged.

Also extracts the looksLikeToolSignatureError predicate previously
inlined as an anonymous closure.

Zero behavior change: all existing unit tests pass unchanged. This
prepares for Phase 2 where a Pool strategy will be plugged in via
the same interface.
2026-04-19 12:33:56 +08:00
Wesley Liddick 51af8df31d Merge pull request #1731 from touwaeriol/fix/rate-billing-autofill-response-limit
fix: subscription billing, alipay redirect + H5, payment secrets, 128MB response limit
2026-04-19 09:43:24 +08:00
erio 235f710853 feat(payment): redact provider secrets in admin config API
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.
2026-04-19 02:22:53 +08:00
erio c3cb0280ef fix(payment): alipay redirect-only flow, H5 detection and popup sizing
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.
2026-04-19 02:22:41 +08:00
erio 2a7272429f fix(payment): size popup to fit alipay checkout without any scrolling
Alipay's page is ~1200x900; a fixed 1250x780 still clipped vertically.
Replace the constant with getPaymentPopupFeatures(): it prefers
1250x900, clamps to window.screen.avail*, and centers the popup so
it never overflows the visible work area on smaller laptops.

Drop POPUP_WINDOW_FEATURES and STRIPE_POPUP_WINDOW_FEATURES in favor of
the helper — all five call sites migrated.

Bump version to 0.1.114.9
2026-04-19 01:32:17 +08:00
erio 4b948e3917 fix(payment): widen popup to 1250x780 so alipay checkout fits without scrolling
Alipay's standard checkout (QR + account login panel) needs ~1200px
width. The default popup was 1000x750, forcing a horizontal scrollbar.
Unify to the wider size that the Stripe-alipay popup already used.

Bump version to 0.1.114.8
2026-04-19 01:24:53 +08:00