Skip to content

feat: add XunhuPay (虎皮椒) payment gateway integration - #4220

Closed
chenzhongd wants to merge 2 commits into
QuantumNous:mainfrom
chenzhongd:feat/xunhupay-payment
Closed

feat: add XunhuPay (虎皮椒) payment gateway integration#4220
chenzhongd wants to merge 2 commits into
QuantumNous:mainfrom
chenzhongd:feat/xunhupay-payment

Conversation

@chenzhongd

@chenzhongd chenzhongd commented Apr 13, 2026

Copy link
Copy Markdown

⚠️ 提交说明 / PR Notice

[!IMPORTANT]

新增虎皮椒(XunhuPay)支付网关支持,允许个人开发者通过 xunhupay.com 无需营业执照即可接入微信支付和支付宝,覆盖充值(top-up)和订阅(subscription)两个场景,稳定可靠且费率清晰,更加适合个人或者小型团队开发。

核心逻辑:

  • 后端新增签名验证模块(MD5 + 有序 key=value 拼接 + appSecret),请求虎皮椒网关获取支付跳转链接,用户完成支付后由异步回调(notify)触发配额增加或订单完成
  • 通过 XunhuPayMethod 配置项(alipay/wxpay/both)动态决定在充值页面展示哪些支付按钮,启用虎皮椒后这些按钮自动路由至虎皮椒接口而非易支付
  • 前端新增独立的虎皮椒配置卡片(SettingsPaymentGatewayXunhu),与易支付配置面板完全分离,互不干扰;两者可以共存,也可以只启用其中一个

生效条件: 管理员在「支付设置」填写 AppID、AppSecret、网关地址后,充值页面的微信/支付宝按钮即自动切换为虎皮椒渠道。

📝 变更描述 / Description

(简述:做了什么?为什么这样改能生效?请基于你对代码逻辑的理解来写,避免粘贴未经整理的内容)

🚀 变更类型 / Type of change

  • [✅ ] ✨ 新功能 (New feature) - 重大特性建议先通过 Issue 沟通

🔗 关联任务 / Related Issue

  • Closes # (如有)

✅ 提交前检查项 / Checklist

  • [✅] 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • [✅] 非重复提交: 我已搜索现有的 IssuesPRs,确认不是重复提交。
  • [✅ ] Bug fix 说明: 若此 PR 标记为 Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。
  • [✅ ] 变更理解: 我已理解这些更改的工作原理及可能影响。
  • [✅] 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • [✅] 本地验证: 已在本地运行并通过测试或手动验证,维护者可以据此复核结果。
  • [✅] 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

(请在此粘贴截图、关键日志或测试报告,以证明变更生效)

  • 管理员配置页截图(显示虎皮椒配置表单)
af99697e3abb11b5267c3934b458de50
  • 充值页截图(显示微信/支付宝按钮已出现)
8434f393da7ff2c27fff59a9918333a2
  • 虎皮椒发起支付后的跳转截图
3952b0e3dfb648e3fab2068f78b190bf

Summary by CodeRabbit

  • New Features

    • Added XunhuPay payment gateway for subscriptions and top-ups, including user-facing payment flows and callback handling
    • Admin UI to configure XunhuPay credentials and choose which user payment methods (WeChat/Alipay) are shown
  • Bug Fixes / Improvements

    • Top-up UI and API now expose an XunhuPay flag and route payments through the appropriate gateway when enabled
    • Improved payment redirect handling (Safari vs new-tab)
  • Chores

    • Updated translations for expanded payment support
    • Ignored local XunhuPay dev files in gitignore
  • Style

    • Minor UI/formatting refinements in payment settings and top-up components

Add support for XunhuPay (xunhupay.com) as a payment gateway for both
top-ups and subscription purchases. XunhuPay enables individual sellers
to accept WeChat Pay and Alipay without a business license.

Backend changes:
- Add setting/operation_setting/xunhupay_setting.go with XunhuPayAppId,
  XunhuPayAppSecret, XunhuPayGateway and XunhuPayMethod vars
- Add controller/topup_xunhupay.go: RequestXunhuPay handler and
  XunhuPayNotify callback for top-up flow
- Add controller/subscription_payment_xunhupay.go: subscription payment
  request and notify/return handlers
- Update model/option.go: persist and load XunhuPay settings
- Update router/api-router.go: register /xunhupay/pay, /xunhupay/notify
  and /subscription/xunhupay/{pay,notify,return} routes
- Update controller/topup.go: auto-inject alipay/wxpay into pay_methods
  based on XunhuPayMethod setting when XunhuPay is configured; expose
  enable_xunhupay_topup flag to frontend

Frontend changes:
- Add web/src/pages/Setting/Payment/SettingsPaymentGatewayXunhu.jsx:
  dedicated settings panel with AppID, AppSecret, Gateway and
  XunhuPayMethod (alipay/wxpay/both) selector
- Update web/src/components/settings/PaymentSetting.jsx: import and
  render SettingsPaymentGatewayXunhu card; add XunhuPayMethod to state
- Update SettingsPaymentGateway.jsx: remove duplicate xunhupay fields
  (now managed by dedicated component)
- Update web/src/components/topup/index.jsx: route alipay/wxpay clicks
  to /api/user/xunhupay/pay when enableXunhupayTopUp is true
- Update RechargeCard.jsx and SubscriptionPlansCard.jsx: pass through
  enableXunhupayTopUp prop; route subscription payment to xunhupay
- Update i18n locales (zh-CN, zh-TW, en, fr, ja, ru, vi) with
  XunhuPay-related translation keys

Signing algorithm: MD5 over sorted key=value pairs + appSecret, as
documented at https://docs.xunhupay.com
@coderabbitai

coderabbitai Bot commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Adds XunhuPay payment gateway support across backend (top-up and subscription controllers, routing, options), frontend UI and settings, i18n updates, and gitignore entries; implements request/notify/return flows, signature verification, and persistence of pending orders.

Changes

Cohort / File(s) Summary
Backend: XunhuPay controllers
controller/subscription_payment_xunhupay.go, controller/topup_xunhupay.go
New controllers implementing XunhuPay request/notify/return handlers, hash generation/verification, gateway HTTP integration, pending order creation, and order completion logic.
Backend: Option & Routing
model/option.go, setting/operation_setting/xunhupay_setting.go, router/api-router.go
Adds XunhuPay option keys (AppId, AppSecret, Gateway, Method), wires them into option map and update flow, and registers new API routes for pay/notify endpoints (user and subscription).
Backend: Top-up logic
controller/topup.go
Updates GetTopUpInfo to expose enable_xunhupay_topup, compute enable flags, and auto-inject alipay/wxpay payMethods when configured.
Frontend: Settings & Pages
web/src/pages/Setting/Payment/SettingsPaymentGatewayXunhu.jsx, web/src/pages/Setting/Payment/SettingsPaymentGateway.jsx, web/src/components/settings/PaymentSetting.jsx
Adds XunhuPay settings page/component, removes legacy PayMethods textarea from generic settings, and wires new XunhuPay inputs into settings UI.
Frontend: Top-up UI & flow
web/src/components/topup/index.jsx, web/src/components/topup/RechargeCard.jsx, web/src/components/topup/SubscriptionPlansCard.jsx
Threads enableXunhupayTopUp prop, routes alipay/wxpay to XunhuPay endpoints when enabled, updates redirect/open behavior (Safari-aware), and adjusts conditional rendering/formatting.
i18n
web/src/i18n/locales/en.json, fr.json, ja.json, ru.json, vi.json, zh-CN.json, zh-TW.json
Adds/updates localized strings to reflect support for both Epay and XunhuPay and related UI fragments.
Misc / Repo
.gitignore
Adds new-api-local to ignore list.

Sequence Diagram

