feat: add XunhuPay (虎皮椒) payment gateway integration - #4220
Conversation
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
WalkthroughAdds 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
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (4)
.gitignore (1)
15-15: Unrelated change in this PR.The addition of
new-api-localto.gitignoreappears 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
returnUrlandnotifyUrlare 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 missingcolorfield.The existing
PayMethodsinsetting/operation_setting/payment_setting_old.goinclude acolorfield for each method (e.g.,"rgba(var(--semi-blue-5), 1)"for alipay). The injected methods here only includename,type, andmin_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
📒 Files selected for processing (20)
.gitignorecontroller/subscription_payment_xunhupay.gocontroller/topup.gocontroller/topup_xunhupay.gomodel/option.gorouter/api-router.gosetting/operation_setting/xunhupay_setting.goweb/src/components/settings/PaymentSetting.jsxweb/src/components/topup/RechargeCard.jsxweb/src/components/topup/SubscriptionPlansCard.jsxweb/src/components/topup/index.jsxweb/src/i18n/locales/en.jsonweb/src/i18n/locales/fr.jsonweb/src/i18n/locales/ja.jsonweb/src/i18n/locales/ru.jsonweb/src/i18n/locales/vi.jsonweb/src/i18n/locales/zh-CN.jsonweb/src/i18n/locales/zh-TW.jsonweb/src/pages/Setting/Payment/SettingsPaymentGateway.jsxweb/src/pages/Setting/Payment/SettingsPaymentGatewayXunhu.jsx
| 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)) | ||
| } |
There was a problem hiding this comment.
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 !)", |
There was a problem hiding this comment.
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.
| "(支持易支付和虎皮椒接口,默认使用上方服务器地址作为回调地址!)": "(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.
| if (options.length === 0) { | ||
| showError(t('没有需要更新的内容')); | ||
| setLoading(false); | ||
| return; | ||
| } |
There was a problem hiding this comment.
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.
| description={`异步回调地址(notify_url):${ | ||
| props.options.CustomCallbackAddress || | ||
| (props.options.ServerAddress | ||
| ? removeTrailingSlash(props.options.ServerAddress) | ||
| : t('网站地址')) | ||
| }/api/user/xunhupay/notify`} |
There was a problem hiding this comment.
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.
| 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.
There was a problem hiding this comment.
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
📒 Files selected for processing (11)
.gitignoremodel/option.gorouter/api-router.goweb/src/components/topup/SubscriptionPlansCard.jsxweb/src/i18n/locales/en.jsonweb/src/i18n/locales/fr.jsonweb/src/i18n/locales/ja.jsonweb/src/i18n/locales/ru.jsonweb/src/i18n/locales/vi.jsonweb/src/i18n/locales/zh-CN.jsonweb/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
| 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(); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
fd -t f "SubscriptionPlansCard.jsx" web/srcRepository: 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 1Repository: QuantumNous/new-api
Length of output: 1253
🏁 Script executed:
rg "useTranslation\|import.*t\(" web/src/components/topup/SubscriptionPlansCard.jsxRepository: QuantumNous/new-api
Length of output: 45
🏁 Script executed:
rg "import.*useTranslation" web/src/components/topup/SubscriptionPlansCard.jsxRepository: 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 2Repository: 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).
| ",当前无生效订阅,将自动使用钱包": ", no active subscription. Wallet will be used automatically.", | ||
| ",时间:": ",time:", | ||
| ",点击更新": ", click Update", |
There was a problem hiding this comment.
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.
| ",当前无生效订阅,将自动使用钱包": ", 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.
| ",当前无生效订阅,将自动使用钱包": ", 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)", |
There was a problem hiding this comment.
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.
| ",当前无生效订阅,将自动使用钱包": ", 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).
| ",当前无生效订阅,将自动使用钱包": "、有効なサブスクリプションがないため、自動的にウォレットを使用します", | ||
| ",时间:": "、時間:", | ||
| ",点击更新": "、クリックして更新してください", |
There was a problem hiding this comment.
🧩 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}")
PYRepository: 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 -nRepository: 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 -5Repository: 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 -20Repository: 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 -nRepository: QuantumNous/new-api
Length of output: 471
🏁 Script executed:
# Check total line count of the file
wc -l web/src/i18n/locales/ja.jsonRepository: QuantumNous/new-api
Length of output: 96
🏁 Script executed:
# Check the biome.json configuration to see if it enforces the noDuplicateObjectKeys rule
cat biome.jsonRepository: 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.
| ",当前无生效订阅,将自动使用钱包": "、有効なサブスクリプションがないため、自動的にウォレットを使用します", | |
| ",时间:": "、時間:", | |
| ",点击更新": "、クリックして更新してください", |
🧰 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.
| ",当前无生效订阅,将自动使用钱包": ", нет активной подписки, автоматически будет использоваться кошелек.", | ||
| ",时间:": ", время: ", | ||
| ",点击更新": ", нажмите для обновления", |
There was a problem hiding this comment.
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).
| ",当前无生效订阅,将自动使用钱包": ", 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", |
There was a problem hiding this comment.
🧩 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)
PYRepository: 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]}...")
PYRepository: 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 + "...")
PYRepository: 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()}")
PYRepository: 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 -100Repository: 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}")
PYRepository: 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])
PYRepository: 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()}")
PYRepository: 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")
PYRepository: 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"
fiRepository: 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
fiRepository: 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}")
PYRepository: 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.
| ",当前无生效订阅,将自动使用钱包": ", 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.
| ",时间:": ",時間:", | ||
| ",点击更新": ",點擊更新", |
There was a problem hiding this comment.
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.
| ",时间:": ",時間:", | |
| ",点击更新": ",點擊更新", |
🧰 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.
|
虎皮椒易支付可以接入的 |
老哥,支付地址是填 https://api.xunhupay.com 吗,我点击充值会404 |
|
谢谢,但是还没合并进来😂,更新不上 |


新增虎皮椒(XunhuPay)支付网关支持,允许个人开发者通过 xunhupay.com 无需营业执照即可接入微信支付和支付宝,覆盖充值(top-up)和订阅(subscription)两个场景,稳定可靠且费率清晰,更加适合个人或者小型团队开发。
核心逻辑:
生效条件: 管理员在「支付设置」填写 AppID、AppSecret、网关地址后,充值页面的微信/支付宝按钮即自动切换为虎皮椒渠道。
📝 变更描述 / Description
(简述:做了什么?为什么这样改能生效?请基于你对代码逻辑的理解来写,避免粘贴未经整理的内容)
🚀 变更类型 / Type of change
🔗 关联任务 / Related Issue
✅ 提交前检查项 / Checklist
Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。📸 运行证明 / Proof of Work
(请在此粘贴截图、关键日志或测试报告,以证明变更生效)
Summary by CodeRabbit
New Features
Bug Fixes / Improvements
Chores
Style