Skip to content

Codex/alipay official payment - #5285

Closed
yuansirrrrr wants to merge 7 commits into
QuantumNous:mainfrom
yuansirrrrr:codex/alipay-official-payment
Closed

Codex/alipay official payment#5285
yuansirrrrr wants to merge 7 commits into
QuantumNous:mainfrom
yuansirrrrr:codex/alipay-official-payment

Conversation

@yuansirrrrr

@yuansirrrrr yuansirrrrr commented Jun 3, 2026

Copy link
Copy Markdown

⚠️ 提交说明 / PR Notice

Important

  • 请提供人工撰写的简洁摘要,避免直接粘贴未经整理的 AI 输出。

📝 变更描述 / Description

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

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix) - 请关联对应 Issue,避免将设计取舍、理解偏差或预期不一致直接归类为 bug
  • ✨ 新功能 (New feature) - 重大特性建议先通过 Issue 沟通
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

  • Closes # (如有)

✅ 提交前检查项 / Checklist

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

📸 运行证明 / Proof of Work

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

Summary by CodeRabbit

Release Notes

New Features

  • Added Alipay official payment method for account top-ups
  • Added ZLHub Video channel type support
  • Users' language preferences now persist after login
  • Docker configuration now supports custom package mirrors and registries

Chores

  • Updated application port from 3000 to 3004
  • Changed default theme from classic to default

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR implements Alipay official payment top-ups, adds ZLHub Video channel support with configurable task adapter paths, updates Docker build configuration for registry/mirror support, improves user language persistence, and provides multilingual UI translations. The changes span backend payment processing (RSA2 signing/verification, webhook handling), relay task adapter enhancements (envelope response parsing, configurable paths), frontend payment flow integration, comprehensive system settings UI, and Docker build flexibility.

Changes

Alipay Official Payment Integration

Layer / File(s) Summary
Payment constants and configuration
model/topup.go, setting/payment_alipay.go, model/option.go
Define PaymentMethodAlipayOfficial and PaymentProviderAlipay constants, declare Alipay gateway/key settings variables, and wire Alipay option keys into the configuration system with defaults.
Alipay RSA signing and verification service
service/alipay.go
Implement RSA2 page-pay request construction with signed parameters, gateway resolution, signature verification against callback parameters, and PKCS#8/PKIX key parsing for private and public RSA keys.
Webhook availability and compliance validation
controller/payment_webhook_availability.go, controller/payment_webhook_availability_test.go
Validate Alipay top-up enablement by checking payment compliance and credential presence; test webhook readiness under various credential scenarios.
Top-up request and webhook handlers
controller/topup_alipay.go, controller/topup.go, router/api-router.go
Implement Alipay pay-request handler validating feature enablement, building signed parameters, persisting pending records; webhook handler verifying signatures, updating status on success, crediting quota, and recording logs.
Frontend API, types, and payment flow
web/default/src/features/wallet/api.ts, web/default/src/features/wallet/types.ts, web/default/src/features/wallet/hooks/use-payment.ts, web/default/src/features/wallet/components/*, web/default/src/features/wallet/lib/payment.ts, web/default/src/features/wallet/lib/billing.ts, web/default/src/features/wallet/lib/ui.tsx, web/default/src/features/wallet/constants.ts
Define Alipay payment type constant with icon color, introduce Alipay payment API function, detect Alipay payment method in checkout flow, manage top-up enablement in recharge form, exclude Alipay from subscription plans, and provide payment method name/icon resolution.
System settings UI and validation
web/default/src/features/system-settings/billing/*, web/default/src/features/system-settings/integrations/payment-settings-section.tsx, web/default/src/features/system-settings/types.ts
Extend BillingSettings type with Alipay fields, add Alipay defaults to settings initialization, implement validation schema, dirty-checking, and selective credential persistence, render Alipay gateway configuration UI section with webhook guidance.
Multilingual translations
web/default/src/i18n/locales/{en,fr,ja,ru,vi,zh}.json
Add translation keys and values for Alipay configuration (app ID, gateway, Open Platform, private/public keys, callback verification) across six language locales.

ZLHub Video Channel Support

Layer / File(s) Summary
Channel type constants and adapter configuration
constant/channel.go, relay/channel/task/doubao/constants.go
Define ChannelTypeZLHubVideo constant and metadata, register channel base URL and type name, add ZLHub-specific model list and channel name exports, define path constants for official and ZLHub task endpoints.
Relay task adapter with configurable paths and response parsing
relay/channel/task/doubao/adaptor.go
Extend TaskAdaptor to support configurable creation/query paths and model lists via factory constructors, implement response envelope parsing for direct or nested payloads, add safe URL path escaping, update metadata accessors to return adaptor configuration.
Relay adapter test coverage
relay/channel/task/doubao/adaptor_test.go
Test request URL construction for official vs ZLHub paths, validate ZLHub fetch endpoint and Bearer authorization, verify adaptor metadata, ensure envelope response parsing for payload extraction and OpenAI video conversion.
Relay routing and backend configuration
relay/relay_adaptor.go, controller/channel-test.go, relay/channel/task/doubao/constants.go
Update GetTaskAdaptor to instantiate adaptor via factory methods and add ZLHubVideo case, register ZLHubVideo in unsupported test list, extend video input ratio map for seedance 2.0 model variants.
Frontend channel UI and configuration
web/classic/src/constants/channel.constants.js, web/classic/src/helpers/render.jsx, web/default/src/features/channels/constants.ts, web/default/src/features/channels/lib/channel-type-config.ts, web/default/src/features/channels/lib/channel-utils.ts, web/default/src/i18n/locales/en.json
Add ZLHubVideo channel option with color and icon mapping, register channel type with i18n key and display ordering, define configuration with Doubao icon and ZLHub base URL, add translation key.

Build Infrastructure and UI Polish

Layer / File(s) Summary
Docker build registry and mirror configuration
Dockerfile, Dockerfile.dev, docker-compose.dev.yml, docker-compose.yml
Parameterize Bun registry and Go module proxy, add Debian mirror support with apt retry/timeout settings, update docker-compose to build from local context with new image tag, expose port 3004, update health check and documentation URLs.
User language persistence and synchronization
web/default/src/components/language-switcher.tsx, web/default/src/main.tsx, web/default/scripts/sync-i18n.mjs
Introduce parseUserSetting helper for safe settings deserialization, extend language-switcher to persist changes via setUser with merged settings, add UserLanguageSync component to sync saved user language with i18next, mark ZLHubVideo as literal key in i18n sync.
Theme default change
setting/system_setting/theme.go
Update default frontend theme from classic to default in initialization.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • QuantumNous/new-api#4876: Modifies language-switcher and language synchronization behavior with different logic for deriving/normalizing interface language.
  • QuantumNous/new-api#3293: Updates controller/topup.go GetTopUpInfo to conditionally expose gateway-specific top-up methods alongside Alipay Official support.
  • QuantumNous/new-api#1976: Introduces top-up payment method field and billing history tracking, sharing model/topup.go and top-up payment-method representation with this PR.

Suggested reviewers

  • Calcium-Ion
  • seefs001
  • creamlike1024

Poem

🐰 A rabbit hops through payment streams,
Where Alipay signs dreams with RSA beams,
While ZLHub channels dance and sway,
Docker mirrors light the way—
All mirrors built, in three cohorts they play! 🎨✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.52% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the primary change: adding official Alipay payment support. This reflects a significant feature addition evidenced by the changeset covering Alipay configuration, payment handlers, service integration, and UI components.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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: 7

🧹 Nitpick comments (2)
web/default/src/main.tsx (1)

115-142: ⚡ Quick win

Duplicated user.setting parsing logic.

getUserSavedLanguage re-implements the same string/object setting parsing that parseUserSetting does in web/default/src/components/language-switcher.tsx. Consider extracting a shared helper (e.g., in src/lib/) so the two stay in sync, especially given the field-priority coupling noted on the LanguageSwitcher setUser call.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/main.tsx` around lines 115 - 142, getUserSavedLanguage
duplicates the string/object parsing logic that's already in parseUserSetting
(used by LanguageSwitcher.setUser); extract the shared parsing into a single
helper (e.g., src/lib/parseUserSettingOrLanguage.ts) and have both
getUserSavedLanguage and parseUserSetting call that helper so the field-priority
coupling and parsing behavior remain consistent across getUserSavedLanguage,
parseUserSetting, and LanguageSwitcher.setUser.
controller/topup.go (1)

108-113: 💤 Low value

Consider using a distinct color for Alipay Official.

The current color rgba(var(--semi-blue-5), 1) is already used by Waffo (line 91). Using a different color would improve visual distinction between payment methods in the UI.

🎨 Suggested alternative colors
     payMethods = append(payMethods, map[string]string{
       "name":      "Alipay Official",
       "type":      model.PaymentMethodAlipayOfficial,
-      "color":     "rgba(var(--semi-blue-5), 1)",
+      "color":     "rgba(var(--semi-cyan-5), 1)",
       "min_topup": strconv.Itoa(operation_setting.MinTopUp),
     })

Alternative suggestions: --semi-teal-5, --semi-indigo-5, or --semi-light-blue-5

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controller/topup.go` around lines 108 - 113, The Alipay Official entry
appended to payMethods currently reuses the blue color
("rgba(var(--semi-blue-5), 1)") used by Waffo; update the color value for the
map added where payMethods is appended (look for the block that constructs the
map with "name":"Alipay Official" and "type": model.PaymentMethodAlipayOfficial)
to a distinct CSS variable such as "rgba(var(--semi-teal-5), 1)" (or
"--semi-indigo-5"/"--semi-light-blue-5") so Alipay is visually distinct; keep
the rest of the map (including "min_topup":
strconv.Itoa(operation_setting.MinTopUp)) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@controller/topup_alipay.go`:
- Line 138: The warning currently logs the full Alipay callback params
(logger.LogWarn call in topup_alipay.go) which may contain sensitive data;
update the LogWarn invocations (the one at signature verification and the
similar calls around the other occurrences) to extract and log only minimal
identifiers such as params["trade_no"] and params["trade_status"] and, if
necessary, a short masked summary (e.g., mask all other fields or replace with
"[REDACTED]") instead of using common.GetJsonString(params); ensure you still
include context like request URI and client IP but remove or redact full payload
content when constructing the log message.
- Around line 171-187: The code persists topUp.Status = TopUpStatusSuccess via
topUp.Update() before calling model.IncreaseUserQuota, which can leave the order
marked success if quota credit fails; instead perform both the conditional
pending→success update and the quota increment inside a single DB transaction:
open a transaction, run an UPDATE on the topup row (or call a transactional
method) that sets Status and CompleteTime only where Status ==
TopUpStatusPending and check affected rows > 0, then call
model.IncreaseUserQuota (or a new IncreaseUserQuotaTx) using the same
transaction; if any step fails rollback and return "fail", otherwise commit and
return success. Ensure to reference topUp.Update, topUp.Status/CompleteTime, and
model.IncreaseUserQuota (or implement a transactional variant) so the transition
+ quota credit are atomic.

In `@docker-compose.dev.yml`:
- Line 26: The docker-compose port change exposes the backend on host port 3004
but the dev proxy (web/default/rsbuild.config.ts) and the comment still assume
http://localhost:3000, breaking the dev proxy flow; fix by either reverting the
docker-compose mapping back to "3000:3000" so the container publishes to host
3000, or update the default serverUrl in rsbuild.config.ts (and the comment on
Line 6) to use http://localhost:3004 so the dev server proxies to the new host
port consistently; ensure the chosen option updates both the docker-compose port
mapping ("3000:3000" vs "3004:3000") or the serverUrl/default comment in
rsbuild.config.ts accordingly.

In `@relay/channel/task/doubao/adaptor.go`:
- Around line 366-393: The current parseResponsePayload and parseResponseTask
can mask real deserialization errors because they unmarshal twice without
validating which shape the raw JSON actually is; change the logic to first
inspect the raw JSON for a discriminator (e.g., check for the presence of keys
like "data" or "code" by unmarshaling into map[string]json.RawMessage or a small
struct) and only then unmarshal into responsePayload/responseTask or
responsePayloadEnvelope/responseTaskEnvelope accordingly, and if the first
unmarshal returned an error return it immediately instead of falling through;
specifically update parseResponsePayload, parseResponseTask to detect envelope
vs direct object before attempting the second Unmarshal so errors from the
appropriate attempt are propagated and not masked.

In
`@web/default/src/features/system-settings/integrations/payment-settings-section.tsx`:
- Around line 410-420: The sanitized object literal in
payment-settings-section.tsx has inconsistent indentation (some properties use 4
spaces vs. the file's 6-space style) which breaks formatting; update the
properties inside the sanitized object (keys like PayAddress, EpayId, EpayKey,
AlipayAppId, AlipayGateway, AlipayPrivateKey, AlipayPublicKey, Price, MinTopUp,
CustomCallbackAddress) to use the same 6-space indentation as the surrounding
code so the object aligns consistently with the rest of the file.

In `@web/default/src/i18n/locales/fr.json`:
- Around line 255-257: Several French localization entries for Alipay are
missing accents/apostrophes; update the value strings for keys "Alipay private
key", "Alipay public key", and "Alipay public key used to verify callbacks" to
proper French spelling/diacritics (e.g., "Clé privée Alipay", "Clé publique
Alipay", "Clé publique Alipay utilisée pour vérifier les callbacks") and review
the other similar keys mentioned in the comment to apply consistent corrections
(fix accents, apostrophes, and common words like "attribué", "intégration", "par
défaut").

In `@web/default/src/i18n/locales/vi.json`:
- Around line 251-257: The new Alipay entries use ASCII-only transliterations;
update the JSON values for the keys "Alipay App ID", "Alipay gateway", "Alipay
Gateway", "Alipay Open Platform", "Alipay private key", "Alipay public key", and
"Alipay public key used to verify callbacks" to proper Vietnamese with
diacritics (e.g., "Alipay App ID" → "ID ứng dụng Alipay", "Alipay gateway" /
"Alipay Gateway" → "Cổng Alipay" or "Cổng thanh toán Alipay", "Alipay Open
Platform" → "Nền tảng mở Alipay", "Alipay private key" → "Khóa riêng Alipay",
"Alipay public key" → "Khóa công khai Alipay", "Alipay public key used to verify
callbacks" → "Khóa công khai Alipay dùng để xác minh callback"); apply the same
diacritic corrections to the other keyed entries noted in the comment (those at
the other indices) so all Vietnamese translations use proper accents and match
the locale's style.

---

Nitpick comments:
In `@controller/topup.go`:
- Around line 108-113: The Alipay Official entry appended to payMethods
currently reuses the blue color ("rgba(var(--semi-blue-5), 1)") used by Waffo;
update the color value for the map added where payMethods is appended (look for
the block that constructs the map with "name":"Alipay Official" and "type":
model.PaymentMethodAlipayOfficial) to a distinct CSS variable such as
"rgba(var(--semi-teal-5), 1)" (or "--semi-indigo-5"/"--semi-light-blue-5") so
Alipay is visually distinct; keep the rest of the map (including "min_topup":
strconv.Itoa(operation_setting.MinTopUp)) unchanged.

In `@web/default/src/main.tsx`:
- Around line 115-142: getUserSavedLanguage duplicates the string/object parsing
logic that's already in parseUserSetting (used by LanguageSwitcher.setUser);
extract the shared parsing into a single helper (e.g.,
src/lib/parseUserSettingOrLanguage.ts) and have both getUserSavedLanguage and
parseUserSetting call that helper so the field-priority coupling and parsing
behavior remain consistent across getUserSavedLanguage, parseUserSetting, and
LanguageSwitcher.setUser.
🪄 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: 5cdbadc1-6c95-4941-a33f-41bba55a77bd

📥 Commits

Reviewing files that changed from the base of the PR and between b0ac042 and 98a0bd0.

📒 Files selected for processing (47)
  • Dockerfile
  • Dockerfile.dev
  • constant/channel.go
  • controller/channel-test.go
  • controller/payment_webhook_availability.go
  • controller/payment_webhook_availability_test.go
  • controller/topup.go
  • controller/topup_alipay.go
  • docker-compose.dev.yml
  • docker-compose.yml
  • model/option.go
  • model/topup.go
  • relay/channel/task/doubao/adaptor.go
  • relay/channel/task/doubao/adaptor_test.go
  • relay/channel/task/doubao/constants.go
  • relay/relay_adaptor.go
  • router/api-router.go
  • service/alipay.go
  • setting/payment_alipay.go
  • setting/system_setting/theme.go
  • web/classic/src/constants/channel.constants.js
  • web/classic/src/helpers/render.jsx
  • web/default/scripts/sync-i18n.mjs
  • web/default/src/components/language-switcher.tsx
  • web/default/src/features/channels/constants.ts
  • web/default/src/features/channels/lib/channel-type-config.ts
  • web/default/src/features/channels/lib/channel-utils.ts
  • web/default/src/features/system-settings/billing/index.tsx
  • web/default/src/features/system-settings/billing/section-registry.tsx
  • web/default/src/features/system-settings/integrations/payment-settings-section.tsx
  • web/default/src/features/system-settings/types.ts
  • web/default/src/features/wallet/api.ts
  • web/default/src/features/wallet/components/recharge-form-card.tsx
  • web/default/src/features/wallet/components/subscription-plans-card.tsx
  • web/default/src/features/wallet/constants.ts
  • web/default/src/features/wallet/hooks/use-payment.ts
  • web/default/src/features/wallet/lib/billing.ts
  • web/default/src/features/wallet/lib/payment.ts
  • web/default/src/features/wallet/lib/ui.tsx
  • web/default/src/features/wallet/types.ts
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/fr.json
  • web/default/src/i18n/locales/ja.json
  • web/default/src/i18n/locales/ru.json
  • web/default/src/i18n/locales/vi.json
  • web/default/src/i18n/locales/zh.json
  • web/default/src/main.tsx

}

if err := service.VerifyAlipayParams(params); err != nil {
logger.LogWarn(c.Request.Context(), fmt.Sprintf("Alipay webhook signature verification failed path=%q client_ip=%s error=%q params=%q", c.Request.RequestURI, c.ClientIP(), err.Error(), common.GetJsonString(params)))

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 | ⚡ Quick win

Avoid logging full Alipay callback params.

These logs serialize the entire webhook payload, which can include sensitive user/payment metadata. Log only minimal identifiers (e.g., trade_no, trade_status) and redact/mask the rest.

Minimal redaction example
- logger.LogWarn(..., common.GetJsonString(params)))
+ safe := map[string]string{
+   "out_trade_no": strings.TrimSpace(params["out_trade_no"]),
+   "trade_status": strings.TrimSpace(params["trade_status"]),
+ }
+ logger.LogWarn(..., common.GetJsonString(safe)))

Also applies to: 151-151, 161-161

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controller/topup_alipay.go` at line 138, The warning currently logs the full
Alipay callback params (logger.LogWarn call in topup_alipay.go) which may
contain sensitive data; update the LogWarn invocations (the one at signature
verification and the similar calls around the other occurrences) to extract and
log only minimal identifiers such as params["trade_no"] and
params["trade_status"] and, if necessary, a short masked summary (e.g., mask all
other fields or replace with "[REDACTED]") instead of using
common.GetJsonString(params); ensure you still include context like request URI
and client IP but remove or redact full payload content when constructing the
log message.

Comment on lines +171 to +187
if topUp.Status == common.TopUpStatusPending {
topUp.Status = common.TopUpStatusSuccess
topUp.CompleteTime = common.GetTimestamp()
if err := topUp.Update(); err != nil {
logger.LogError(c.Request.Context(), fmt.Sprintf("Alipay update topup order failed trade_no=%s user_id=%d client_ip=%s error=%q topup=%q", topUp.TradeNo, topUp.UserId, c.ClientIP(), err.Error(), common.GetJsonString(topUp)))
_, _ = c.Writer.Write([]byte("fail"))
return
}

dAmount := decimal.NewFromInt(int64(topUp.Amount))
dQuotaPerUnit := decimal.NewFromFloat(common.QuotaPerUnit)
quotaToAdd := int(dAmount.Mul(dQuotaPerUnit).IntPart())
if err := model.IncreaseUserQuota(topUp.UserId, quotaToAdd, true); err != nil {
logger.LogError(c.Request.Context(), fmt.Sprintf("Alipay increase user quota failed trade_no=%s user_id=%d client_ip=%s quota_to_add=%d error=%q topup=%q", topUp.TradeNo, topUp.UserId, c.ClientIP(), quotaToAdd, err.Error(), common.GetJsonString(topUp)))
_, _ = c.Writer.Write([]byte("fail"))
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 | 🔴 Critical | 🏗️ Heavy lift

Make order state transition and quota credit atomic.

topUp.Status is persisted before quota credit. If quota update fails, the order remains success and later retries won’t credit the user. This can permanently lose funds/credits. Use a single DB transaction with a conditional pending→success update and quota increment in the same transaction.

Suggested direction
- if topUp.Status == common.TopUpStatusPending {
-   topUp.Status = common.TopUpStatusSuccess
-   topUp.CompleteTime = common.GetTimestamp()
-   if err := topUp.Update(); err != nil { ... }
-   ...
-   if err := model.IncreaseUserQuota(topUp.UserId, quotaToAdd, true); err != nil { ... }
- }
+ // Use a transactional model method that:
+ // 1) locks/conditionally updates pending->success
+ // 2) increments user quota
+ // 3) commits or rolls back as one unit
+ // 4) returns idempotent success when already completed
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controller/topup_alipay.go` around lines 171 - 187, The code persists
topUp.Status = TopUpStatusSuccess via topUp.Update() before calling
model.IncreaseUserQuota, which can leave the order marked success if quota
credit fails; instead perform both the conditional pending→success update and
the quota increment inside a single DB transaction: open a transaction, run an
UPDATE on the topup row (or call a transactional method) that sets Status and
CompleteTime only where Status == TopUpStatusPending and check affected rows >
0, then call model.IncreaseUserQuota (or a new IncreaseUserQuotaTx) using the
same transaction; if any step fails rollback and return "fail", otherwise commit
and return success. Ensure to reference topUp.Update, topUp.Status/CompleteTime,
and model.IncreaseUserQuota (or implement a transactional variant) so the
transition + quota credit are atomic.

Comment thread docker-compose.dev.yml
restart: unless-stopped
ports:
- "3000:3000"
- "3004:3000"

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 | ⚡ Quick win

Port change breaks the documented dev proxy flow.

The backend container is now published on host port 3004, but the Rsbuild dev server proxies /api, /mj, /pg to serverUrl which defaults to http://localhost:3000 (web/default/rsbuild.config.ts), and the comment on Line 6 still states "API auto-proxied to :3000". After this change, API requests from the dev server (run on host via bun run dev) will hit localhost:3000 where nothing is listening, so the dev workflow breaks unless the user manually sets VITE_REACT_APP_SERVER_URL=http://localhost:3004.

Either keep the dev mapping at "3000:3000", or update the proxy default and the Line 6 comment to :3004 consistently.

Proposed fix (keep dev backend on :3000)
     ports:
-      - "3004:3000"
+      - "3000:3000"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docker-compose.dev.yml` at line 26, The docker-compose port change exposes
the backend on host port 3004 but the dev proxy (web/default/rsbuild.config.ts)
and the comment still assume http://localhost:3000, breaking the dev proxy flow;
fix by either reverting the docker-compose mapping back to "3000:3000" so the
container publishes to host 3000, or update the default serverUrl in
rsbuild.config.ts (and the comment on Line 6) to use http://localhost:3004 so
the dev server proxies to the new host port consistently; ensure the chosen
option updates both the docker-compose port mapping ("3000:3000" vs "3004:3000")
or the serverUrl/default comment in rsbuild.config.ts accordingly.

Comment on lines +366 to +393
func parseResponsePayload(respBody []byte) (responsePayload, error) {
var payload responsePayload
if err := common.Unmarshal(respBody, &payload); err != nil {
return responsePayload{}, err
}
if payload.ID != "" {
return payload, nil
}

var envelope responsePayloadEnvelope
if err := common.Unmarshal(respBody, &envelope); err != nil {
return responsePayload{}, err
}
return envelope.Data, nil
}

func parseResponseTask(respBody []byte) (responseTask, error) {
var envelope responseTaskEnvelope
if err := common.Unmarshal(respBody, &envelope); err == nil && envelope.Data.ID != "" {
return envelope.Data, nil
}

var task responseTask
if err := common.Unmarshal(respBody, &task); err != nil {
return responseTask{}, err
}
return task, nil
}

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 | ⚡ Quick win

Envelope parsing may mask deserialization errors.

The parsing functions try multiple response formats but might mask actual errors. For example, in parseResponsePayload, if the first Unmarshal succeeds but returns an empty ID due to a malformed response (not because it's an envelope), the second unmarshal is attempted and might incorrectly succeed or fail with a misleading error.

Consider checking the error from the first unmarshal more carefully, or adding a discriminator field check (e.g., presence of "code" or "data" keys in envelope) to determine format before unmarshaling.

🛡️ Proposed improvement with discriminator check
 func parseResponsePayload(respBody []byte) (responsePayload, error) {
+	// Quick check for envelope format by looking for "data" field
+	if bytes.Contains(respBody, []byte(`"data"`)) {
+		var envelope responsePayloadEnvelope
+		if err := common.Unmarshal(respBody, &envelope); err == nil && envelope.Data.ID != "" {
+			return envelope.Data, nil
+		}
+	}
+
 	var payload responsePayload
 	if err := common.Unmarshal(respBody, &payload); err != nil {
 		return responsePayload{}, err
 	}
-	if payload.ID != "" {
-		return payload, nil
-	}
-
-	var envelope responsePayloadEnvelope
-	if err := common.Unmarshal(respBody, &envelope); err != nil {
-		return responsePayload{}, err
-	}
-	return envelope.Data, nil
+	return payload, nil
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@relay/channel/task/doubao/adaptor.go` around lines 366 - 393, The current
parseResponsePayload and parseResponseTask can mask real deserialization errors
because they unmarshal twice without validating which shape the raw JSON
actually is; change the logic to first inspect the raw JSON for a discriminator
(e.g., check for the presence of keys like "data" or "code" by unmarshaling into
map[string]json.RawMessage or a small struct) and only then unmarshal into
responsePayload/responseTask or responsePayloadEnvelope/responseTaskEnvelope
accordingly, and if the first unmarshal returned an error return it immediately
instead of falling through; specifically update parseResponsePayload,
parseResponseTask to detect envelope vs direct object before attempting the
second Unmarshal so errors from the appropriate attempt are propagated and not
masked.

Comment on lines 410 to +420
const sanitized = {
PayAddress: removeTrailingSlash(values.PayAddress),
EpayId: values.EpayId.trim(),
EpayKey: values.EpayKey.trim(),
Price: values.Price,
MinTopUp: values.MinTopUp,
CustomCallbackAddress: removeTrailingSlash(values.CustomCallbackAddress),
EpayId: values.EpayId.trim(),
EpayKey: values.EpayKey.trim(),
AlipayAppId: values.AlipayAppId.trim(),
AlipayGateway: removeTrailingSlash(values.AlipayGateway.trim()),
AlipayPrivateKey: values.AlipayPrivateKey.trim(),
AlipayPublicKey: values.AlipayPublicKey.trim(),
Price: values.Price,
MinTopUp: values.MinTopUp,
CustomCallbackAddress: removeTrailingSlash(values.CustomCallbackAddress),

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 | ⚡ Quick win

Inconsistent indentation in sanitized object.

Lines 411-420 use 4-space indentation while the rest of the object (line 410 and lines 421+) uses 6 spaces. This appears to be a formatting error that breaks code consistency.

🔧 Suggested fix
     const sanitized = {
       PayAddress: removeTrailingSlash(values.PayAddress),
-    EpayId: values.EpayId.trim(),
-    EpayKey: values.EpayKey.trim(),
-    AlipayAppId: values.AlipayAppId.trim(),
-    AlipayGateway: removeTrailingSlash(values.AlipayGateway.trim()),
-    AlipayPrivateKey: values.AlipayPrivateKey.trim(),
-    AlipayPublicKey: values.AlipayPublicKey.trim(),
-    Price: values.Price,
-    MinTopUp: values.MinTopUp,
-    CustomCallbackAddress: removeTrailingSlash(values.CustomCallbackAddress),
+      EpayId: values.EpayId.trim(),
+      EpayKey: values.EpayKey.trim(),
+      AlipayAppId: values.AlipayAppId.trim(),
+      AlipayGateway: removeTrailingSlash(values.AlipayGateway.trim()),
+      AlipayPrivateKey: values.AlipayPrivateKey.trim(),
+      AlipayPublicKey: values.AlipayPublicKey.trim(),
+      Price: values.Price,
+      MinTopUp: values.MinTopUp,
+      CustomCallbackAddress: removeTrailingSlash(values.CustomCallbackAddress),
       PayMethods: values.PayMethods.trim(),
📝 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
const sanitized = {
PayAddress: removeTrailingSlash(values.PayAddress),
EpayId: values.EpayId.trim(),
EpayKey: values.EpayKey.trim(),
Price: values.Price,
MinTopUp: values.MinTopUp,
CustomCallbackAddress: removeTrailingSlash(values.CustomCallbackAddress),
EpayId: values.EpayId.trim(),
EpayKey: values.EpayKey.trim(),
AlipayAppId: values.AlipayAppId.trim(),
AlipayGateway: removeTrailingSlash(values.AlipayGateway.trim()),
AlipayPrivateKey: values.AlipayPrivateKey.trim(),
AlipayPublicKey: values.AlipayPublicKey.trim(),
Price: values.Price,
MinTopUp: values.MinTopUp,
CustomCallbackAddress: removeTrailingSlash(values.CustomCallbackAddress),
const sanitized = {
PayAddress: removeTrailingSlash(values.PayAddress),
EpayId: values.EpayId.trim(),
EpayKey: values.EpayKey.trim(),
AlipayAppId: values.AlipayAppId.trim(),
AlipayGateway: removeTrailingSlash(values.AlipayGateway.trim()),
AlipayPrivateKey: values.AlipayPrivateKey.trim(),
AlipayPublicKey: values.AlipayPublicKey.trim(),
Price: values.Price,
MinTopUp: values.MinTopUp,
CustomCallbackAddress: removeTrailingSlash(values.CustomCallbackAddress),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@web/default/src/features/system-settings/integrations/payment-settings-section.tsx`
around lines 410 - 420, The sanitized object literal in
payment-settings-section.tsx has inconsistent indentation (some properties use 4
spaces vs. the file's 6-space style) which breaks formatting; update the
properties inside the sanitized object (keys like PayAddress, EpayId, EpayKey,
AlipayAppId, AlipayGateway, AlipayPrivateKey, AlipayPublicKey, Price, MinTopUp,
CustomCallbackAddress) to use the same 6-space indentation as the surrounding
code so the object aligns consistently with the rest of the file.

Comment on lines +255 to +257
"Alipay private key": "Cle privee Alipay",
"Alipay public key": "Cle publique Alipay",
"Alipay public key used to verify callbacks": "Cle publique Alipay utilisee pour verifier les callbacks",

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 | ⚡ Quick win

Fix French localization quality in new Alipay strings (missing accents/apostrophes).

Several new French entries look machine-transliterated (e.g., Cle privee, attribue, integration, defaut), which degrades UX consistency in a user-facing payment flow. Please switch these to proper French spelling/accents.

Suggested patch
-    "Alipay private key": "Cle privee Alipay",
-    "Alipay public key": "Cle publique Alipay",
-    "Alipay public key used to verify callbacks": "Cle publique Alipay utilisee pour verifier les callbacks",
+    "Alipay private key": "Clé privée Alipay",
+    "Alipay public key": "Clé publique Alipay",
+    "Alipay public key used to verify callbacks": "Clé publique Alipay utilisée pour vérifier les callbacks",
@@
-    "App ID assigned by Alipay Open Platform": "ID d'application attribue par Alipay Open Platform",
+    "App ID assigned by Alipay Open Platform": "ID d'application attribué par Alipay Open Platform",
@@
-    "Configuration for Alipay official payment integration": "Configuration de l'integration du paiement officiel Alipay",
+    "Configuration for Alipay official payment integration": "Configuration de l'intégration du paiement officiel Alipay",
@@
-    "Leave the default gateway unless you are using a custom endpoint": "Conservez la passerelle par defaut sauf si vous utilisez un endpoint personnalise",
+    "Leave the default gateway unless you are using a custom endpoint": "Conservez la passerelle par défaut sauf si vous utilisez un endpoint personnalisé",
@@
-    "Paste Alipay public key here": "Collez la cle publique Alipay ici",
+    "Paste Alipay public key here": "Collez la clé publique Alipay ici",
@@
-    "Paste your private key here": "Collez votre cle privee ici",
+    "Paste your private key here": "Collez votre clé privée ici",
@@
-    "RSA2 private key used to sign payment requests": "Cle privee RSA2 utilisee pour signer les demandes de paiement",
+    "RSA2 private key used to sign payment requests": "Clé privée RSA2 utilisée pour signer les demandes de paiement",

Also applies to: 370-370, 834-834, 2174-2174, 2861-2861, 2863-2863, 3413-3413

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/i18n/locales/fr.json` around lines 255 - 257, Several French
localization entries for Alipay are missing accents/apostrophes; update the
value strings for keys "Alipay private key", "Alipay public key", and "Alipay
public key used to verify callbacks" to proper French spelling/diacritics (e.g.,
"Clé privée Alipay", "Clé publique Alipay", "Clé publique Alipay utilisée pour
vérifier les callbacks") and review the other similar keys mentioned in the
comment to apply consistent corrections (fix accents, apostrophes, and common
words like "attribué", "intégration", "par défaut").

Comment on lines +251 to +257
"Alipay App ID": "ID ung dung Alipay",
"Alipay gateway": "Cong Alipay",
"Alipay Gateway": "Cong thanh toan Alipay",
"Alipay Open Platform": "Nen tang mo Alipay",
"Alipay private key": "Khoa rieng Alipay",
"Alipay public key": "Khoa cong khai Alipay",
"Alipay public key used to verify callbacks": "Khoa cong khai Alipay dung de xac minh callback",

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 | ⚡ Quick win

Fix Vietnamese diacritics in new Alipay translations (readability regression).

Several newly added values are ASCII-only transliterations (e.g., at Line 251, Line 370, Line 834), which is inconsistent with the rest of this locale file and degrades UX for Vietnamese users.

Suggested translation fixes
-    "Alipay App ID": "ID ung dung Alipay",
-    "Alipay gateway": "Cong Alipay",
-    "Alipay Gateway": "Cong thanh toan Alipay",
-    "Alipay Open Platform": "Nen tang mo Alipay",
-    "Alipay private key": "Khoa rieng Alipay",
-    "Alipay public key": "Khoa cong khai Alipay",
-    "Alipay public key used to verify callbacks": "Khoa cong khai Alipay dung de xac minh callback",
+    "Alipay App ID": "ID ứng dụng Alipay",
+    "Alipay gateway": "Cổng Alipay",
+    "Alipay Gateway": "Cổng thanh toán Alipay",
+    "Alipay Open Platform": "Nền tảng Mở Alipay",
+    "Alipay private key": "Khóa riêng Alipay",
+    "Alipay public key": "Khóa công khai Alipay",
+    "Alipay public key used to verify callbacks": "Khóa công khai Alipay dùng để xác minh callback",

-    "App ID assigned by Alipay Open Platform": "ID ung dung do Alipay Open Platform cap",
+    "App ID assigned by Alipay Open Platform": "ID ứng dụng do Nền tảng Mở Alipay cấp",

-    "Configuration for Alipay official payment integration": "Cau hinh tich hop thanh toan chinh thuc Alipay",
+    "Configuration for Alipay official payment integration": "Cấu hình tích hợp thanh toán chính thức Alipay",

-    "Leave the default gateway unless you are using a custom endpoint": "Giu cong mac dinh neu ban khong dung endpoint tuy chinh",
+    "Leave the default gateway unless you are using a custom endpoint": "Giữ cổng mặc định nếu bạn không dùng endpoint tùy chỉnh",

-    "Paste Alipay public key here": "Dan khoa cong khai Alipay vao day",
+    "Paste Alipay public key here": "Dán khóa công khai Alipay vào đây",

-    "Paste your private key here": "Dan khoa rieng cua ban vao day",
+    "Paste your private key here": "Dán khóa riêng của bạn vào đây",

-    "RSA2 private key used to sign payment requests": "Khoa rieng RSA2 dung de ky yeu cau thanh toan",
+    "RSA2 private key used to sign payment requests": "Khóa riêng RSA2 dùng để ký yêu cầu thanh toán",

Also applies to: 370-370, 834-834, 2174-2174, 2861-2861, 2863-2863, 3413-3413

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/i18n/locales/vi.json` around lines 251 - 257, The new Alipay
entries use ASCII-only transliterations; update the JSON values for the keys
"Alipay App ID", "Alipay gateway", "Alipay Gateway", "Alipay Open Platform",
"Alipay private key", "Alipay public key", and "Alipay public key used to verify
callbacks" to proper Vietnamese with diacritics (e.g., "Alipay App ID" → "ID ứng
dụng Alipay", "Alipay gateway" / "Alipay Gateway" → "Cổng Alipay" or "Cổng thanh
toán Alipay", "Alipay Open Platform" → "Nền tảng mở Alipay", "Alipay private
key" → "Khóa riêng Alipay", "Alipay public key" → "Khóa công khai Alipay",
"Alipay public key used to verify callbacks" → "Khóa công khai Alipay dùng để
xác minh callback"); apply the same diacritic corrections to the other keyed
entries noted in the comment (those at the other indices) so all Vietnamese
translations use proper accents and match the locale's style.

@yuansirrrrr yuansirrrrr closed this Jun 3, 2026
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.

1 participant