sequenceDiagram
    actor User
    participant Client as Web Client
    participant Controller as Backend Controller
    participant DB as Database
    participant Gateway as XunhuPay Gateway

    rect rgba(100, 150, 255, 0.5)
    Note over User,Gateway: Top-up / Subscription Payment Request
    User->>Client: submit amount & payment method
    Client->>Controller: POST /api/*/xunhupay/pay
    activate Controller
    Controller->>Controller: validate input, compute tradeId & hash
    Controller->>DB: create pending order
    Controller->>Gateway: POST payment request (form/json)
    activate Gateway
    Gateway-->>Controller: return payment URL/response
    deactivate Gateway
    Controller-->>Client: return gateway URL
    deactivate Controller
    Client->>User: redirect/open payment page
    end

    rect rgba(100, 200, 150, 0.5)
    Note over User,Gateway: Payment Callback Handling
    User->>Gateway: complete payment
    Gateway->>Controller: POST/GET /xunhupay/notify or return
    activate Controller
    Controller->>Controller: extract params, verify hash, check status
    Controller->>DB: lock & load order, update status, credit user (if success)
    Controller-->>Gateway: respond "success"/"fail"
    deactivate Controller
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • Calcium-Ion
  • seefs001
  • creamlike1024

Poem

🐰 I hopped through code with nimble feet,
XunhuPay joined the payment suite.
Hashes checked and orders queued,
Redirects sent — the flow is glued.
Hop, hop, the rabbit says: complete! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add XunhuPay (虎皮椒) payment gateway integration' accurately and clearly summarizes the main change: adding support for a new payment gateway integration.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (4)
.gitignore (1)

15-15: Unrelated change in this PR.

The addition of new-api-local to .gitignore appears unrelated to the XunhuPay payment gateway integration. While this is a harmless convenience for local development, consider keeping PR changes focused on the stated objective to improve reviewability.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.gitignore at line 15, The PR contains an unrelated .gitignore addition
"new-api-local" which is not part of the XunhuPay payment gateway work; remove
that entry from the current diff (or revert the .gitignore change) so the PR
only includes payment gateway-related changes, or alternatively extract the
.gitignore modification into a separate cleanup PR referencing "new-api-local"
to keep this review focused.
web/src/components/topup/index.jsx (1)

237-249: Fragile response type detection using axios config inspection.

The condition res.config?.url?.includes('xunhupay') relies on internal axios request config to determine the response type. This is brittle and could break with axios version changes or request interceptors. Consider tracking which endpoint was called in a local variable.

♻️ Proposed fix using explicit tracking
       let res;
+      let isXunhupayRequest = false;
       if (payWay === 'stripe') {
         res = await API.post('/api/user/stripe/pay', {
           amount: parseInt(topUpCount),
           payment_method: 'stripe',
         });
       } else if (
         enableXunhupayTopUp &&
         (payWay === 'alipay' || payWay === 'wxpay')
       ) {
         // 虎皮椒支付 — 直接跳转 URL
+        isXunhupayRequest = true;
         res = await API.post('/api/user/xunhupay/pay', {
           amount: parseInt(topUpCount),
           payment_method: payWay,
         });
       } else {
         // ... existing code
       }
       
       // Then in the success handler:
-          } else if (
-            res.config?.url?.includes('xunhupay') ||
-            (enableXunhupayTopUp && res.data.url && !res.data.data)
-          ) {
+          } else if (isXunhupayRequest) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/topup/index.jsx` around lines 237 - 249, The current
branch detection uses axios response config
(res.config?.url?.includes('xunhupay')) which is brittle; instead, declare and
set a local flag (e.g., isXunhupayRequest or calledEndpoint) right before making
the axios request in the top-up flow, pass or capture that flag into the
response handler, and replace the res.config check with that flag when deciding
to open res.data.url (when enableXunhupayTopUp && res.data.url &&
!res.data.data). Ensure the new flag is set for the code paths that call the
axios request so navigator/ window.location.href / window.open logic keeps
working without relying on axios internals.
controller/topup_xunhupay.go (1)

93-103: Validate XunhuPay configuration before generating URLs.

The configuration check at lines 100-103 happens after returnUrl and notifyUrl are already constructed at lines 94-95. While this doesn't cause functional issues, it's cleaner to validate configuration first.

♻️ Proposed reordering
+	if operation_setting.XunhuPayAppId == "" || operation_setting.XunhuPayAppSecret == "" || operation_setting.XunhuPayGateway == "" {
+		c.JSON(200, gin.H{"message": "error", "data": "当前管理员未配置虎皮椒支付信息"})
+		return
+	}
+
 	callBackAddress := service.GetCallbackAddress()
 	returnUrl := system_setting.ServerAddress + "/console/log"
 	notifyUrl := callBackAddress + "/api/user/xunhupay/notify"
 	
 	tradeNo := fmt.Sprintf("%s%d", common.GetRandomString(6), time.Now().Unix())
 	tradeNo = fmt.Sprintf("USR%dNO%s", id, tradeNo)
-	
-	if operation_setting.XunhuPayAppId == "" || operation_setting.XunhuPayAppSecret == "" || operation_setting.XunhuPayGateway == "" {
-		c.JSON(200, gin.H{"message": "error", "data": "当前管理员未配置虎皮椒支付信息"})
-		return
-	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controller/topup_xunhupay.go` around lines 93 - 103, Move the XunhuPay
configuration validation to run before constructing returnUrl and notifyUrl so
invalid config aborts early; specifically, in the handler in topup_xunhupay.go
check operation_setting.XunhuPayAppId, XunhuPayAppSecret and XunhuPayGateway
(the existing if block) before calling service.GetCallbackAddress() and building
returnUrl and notifyUrl (the variables returnUrl and notifyUrl), and return the
same JSON error if validation fails.
controller/topup.go (1)

99-105: Injected payment methods missing color field.

The existing PayMethods in setting/operation_setting/payment_setting_old.go include a color field for each method (e.g., "rgba(var(--semi-blue-5), 1)" for alipay). The injected methods here only include name, type, and min_topup, which may cause inconsistent UI rendering if the frontend expects a color.

🔧 Proposed fix to add color field
 			if !hasAlipay {
 				payMethods = append(payMethods, map[string]string{
 					"name":      "支付宝",
 					"type":      "alipay",
+					"color":     "rgba(var(--semi-blue-5), 1)",
 					"min_topup": minTopupStr,
 				})
 			}
 			if !hasWxpay {
 				payMethods = append(payMethods, map[string]string{
 					"name":      "微信支付",
 					"type":      "wxpay",
+					"color":     "rgba(var(--semi-green-5), 1)",
 					"min_topup": minTopupStr,
 				})
 			}

Also applies to: 115-121

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controller/topup.go` around lines 99 - 105, The injected payment method maps
in controller/topup.go (the block that appends to payMethods when !hasAlipay and
the similar block around lines 115-121) are missing the "color" key expected by
the frontend; update those append calls (the map literal created inside the if
!hasAlipay branch and the other similar branch) to include a "color" field with
the same value used in setting/operation_setting/payment_setting_old.go (e.g.,
"rgba(var(--semi-blue-5), 1)" for alipay) so the returned PayMethods entries
match the existing structure.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@controller/topup_xunhupay.go`:
- Around line 240-256: The notify handler currently returns early on Update()
and IncreaseUserQuota() errors without writing a response, causing the caller to
see no explicit failure; update the handler around topUp.Update() and
model.IncreaseUserQuota(...) to write an explicit failure response (e.g., send
"fail" or the expected error payload) before each early return, then proceed to
return; locate the block handling topUp.Status == "pending" (references: topUp,
Update(), IncreaseUserQuota, RecordLog) and add the response writes immediately
prior to the existing returns for both error branches.

In `@web/src/i18n/locales/fr.json`:
- Line 17: The French locale changed the source i18n key string which breaks
lookups from SettingsPaymentGateway.jsx where
t('(支持易支付和虎皮椒接口,默认使用上方服务器地址作为回调地址!)') is still used; restore compatibility by
keeping the original Chinese key as an entry in web/src/i18n/locales/fr.json and
map it to the same French translation (i.e., add the old Chinese key with the
same value as the new key), so t('…原中文key…') in SettingsPaymentGateway.jsx
continues to resolve correctly while you migrate callsites.

In `@web/src/pages/Setting/Payment/SettingsPaymentGatewayXunhu.jsx`:
- Around line 127-132: The banner description contains hard-coded Chinese text
"异步回调地址(notify_url):" which breaks i18n; update the description string in
SettingsPaymentGatewayXunhu.jsx to wrap that prefix with the translation
function t(), e.g. use t('异步回调地址(notify_url):') combined with the existing
dynamic URL expression (keep props.options.CustomCallbackAddress,
props.options.ServerAddress and removeTrailingSlash(...) logic intact) so the
entire visible prefix is localizable while preserving the URL construction.
- Around line 81-85: The check for empty options is dead because XunhuPayMethod
is pushed before it; move the emptiness check to occur before pushing
XunhuPayMethod (or alternatively remove the check) so that the condition can
actually be true; update the logic around the options array construction in the
function that builds options (referencing XunhuPayMethod and options) to
validate the array contents first and only push XunhuPayMethod after the
empty-check passes or handle the empty-case appropriately (e.g., showError and
return) to restore the intended behavior.

---

Nitpick comments:
In @.gitignore:
- Line 15: The PR contains an unrelated .gitignore addition "new-api-local"
which is not part of the XunhuPay payment gateway work; remove that entry from
the current diff (or revert the .gitignore change) so the PR only includes
payment gateway-related changes, or alternatively extract the .gitignore
modification into a separate cleanup PR referencing "new-api-local" to keep this
review focused.

In `@controller/topup_xunhupay.go`:
- Around line 93-103: Move the XunhuPay configuration validation to run before
constructing returnUrl and notifyUrl so invalid config aborts early;
specifically, in the handler in topup_xunhupay.go check
operation_setting.XunhuPayAppId, XunhuPayAppSecret and XunhuPayGateway (the
existing if block) before calling service.GetCallbackAddress() and building
returnUrl and notifyUrl (the variables returnUrl and notifyUrl), and return the
same JSON error if validation fails.

In `@controller/topup.go`:
- Around line 99-105: The injected payment method maps in controller/topup.go
(the block that appends to payMethods when !hasAlipay and the similar block
around lines 115-121) are missing the "color" key expected by the frontend;
update those append calls (the map literal created inside the if !hasAlipay
branch and the other similar branch) to include a "color" field with the same
value used in setting/operation_setting/payment_setting_old.go (e.g.,
"rgba(var(--semi-blue-5), 1)" for alipay) so the returned PayMethods entries
match the existing structure.

In `@web/src/components/topup/index.jsx`:
- Around line 237-249: The current branch detection uses axios response config
(res.config?.url?.includes('xunhupay')) which is brittle; instead, declare and
set a local flag (e.g., isXunhupayRequest or calledEndpoint) right before making
the axios request in the top-up flow, pass or capture that flag into the
response handler, and replace the res.config check with that flag when deciding
to open res.data.url (when enableXunhupayTopUp && res.data.url &&
!res.data.data). Ensure the new flag is set for the code paths that call the
axios request so navigator/ window.location.href / window.open logic keeps
working without relying on axios internals.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 104c9f34-d27d-4d4f-a93c-f0714bca53b6

📥 Commits

Reviewing files that changed from the base of the PR and between 8c8661d and 2101b8f.

📒 Files selected for processing (20)
  • .gitignore
  • controller/subscription_payment_xunhupay.go
  • controller/topup.go
  • controller/topup_xunhupay.go
  • model/option.go
  • router/api-router.go
  • setting/operation_setting/xunhupay_setting.go
  • web/src/components/settings/PaymentSetting.jsx
  • web/src/components/topup/RechargeCard.jsx
  • web/src/components/topup/SubscriptionPlansCard.jsx
  • web/src/components/topup/index.jsx
  • web/src/i18n/locales/en.json
  • web/src/i18n/locales/fr.json
  • web/src/i18n/locales/ja.json
  • web/src/i18n/locales/ru.json
  • web/src/i18n/locales/vi.json
  • web/src/i18n/locales/zh-CN.json
  • web/src/i18n/locales/zh-TW.json
  • web/src/pages/Setting/Payment/SettingsPaymentGateway.jsx
  • web/src/pages/Setting/Payment/SettingsPaymentGatewayXunhu.jsx

Comment on lines +240 to +256
if topUp.Status == "pending" {
topUp.Status = "success"
err := topUp.Update()
if err != nil {
log.Printf("虎皮椒回调更新订单失败: %v", topUp)
return
}
dAmount := decimal.NewFromInt(int64(topUp.Amount))
dQuotaPerUnit := decimal.NewFromFloat(common.QuotaPerUnit)
quotaToAdd := int(dAmount.Mul(dQuotaPerUnit).IntPart())
err = model.IncreaseUserQuota(topUp.UserId, quotaToAdd, true)
if err != nil {
log.Printf("虎皮椒回调更新用户失败: %v", topUp)
return
}
model.RecordLog(topUp.UserId, model.LogTypeTopup, fmt.Sprintf("使用虎皮椒充值成功,充值金额: %v,支付金额:%f", logger.LogQuota(quotaToAdd), topUp.Money))
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing response write on error paths in notify handler.

Lines 245 and 253 return early without writing a response to the client. While XunhuPay may retry on timeout, explicit "fail" responses provide clearer feedback.

🐛 Proposed fix to add response writes
 		if topUp.Status == "pending" {
 			topUp.Status = "success"
 			err := topUp.Update()
 			if err != nil {
 				log.Printf("虎皮椒回调更新订单失败: %v", topUp)
+				_, _ = c.Writer.Write([]byte("fail"))
 				return
 			}
 			dAmount := decimal.NewFromInt(int64(topUp.Amount))
 			dQuotaPerUnit := decimal.NewFromFloat(common.QuotaPerUnit)
 			quotaToAdd := int(dAmount.Mul(dQuotaPerUnit).IntPart())
 			err = model.IncreaseUserQuota(topUp.UserId, quotaToAdd, true)
 			if err != nil {
 				log.Printf("虎皮椒回调更新用户失败: %v", topUp)
+				_, _ = c.Writer.Write([]byte("fail"))
 				return
 			}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controller/topup_xunhupay.go` around lines 240 - 256, The notify handler
currently returns early on Update() and IncreaseUserQuota() errors without
writing a response, causing the caller to see no explicit failure; update the
handler around topUp.Update() and model.IncreaseUserQuota(...) to write an
explicit failure response (e.g., send "fail" or the expected error payload)
before each early return, then proceed to return; locate the block handling
topUp.Status == "pending" (references: topUp, Update(), IncreaseUserQuota,
RecordLog) and add the response writes immediately prior to the existing returns
for both error branches.

",时间:": ", time:",
",点击更新": ", cliquez sur Mettre à jour",
"(当前仅支持易支付接口,默认使用上方服务器地址作为回调地址!)": "(Actuellement, seule l'interface Epay est prise en charge, l'adresse du serveur ci-dessus est utilisée par défaut comme adresse de rappel !)",
"(支持易支付和虎皮椒接口,默认使用上方服务器地址作为回调地址!)": "(Prise en charge des interfaces Epay et XunhuPay, l'adresse du serveur ci-dessus est utilisée par défaut comme adresse de rappel !)",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Preserve the old i18n key until all callsites are migrated.

Line 17 changed the source key, but web/src/pages/Setting/Payment/SettingsPaymentGateway.jsx Line 216 still calls the old key. This will break French lookup for that string and show fallback Chinese.

Suggested compatibility patch
+    "(当前仅支持易支付接口,默认使用上方服务器地址作为回调地址!)": "(Prise en charge des interfaces Epay et XunhuPay, l'adresse du serveur ci-dessus est utilisée par défaut comme adresse de rappel !)",
     "(支持易支付和虎皮椒接口,默认使用上方服务器地址作为回调地址!)": "(Prise en charge des interfaces Epay et XunhuPay, l'adresse du serveur ci-dessus est utilisée par défaut comme adresse de rappel !)",

As per coding guidelines, translation files use Chinese source strings as keys and components must call matching t('中文key') keys.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"(支持易支付和虎皮椒接口,默认使用上方服务器地址作为回调地址!)": "(Prise en charge des interfaces Epay et XunhuPay, l'adresse du serveur ci-dessus est utilisée par défaut comme adresse de rappel !)",
"(当前仅支持易支付接口,默认使用上方服务器地址作为回调地址!)": "(Prise en charge des interfaces Epay et XunhuPay, l'adresse du serveur ci-dessus est utilisée par défaut comme adresse de rappel !)",
"(支持易支付和虎皮椒接口,默认使用上方服务器地址作为回调地址!)": "(Prise en charge des interfaces Epay et XunhuPay, l'adresse du serveur ci-dessus est utilisée par défaut comme adresse de rappel !)",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/i18n/locales/fr.json` at line 17, The French locale changed the
source i18n key string which breaks lookups from SettingsPaymentGateway.jsx
where t('(支持易支付和虎皮椒接口,默认使用上方服务器地址作为回调地址!)') is still used; restore compatibility
by keeping the original Chinese key as an entry in web/src/i18n/locales/fr.json
and map it to the same French translation (i.e., add the old Chinese key with
the same value as the new key), so t('…原中文key…') in SettingsPaymentGateway.jsx
continues to resolve correctly while you migrate callsites.

Comment on lines +81 to +85
if (options.length === 0) {
showError(t('没有需要更新的内容'));
setLoading(false);
return;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Dead code: empty options check will never trigger.

Since XunhuPayMethod is always pushed to the options array at line 79 (before this check), options.length will always be at least 1. This check will never trigger and the error message will never be shown.

🐛 Proposed fix to check before pushing XunhuPayMethod
-      // 支付方式选择始终保存
-      options.push({ key: 'XunhuPayMethod', value: inputs.XunhuPayMethod || 'both' });
-
-      if (options.length === 0) {
+      if (options.length === 0) {
         showError(t('没有需要更新的内容'));
         setLoading(false);
         return;
       }
+
+      // 支付方式选择始终保存
+      options.push({ key: 'XunhuPayMethod', value: inputs.XunhuPayMethod || 'both' });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/Setting/Payment/SettingsPaymentGatewayXunhu.jsx` around lines
81 - 85, The check for empty options is dead because XunhuPayMethod is pushed
before it; move the emptiness check to occur before pushing XunhuPayMethod (or
alternatively remove the check) so that the condition can actually be true;
update the logic around the options array construction in the function that
builds options (referencing XunhuPayMethod and options) to validate the array
contents first and only push XunhuPayMethod after the empty-check passes or
handle the empty-case appropriately (e.g., showError and return) to restore the
intended behavior.

Comment on lines +127 to +132
description={`异步回调地址(notify_url):${
props.options.CustomCallbackAddress ||
(props.options.ServerAddress
? removeTrailingSlash(props.options.ServerAddress)
: t('网站地址'))
}/api/user/xunhupay/notify`}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Chinese text in Banner description not wrapped with t() for i18n.

The text 异步回调地址(notify_url): should be wrapped with t() for internationalization consistency, following the coding guidelines.

🌐 Proposed fix for i18n
           <Banner
             type='info'
             style={{ marginTop: 12, marginBottom: 4 }}
-            description={`异步回调地址(notify_url):${
+            description={`${t('异步回调地址')}(notify_url):${
               props.options.CustomCallbackAddress ||
               (props.options.ServerAddress
                 ? removeTrailingSlash(props.options.ServerAddress)
                 : t('网站地址'))
             }/api/user/xunhupay/notify`}
           />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
description={`异步回调地址(notify_url):${
props.options.CustomCallbackAddress ||
(props.options.ServerAddress
? removeTrailingSlash(props.options.ServerAddress)
: t('网站地址'))
}/api/user/xunhupay/notify`}
description={`${t('异步回调地址')}(notify_url):${
props.options.CustomCallbackAddress ||
(props.options.ServerAddress
? removeTrailingSlash(props.options.ServerAddress)
: t('网站地址'))
}/api/user/xunhupay/notify`}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/Setting/Payment/SettingsPaymentGatewayXunhu.jsx` around lines
127 - 132, The banner description contains hard-coded Chinese text
"异步回调地址(notify_url):" which breaks i18n; update the description string in
SettingsPaymentGatewayXunhu.jsx to wrap that prefix with the translation
function t(), e.g. use t('异步回调地址(notify_url):') combined with the existing
dynamic URL expression (keep props.options.CustomCallbackAddress,
props.options.ServerAddress and removeTrailingSlash(...) logic intact) so the
entire visible prefix is localizable while preserving the URL construction.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/src/components/topup/SubscriptionPlansCard.jsx`:
- Around line 191-206: The code calls submitEpayForm and treats the payment as
started even when res.data.url is missing or window.open() is blocked; update
the logic in the block handling isXunhupayMethod and non-Xunhu flow to first
verify res.data.url exists before calling submitEpayForm, and when using
window.open(res.data.url, '_blank') check the returned window reference (it can
be null if blocked) and fall back to same-tab navigation via
window.location.href; only call showSuccess() and closeBuy() after a guaranteed
navigation or successful submitEpayForm invocation (i.e., when URL is present
and either form submitted or popup opened or fallback navigation executed).

In `@web/src/i18n/locales/en.json`:
- Around line 12-14: Remove the duplicate i18n entries by deleting the repeated
keys shown in the diff (", no active subscription. Wallet will be used
automatically.", ",time:", ", click Update") from their duplicate location so
only the original definitions remain; locate the duplicate JSON keys (the
Chinese keys ",当前无生效订阅,将自动使用钱包", ",时间:", ",点击更新") in
web/src/i18n/locales/en.json and remove the extra occurrences that conflict with
the originals at lines ~3518-3520 to satisfy the noDuplicateObjectKeys lint
rule.

In `@web/src/i18n/locales/fr.json`:
- Around line 14-18: The file contains duplicate i18n keys (e.g. the entries for
",当前无生效订阅,将自动使用钱包", ",时间:", ",点击更新" and "(筛选后显示 {{count}} 条)_one") that shadow
later definitions and trigger noDuplicateObjectKeys errors; remove the earlier
duplicate entries (the ones near the top of the file shown in the diff) or
consolidate them with the canonical definitions later in the file so only one
definition remains per key (ensure you keep the intended French translations in
the canonical entries and delete the redundant top-of-file occurrences).

In `@web/src/i18n/locales/ja.json`:
- Around line 12-14: Remove the duplicate JSON keys in ja.json that collide with
later definitions: ",当前无生效订阅,将自动使用钱包", ",时间:", and ",点击更新"; keep the single
intended translation (the later entries at lines 3455–3457) and delete the
earlier occurrences shown in the diff so the linter rule noDuplicateObjectKeys
no longer errors and the translations are not shadowed.

In `@web/src/i18n/locales/ru.json`:
- Around line 16-18: Remove the duplicate JSON keys ",当前无生效订阅,将自动使用钱包", ",时间:",
and ",点击更新" from this block and instead update their single existing
declarations later in the same ru.json object; locate the earlier definitions of
those keys and apply any intended text changes there (do not leave duplicate
keys in the file).

In `@web/src/i18n/locales/vi.json`:
- Around line 12-14: Remove the redundant early JSON entries for the three
duplicate keys ",当前无生效订阅,将自动使用钱包", ",时间:", and ",点击更新" by deleting those initial
occurrences so only the later definitions (the authoritative ones) remain;
locate these exact key strings in the vi.json content and remove the earlier
key/value pairs to avoid duplicate-key parsing behavior and ensure the later
values are used at runtime.

In `@web/src/i18n/locales/zh-TW.json`:
- Around line 9-10: Remove the duplicated i18n keys ",时间:" and ",点击更新" from the
zh-TW locale file by deleting the duplicate entries found in the diff so only
the original occurrences remain (the translations already present elsewhere
should be preserved); ensure there is exactly one key/value pair for ",时间:" and
one for ",点击更新" in the file and re-run the linter to confirm the
noDuplicateObjectKeys error is resolved.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 01de668a-3121-4085-b708-aac08363b1b3

📥 Commits

Reviewing files that changed from the base of the PR and between 2101b8f and 5a70206.

📒 Files selected for processing (11)
  • .gitignore
  • model/option.go
  • router/api-router.go
  • web/src/components/topup/SubscriptionPlansCard.jsx
  • web/src/i18n/locales/en.json
  • web/src/i18n/locales/fr.json
  • web/src/i18n/locales/ja.json
  • web/src/i18n/locales/ru.json
  • web/src/i18n/locales/vi.json
  • web/src/i18n/locales/zh-CN.json
  • web/src/i18n/locales/zh-TW.json
✅ Files skipped from review due to trivial changes (3)
  • .gitignore
  • model/option.go
  • web/src/i18n/locales/zh-CN.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • router/api-router.go

Comment on lines 191 to 206
if (res.data?.message === 'success') {
submitEpayForm({ url: res.data.url, params: res.data.data });
if (isXunhupayMethod && res.data.url) {
// 虎皮椒返回直接跳转链接
const isSafari =
navigator.userAgent.indexOf('Safari') > -1 &&
navigator.userAgent.indexOf('Chrome') < 1;
if (isSafari) {
window.location.href = res.data.url;
} else {
window.open(res.data.url, '_blank');
}
} else {
submitEpayForm({ url: res.data.url, params: res.data.data });
}
showSuccess(t('已发起支付'));
closeBuy();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

fd -t f "SubscriptionPlansCard.jsx" web/src

Repository: QuantumNous/new-api

Length of output: 113


🏁 Script executed:

cat -n web/src/components/topup/SubscriptionPlansCard.jsx | sed -n '170,210p'

Repository: QuantumNous/new-api

Length of output: 1617


🏁 Script executed:

cat -n web/src/components/topup/SubscriptionPlansCard.jsx | sed -n '1,50p'

Repository: QuantumNous/new-api

Length of output: 1906


🏁 Script executed:

cat -n web/src/components/topup/SubscriptionPlansCard.jsx | sed -n '205,220p'

Repository: QuantumNous/new-api

Length of output: 523


🏁 Script executed:

rg "showSuccess|showError" web/src/components/topup/SubscriptionPlansCard.jsx -A 1 -B 1

Repository: QuantumNous/new-api

Length of output: 1253


🏁 Script executed:

rg "useTranslation\|import.*t\(" web/src/components/topup/SubscriptionPlansCard.jsx

Repository: QuantumNous/new-api

Length of output: 45


🏁 Script executed:

rg "import.*useTranslation" web/src/components/topup/SubscriptionPlansCard.jsx

Repository: QuantumNous/new-api

Length of output: 45


🏁 Script executed:

cat -n web/src/components/topup/SubscriptionPlansCard.jsx | sed -n '50,80p'

Repository: QuantumNous/new-api

Length of output: 1093


🏁 Script executed:

cat -n web/src/components/topup/SubscriptionPlansCard.jsx | sed -n '191,207p'

Repository: QuantumNous/new-api

Length of output: 761


🏁 Script executed:

rg "window.open" web/src/components/topup/SubscriptionPlansCard.jsx -B 2 -A 2

Repository: QuantumNous/new-api

Length of output: 550


Guard XunhuPay URL before launch, and handle popup blocking with fallback.

When isXunhupayMethod && res.data.url is false, execution falls into the else branch and calls submitEpayForm with an undefined URL. Additionally, if window.open() is blocked, the popup returns null but no fallback exists. Both cases still trigger showSuccess() and closeBuy(), misleading the user into thinking the payment started. Verify the URL exists, then fall back to same-tab navigation if the popup is blocked.

🛠️ Suggested guard
       if (res.data?.message === 'success') {
-        if (isXunhupayMethod && res.data.url) {
+        if (isXunhupayMethod) {
+          if (!res.data.url) {
+            showError(t('支付失败'));
+            return;
+          }
           // 虎皮椒返回直接跳转链接
           const isSafari =
             navigator.userAgent.indexOf('Safari') > -1 &&
             navigator.userAgent.indexOf('Chrome') < 1;
           if (isSafari) {
             window.location.href = res.data.url;
           } else {
-            window.open(res.data.url, '_blank');
+            const popup = window.open(res.data.url, '_blank');
+            if (!popup) {
+              window.location.href = res.data.url;
+            }
           }
         } else {
           submitEpayForm({ url: res.data.url, params: res.data.data });
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/topup/SubscriptionPlansCard.jsx` around lines 191 - 206,
The code calls submitEpayForm and treats the payment as started even when
res.data.url is missing or window.open() is blocked; update the logic in the
block handling isXunhupayMethod and non-Xunhu flow to first verify res.data.url
exists before calling submitEpayForm, and when using window.open(res.data.url,
'_blank') check the returned window reference (it can be null if blocked) and
fall back to same-tab navigation via window.location.href; only call
showSuccess() and closeBuy() after a guaranteed navigation or successful
submitEpayForm invocation (i.e., when URL is present and either form submitted
or popup opened or fallback navigation executed).

Comment on lines +12 to +14
",当前无生效订阅,将自动使用钱包": ", no active subscription. Wallet will be used automatically.",
",时间:": ",time:",
",点击更新": ", click Update",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove duplicate i18n keys to fix lint errors.

Line 12, Line 13, and Line 14 re-declare keys that already exist at Line 3518-Line 3520, which triggers noDuplicateObjectKeys and can fail CI.

Suggested fix
-    ",当前无生效订阅,将自动使用钱包": ", no active subscription. Wallet will be used automatically.",
-    ",时间:": ",time:",
-    ",点击更新": ", click Update",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
",当前无生效订阅,将自动使用钱包": ", no active subscription. Wallet will be used automatically.",
",时间:": ",time:",
",点击更新": ", click Update",
🧰 Tools
🪛 Biome (2.4.10)

[error] 12-12: The key ,当前无生效订阅,将自动使用钱包 was already declared.

(lint/suspicious/noDuplicateObjectKeys)


[error] 13-13: The key ,时间: was already declared.

(lint/suspicious/noDuplicateObjectKeys)


[error] 14-14: The key ,点击更新 was already declared.

(lint/suspicious/noDuplicateObjectKeys)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/i18n/locales/en.json` around lines 12 - 14, Remove the duplicate i18n
entries by deleting the repeated keys shown in the diff (", no active
subscription. Wallet will be used automatically.", ",time:", ", click Update")
from their duplicate location so only the original definitions remain; locate
the duplicate JSON keys (the Chinese keys ",当前无生效订阅,将自动使用钱包", ",时间:", ",点击更新")
in web/src/i18n/locales/en.json and remove the extra occurrences that conflict
with the originals at lines ~3518-3520 to satisfy the noDuplicateObjectKeys lint
rule.

Comment on lines +14 to +18
",当前无生效订阅,将自动使用钱包": ", aucun abonnement actif, le portefeuille sera utilisé automatiquement.",
",时间:": ", time:",
",点击更新": ", cliquez sur Mettre à jour",
"(支持易支付和虎皮椒接口,默认使用上方服务器地址作为回调地址!)": "(Prise en charge des interfaces Epay et XunhuPay, l'adresse du serveur ci-dessus est utilisée par défaut comme adresse de rappel !)",
"(筛选后显示 {{count}} 条)_one": "(Showing {{count}} item after filtering)",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove duplicate i18n keys to avoid shadowed values and Biome errors.

Line 14-16 and Line 18 redeclare keys that already exist later in this file (Line 3475-3477 and Line 21). This triggers noDuplicateObjectKeys and makes earlier entries ineffective.

🧹 Suggested cleanup
-    ",当前无生效订阅,将自动使用钱包": ", aucun abonnement actif, le portefeuille sera utilisé automatiquement.",
-    ",时间:": ", time:",
-    ",点击更新": ", cliquez sur Mettre à jour",
     "(支持易支付和虎皮椒接口,默认使用上方服务器地址作为回调地址!)": "(Prise en charge des interfaces Epay et XunhuPay, l'adresse du serveur ci-dessus est utilisée par défaut comme adresse de rappel !)",
-    "(筛选后显示 {{count}} 条)_one": "(Showing {{count}} item after filtering)",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
",当前无生效订阅,将自动使用钱包": ", aucun abonnement actif, le portefeuille sera utilisé automatiquement.",
",时间:": ", time:",
",点击更新": ", cliquez sur Mettre à jour",
"(支持易支付和虎皮椒接口,默认使用上方服务器地址作为回调地址!)": "(Prise en charge des interfaces Epay et XunhuPay, l'adresse du serveur ci-dessus est utilisée par défaut comme adresse de rappel !)",
"(筛选后显示 {{count}} 条)_one": "(Showing {{count}} item after filtering)",
"(支持易支付和虎皮椒接口,默认使用上方服务器地址作为回调地址!)": "(Prise en charge des interfaces Epay et XunhuPay, l'adresse du serveur ci-dessus est utilisée par défaut comme adresse de rappel !)",
🧰 Tools
🪛 Biome (2.4.10)

[error] 14-14: The key ,当前无生效订阅,将自动使用钱包 was already declared.

(lint/suspicious/noDuplicateObjectKeys)


[error] 15-15: The key ,时间: was already declared.

(lint/suspicious/noDuplicateObjectKeys)


[error] 16-16: The key ,点击更新 was already declared.

(lint/suspicious/noDuplicateObjectKeys)


[error] 18-18: The key (筛选后显示 {{count}} 条)_one was already declared.

(lint/suspicious/noDuplicateObjectKeys)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/i18n/locales/fr.json` around lines 14 - 18, The file contains
duplicate i18n keys (e.g. the entries for ",当前无生效订阅,将自动使用钱包", ",时间:", ",点击更新"
and "(筛选后显示 {{count}} 条)_one") that shadow later definitions and trigger
noDuplicateObjectKeys errors; remove the earlier duplicate entries (the ones
near the top of the file shown in the diff) or consolidate them with the
canonical definitions later in the file so only one definition remains per key
(ensure you keep the intended French translations in the canonical entries and
delete the redundant top-of-file occurrences).

Comment on lines +12 to +14
",当前无生效订阅,将自动使用钱包": "、有効なサブスクリプションがないため、自動的にウォレットを使用します",
",时间:": "、時間:",
",点击更新": "、クリックして更新してください",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify duplicate i18n keys in this locale file
python - <<'PY'
import re
from collections import defaultdict

path = "web/src/i18n/locales/ja.json"
pat = re.compile(r'^\s*"([^"]+)":')
seen = defaultdict(list)

with open(path, encoding="utf-8") as f:
    for i, line in enumerate(f, 1):
        m = pat.match(line)
        if m:
            seen[m.group(1)].append(i)

dups = {k:v for k,v in seen.items() if len(v) > 1}
if not dups:
    print("No duplicate keys found.")
else:
    print("Duplicate keys found:")
    for k, lines in sorted(dups.items(), key=lambda kv: kv[1][0]):
        print(f"- {k}: lines {lines}")
PY

Repository: QuantumNous/new-api

Length of output: 172


🏁 Script executed:

# Check the file structure around these lines to understand context
head -20 web/src/i18n/locales/ja.json | cat -n

Repository: QuantumNous/new-api

Length of output: 2137


🏁 Script executed:

# Check if Biome lint is configured in the project
fd -i biome.json -o .biome.json -o biome.jsonc 2>/dev/null | head -5

Repository: QuantumNous/new-api

Length of output: 45


🏁 Script executed:

# Check for any git history on this file to understand if this was an accidental merge
git log --oneline -n 10 web/src/i18n/locales/ja.json 2>/dev/null || echo "Git log not available"

Repository: QuantumNous/new-api

Length of output: 118


🏁 Script executed:

# Check if lint/biome config exists elsewhere
find . -name "*biome*" -o -name ".eslintrc*" -o -name "package.json" | grep -E "(biome|eslint|package)" | head -20

Repository: QuantumNous/new-api

Length of output: 138


🏁 Script executed:

# Check lines around 3455-3457 to see the duplicate keys context
sed -n '3450,3460p' web/src/i18n/locales/ja.json | cat -n

Repository: QuantumNous/new-api

Length of output: 471


🏁 Script executed:

# Check total line count of the file
wc -l web/src/i18n/locales/ja.json

Repository: QuantumNous/new-api

Length of output: 96


🏁 Script executed:

# Check the biome.json configuration to see if it enforces the noDuplicateObjectKeys rule
cat biome.json

Repository: QuantumNous/new-api

Length of output: 1322


Remove duplicate locale keys that will fail linter.

Lines 12–14 define keys already present at lines 3455–3457. Biome is configured with "noDuplicateObjectKeys": "error", which will block the build. In JSON, duplicate keys result in only the last value being retained, making the early definitions ineffective.

Proposed fix
-    ",当前无生效订阅,将自动使用钱包": "、有効なサブスクリプションがないため、自動的にウォレットを使用します",
-    ",时间:": "、時間:",
-    ",点击更新": "、クリックして更新してください",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
",当前无生效订阅,将自动使用钱包": "、有効なサブスクリプションがないため、自動的にウォレットを使用します",
",时间:": "、時間:",
",点击更新": "、クリックして更新してください",
🧰 Tools
🪛 Biome (2.4.10)

[error] 12-12: The key ,当前无生效订阅,将自动使用钱包 was already declared.

(lint/suspicious/noDuplicateObjectKeys)


[error] 13-13: The key ,时间: was already declared.

(lint/suspicious/noDuplicateObjectKeys)


[error] 14-14: The key ,点击更新 was already declared.

(lint/suspicious/noDuplicateObjectKeys)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/i18n/locales/ja.json` around lines 12 - 14, Remove the duplicate JSON
keys in ja.json that collide with later definitions: ",当前无生效订阅,将自动使用钱包", ",时间:",
and ",点击更新"; keep the single intended translation (the later entries at lines
3455–3457) and delete the earlier occurrences shown in the diff so the linter
rule noDuplicateObjectKeys no longer errors and the translations are not
shadowed.

Comment on lines +16 to +18
",当前无生效订阅,将自动使用钱包": ", нет активной подписки, автоматически будет использоваться кошелек.",
",时间:": ", время: ",
",点击更新": ", нажмите для обновления",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove the duplicate locale keys from this block.

These entries are already declared later in the same object, so the new copies are ignored at runtime and Biome will fail the file on duplicate keys. If the intent was to update the copy, edit the existing declaration instead of adding another one.

🧹 Minimal fix
-    ",当前无生效订阅,将自动使用钱包": ", нет активной подписки, автоматически будет использоваться кошелек.",
-    ",时间:": ", время: ",
-    ",点击更新": ", нажмите для обновления",
     "(支持易支付和虎皮椒接口,默认使用上方服务器地址作为回调地址!)": "(Поддерживаются интерфейсы YiPay и XunhuPay, по умолчанию используется адрес сервера выше в качестве адреса обратного вызова!)",
-    "(筛选后显示 {{count}} 条)_one": "(Showing {{count}} item after filtering)",

Also applies to: 20-20

🧰 Tools
🪛 Biome (2.4.10)

[error] 16-16: The key ,当前无生效订阅,将自动使用钱包 was already declared.

(lint/suspicious/noDuplicateObjectKeys)


[error] 17-17: The key ,时间: was already declared.

(lint/suspicious/noDuplicateObjectKeys)


[error] 18-18: The key ,点击更新 was already declared.

(lint/suspicious/noDuplicateObjectKeys)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/i18n/locales/ru.json` around lines 16 - 18, Remove the duplicate JSON
keys ",当前无生效订阅,将自动使用钱包", ",时间:", and ",点击更新" from this block and instead update
their single existing declarations later in the same ru.json object; locate the
earlier definitions of those keys and apply any intended text changes there (do
not leave duplicate keys in the file).

Comment on lines +12 to +14
",当前无生效订阅,将自动使用钱包": ", hiện không có gói đăng ký hiệu lực, sẽ tự động dùng ví.",
",时间:": ", thời gian:",
",点击更新": ", nhấn để cập nhật",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify duplicate JSON keys in web/src/i18n/locales/vi.json (read-only)
python - <<'PY'
import re
from collections import Counter
p = "web/src/i18n/locales/vi.json"
with open(p, "r", encoding="utf-8") as f:
    s = f.read()

# Naive key extraction for flat translation map entries
keys = re.findall(r'^\s*"([^"\\]*(?:\\.[^"\\]*)*)"\s*:', s, flags=re.M)
dups = [k for k, c in Counter(keys).items() if c > 1]
print("duplicate_count:", len(dups))
for k in dups:
    print(k)
PY

Repository: QuantumNous/new-api

Length of output: 112


🏁 Script executed:

#!/bin/bash
# Find line numbers for each duplicate key in vi.json
python - <<'PY'
import re

p = "web/src/i18n/locales/vi.json"
with open(p, "r", encoding="utf-8") as f:
    lines = f.readlines()

# Keys identified as duplicates
duplicate_keys = [
    ",当前无生效订阅,将自动使用钱包",
    ",时间:",
    ",点击更新",
    "覆盖"
]

for key in duplicate_keys:
    escaped_key = re.escape(key)
    pattern = rf'^[^}]*"{escaped_key}"\s*:'
    occurrences = []
    for i, line in enumerate(lines, 1):
        if re.search(pattern, line):
            occurrences.append((i, line.strip()))
    
    if occurrences:
        print(f"\nKey: {key}")
        print(f"  Found at {len(occurrences)} locations:")
        for line_num, line_content in occurrences:
            print(f"    Line {line_num}: {line_content[:80]}...")
PY

Repository: QuantumNous/new-api

Length of output: 226


🏁 Script executed:

#!/bin/bash
# Find line numbers for each duplicate key in vi.json
python - <<'PY'
import re

p = "web/src/i18n/locales/vi.json"
with open(p, "r", encoding="utf-8") as f:
    lines = f.readlines()

# Keys identified as duplicates
duplicate_keys = [
    ",当前无生效订阅,将自动使用钱包",
    ",时间:",
    ",点击更新",
    "覆盖"
]

for key in duplicate_keys:
    escaped_key = re.escape(key)
    pattern = '^[^}]*"' + escaped_key + r'"\s*:'
    occurrences = []
    for i, line in enumerate(lines, 1):
        if re.search(pattern, line):
            occurrences.append((i, line.strip()))
    
    if occurrences:
        print("Key: " + key)
        print("  Found at " + str(len(occurrences)) + " locations:")
        for line_num, line_content in occurrences:
            preview = line_content[:80] if len(line_content) > 80 else line_content
            print("    Line " + str(line_num) + ": " + preview + "...")
PY

Repository: QuantumNous/new-api

Length of output: 650


🏁 Script executed:

#!/bin/bash
# Check context around duplicate keys to understand which should be kept
python - <<'PY'
p = "web/src/i18n/locales/vi.json"
with open(p, "r", encoding="utf-8") as f:
    lines = f.readlines()

# Check context around lines 12-14 and 4023-4025
print("=== Context around lines 10-16 ===")
for i in range(9, 16):
    if i < len(lines):
        print(f"Line {i+1}: {lines[i].rstrip()}")

print("\n=== Context around lines 4021-4027 ===")
for i in range(4020, 4027):
    if i < len(lines):
        print(f"Line {i+1}: {lines[i].rstrip()}")

print("\n=== Context around lines 2230-2235 ===")
for i in range(2229, 2235):
    if i < len(lines):
        print(f"Line {i+1}: {lines[i].rstrip()}")

print("\n=== Context around lines 3051-3055 ===")
for i in range(3050, 3055):
    if i < len(lines):
        print(f"Line {i+1}: {lines[i].rstrip()}")
PY

Repository: QuantumNous/new-api

Length of output: 1525


🏁 Script executed:

#!/bin/bash
# Check if i18n linting tool detects these duplicates
cd web
bun run i18n:lint 2>&1 | grep -A 5 -B 5 "vi.json\|duplicate\|noDuplicate" || echo "No direct errors found, checking full lint output..."

Repository: QuantumNous/new-api

Length of output: 115


🏁 Script executed:

#!/bin/bash
# Check what i18n:lint command actually does
cd web
cat package.json | grep -A 5 '"i18n:lint"'

Repository: QuantumNous/new-api

Length of output: 187


🏁 Script executed:

#!/bin/bash
# Run i18next-cli lint with verbose output
cd web
bunx i18next-cli lint 2>&1 | head -100

Repository: QuantumNous/new-api

Length of output: 105


🏁 Script executed:

#!/bin/bash
# Test JSON parsing with duplicate keys
python - <<'PY'
import json

p = "web/src/i18n/locales/vi.json"
try:
    with open(p, "r", encoding="utf-8") as f:
        data = json.load(f)
    print("✓ JSON parsed successfully")
    print("Total keys in root object:", len(data))
    
    # Check if duplicate keys are preserved in parsed object
    test_key = ",当前无生效订阅,将自动使用钱包"
    if test_key in data:
        print(f"✓ Key '{test_key}' found in parsed JSON")
        print(f"  Value: {data[test_key]}")
except json.JSONDecodeError as e:
    print(f"✗ JSON parsing failed: {e}")
PY

Repository: QuantumNous/new-api

Length of output: 118


🏁 Script executed:

#!/bin/bash
# Check JSON structure
python - <<'PY'
import json

p = "web/src/i18n/locales/vi.json"
with open(p, "r", encoding="utf-8") as f:
    data = json.load(f)

print("Root keys:", list(data.keys()))
print("Type of root structure:", type(data))

# If nested, check depth
root_key = list(data.keys())[0]
nested = data[root_key]
print(f"Type of '{root_key}':", type(nested))
if isinstance(nested, dict):
    print(f"Number of keys in '{root_key}':", len(nested))
    print("First 5 keys:", list(nested.keys())[:5])
PY

Repository: QuantumNous/new-api

Length of output: 613


🏁 Script executed:

#!/bin/bash
# Check broader context to understand file sections
python - <<'PY'
import json
import re

p = "web/src/i18n/locales/vi.json"
with open(p, "r", encoding="utf-8") as f:
    lines = f.readlines()

# Find keys that appear before/after each duplicate location
print("=== Context before line 12 (find section header) ===")
for i in range(max(0, 0), 12):
    line = lines[i]
    # Look for keys that might indicate section
    if re.search(r'^\s*"[^"]{20,}": ', line):
        print(f"Line {i+1}: {line.rstrip()}")

print("\n=== Context line 12-20 ===")
for i in range(11, min(20, len(lines))):
    print(f"Line {i+1}: {lines[i].rstrip()}")

print("\n=== Context line 4018-4027 ===")
for i in range(4017, min(4027, len(lines))):
    print(f"Line {i+1}: {lines[i].rstrip()}")
PY

Repository: QuantumNous/new-api

Length of output: 2830


🏁 Script executed:

#!/bin/bash
# Verify which duplicate occurrence is being used at runtime
python - <<'PY'
import json

p = "web/src/i18n/locales/vi.json"
with open(p, "r", encoding="utf-8") as f:
    data = json.load(f)

translation = data["translation"]

# Check which value is actually accessible for duplicate keys
test_keys = [
    ",当前无生效订阅,将自动使用钱包",
    ",时间:",
    ",点击更新",
    "覆盖"
]

print("Runtime values (later keys override earlier ones):")
for key in test_keys:
    if key in translation:
        print(f"  '{key}': {translation[key]}")
    else:
        print(f"  '{key}': NOT FOUND")
PY

Repository: QuantumNous/new-api

Length of output: 261


🏁 Script executed:

#!/bin/bash
# Check if Biome validates JSON files and checks for duplicate keys
cd web
if [ -f "biome.json" ] || [ -f ".biomerc" ]; then
    echo "=== Biome config found ==="
    [ -f "biome.json" ] && cat biome.json || cat .biomerc
else
    echo "=== Checking for biome config in parent ==="
    [ -f "../biome.json" ] && cat ../biome.json || echo "No biome config found"
fi

Repository: QuantumNous/new-api

Length of output: 1366


🏁 Script executed:

#!/bin/bash
# Check if Biome is actually validating JSON files or only JS/TS
cd ..
grep -r "json" biome.json || echo "No explicit JSON config in biome.json"

# Check if the project runs Biome as part of CI/pre-commit
echo ""
echo "=== Checking for lint scripts ==="
grep -r "biome\|lint" .git/hooks 2>/dev/null | head -5 || echo "No git hooks found"

# Look for GitHub Actions or other CI configurations
if [ -f ".github/workflows/ci.yml" ] || [ -f ".github/workflows/lint.yml" ]; then
    echo ""
    echo "=== CI config (checking for linting) ==="
    grep -l "biome\|lint" .github/workflows/*.yml 2>/dev/null | head -3
fi

Repository: QuantumNous/new-api

Length of output: 196


🏁 Script executed:

#!/bin/bash
# Check if Biome validates JSON files and if noDuplicateObjectKeys applies
cd ..
echo "=== Checking Biome file extensions ==="
cat biome.json | grep -A 20 '"json"' || echo "No JSON section in biome"

echo ""
echo "=== Checking if Biome includes JSON files in linting ==="
# Look for how Biome is invoked
grep -r "biome" . --include="*.json" --include="*.yaml" --include="*.yml" 2>/dev/null | grep -v node_modules | grep -v ".git" | head -10

echo ""
echo "=== Test: Does noDuplicateObjectKeys apply to JSON? ==="
# According to Biome docs, noDuplicateObjectKeys applies to JavaScript object literals, not JSON files
echo "noDuplicateObjectKeys is a linting rule for JS/TS object literals, not JSON files"

Repository: QuantumNous/new-api

Length of output: 1234


🏁 Script executed:

#!/bin/bash
# Final verification: Check when these entries were added and if there's a reason
python - <<'PY'
import json

p = "web/src/i18n/locales/vi.json"
with open(p, "r", encoding="utf-8") as f:
    lines = f.readlines()

# Check actual line numbers in the file provided in review
print("=== Review mentions lines 12-14, checking actual content ===")
for i in range(11, 15):
    if i < len(lines):
        print(f"Line {i+1}: {lines[i].rstrip()}")

print("\n=== Duplicate at 4023-4025 ===")
for i in range(4022, 4026):
    if i < len(lines):
        print(f"Line {i+1}: {lines[i].rstrip()}")

# Check if these are in logically grouped sections or scattered randomly
print("\n=== Checking if sections have thematic grouping ===")
# Get keys around line 12
print("\nAround line 12 (keys 10-16):")
with open(p, "r") as f:
    data = json.load(f)
translation = data["translation"]
all_keys = list(translation.keys())

# Find indices of our duplicate keys
idx_key1 = None
for i, k in enumerate(all_keys):
    if k == ",当前无生效订阅,将自动使用钱包":
        idx_key1 = i
        break

if idx_key1:
    print(f"Found at index {idx_key1}")
    print("Nearby keys:")
    for i in range(max(0, idx_key1-2), min(len(all_keys), idx_key1+3)):
        preview = all_keys[i][:60] if len(all_keys[i]) > 60 else all_keys[i]
        print(f"  [{i}] {preview}")
PY

Repository: QuantumNous/new-api

Length of output: 868


Remove duplicate i18n keys to avoid redundant entries.

Lines 12–14 duplicate keys that already exist later in the file (lines 4023–4025). In JSON, when duplicate keys are parsed, the earlier entries are ignored and only the later values are used at runtime, making the early occurrences redundant.

Proposed fix
-    ",当前无生效订阅,将自动使用钱包": ", hiện không có gói đăng ký hiệu lực, sẽ tự động dùng ví.",
-    ",时间:": ", thời gian:",
-    ",点击更新": ", nhấn để cập nhật",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
",当前无生效订阅,将自动使用钱包": ", hiện không có gói đăng ký hiệu lực, sẽ tự động dùng ví.",
",时间:": ", thời gian:",
",点击更新": ", nhấn để cập nhật",
🧰 Tools
🪛 Biome (2.4.10)

[error] 12-12: The key ,当前无生效订阅,将自动使用钱包 was already declared.

(lint/suspicious/noDuplicateObjectKeys)


[error] 13-13: The key ,时间: was already declared.

(lint/suspicious/noDuplicateObjectKeys)


[error] 14-14: The key ,点击更新 was already declared.

(lint/suspicious/noDuplicateObjectKeys)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/i18n/locales/vi.json` around lines 12 - 14, Remove the redundant
early JSON entries for the three duplicate keys ",当前无生效订阅,将自动使用钱包", ",时间:", and
",点击更新" by deleting those initial occurrences so only the later definitions (the
authoritative ones) remain; locate these exact key strings in the vi.json
content and remove the earlier key/value pairs to avoid duplicate-key parsing
behavior and ensure the later values are used at runtime.

Comment on lines +9 to +10
",时间:": ",時間:",
",点击更新": ",點擊更新",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove duplicated i18n keys to fix lint errors.

Line 9 and Line 10 duplicate keys that already exist at Line 3138 and Line 3139, which triggers noDuplicateObjectKeys errors.

🛠️ Proposed fix
-    ",时间:": ",時間:",
-    ",点击更新": ",點擊更新",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
",时间:": ",時間:",
",点击更新": ",點擊更新",
🧰 Tools
🪛 Biome (2.4.10)

[error] 9-9: The key ,时间: was already declared.

(lint/suspicious/noDuplicateObjectKeys)


[error] 10-10: The key ,点击更新 was already declared.

(lint/suspicious/noDuplicateObjectKeys)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/i18n/locales/zh-TW.json` around lines 9 - 10, Remove the duplicated
i18n keys ",时间:" and ",点击更新" from the zh-TW locale file by deleting the
duplicate entries found in the diff so only the original occurrences remain (the
translations already present elsewhere should be preserved); ensure there is
exactly one key/value pair for ",时间:" and one for ",点击更新" in the file and re-run
the linter to confirm the noDuplicateObjectKeys error is resolved.

@Xbang0222

Copy link
Copy Markdown
Contributor

虎皮椒易支付可以接入的

@hamwong233

Copy link
Copy Markdown

虎皮椒易支付可以接入的

老哥,支付地址是填 https://api.xunhupay.com 吗,我点击充值会404

@chenzhongd

Copy link
Copy Markdown
Author

虎皮椒易支付可以接入的

老哥,支付地址是填 https://api.xunhupay.com 吗,我点击充值会404

按照这样填写就行
image

@hamwong233

Copy link
Copy Markdown

虎皮椒易支付可以接入的

老哥,支付地址是填 https://api.xunhupay.com 吗,我点击充值会404

按照这样填写就行 image

谢谢,但是还没合并进来😂,更新不上

@chenzhongd
chenzhongd deleted the feat/xunhupay-payment branch April 20, 2026 01:53
@chenzhongd
chenzhongd restored the feat/xunhupay-payment branch April 20, 2026 01:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants