Skip to content

feat(waffo): Waffo payment gateway integration - #3293

Merged
seefs001 merged 7 commits into
QuantumNous:mainfrom
zhongyuanzhao-alt:ft-waffo-payment-zzy20260317
Mar 18, 2026
Merged

feat(waffo): Waffo payment gateway integration#3293
seefs001 merged 7 commits into
QuantumNous:mainfrom
zhongyuanzhao-alt:ft-waffo-payment-zzy20260317

Conversation

@zhongyuanzhao-alt

@zhongyuanzhao-alt zhongyuanzhao-alt commented Mar 17, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Integrate Waffo payment SDK (waffo-go v1.3.1) as a new payment gateway alongside Stripe/Creem
  • Add backend webhook handler, pay endpoint, and improved order lock mechanism (race-condition fix)
  • Add admin settings panel for full Waffo configuration (API keys, sandbox/prod, currency, pay methods)
  • Add Waffo payment buttons in the topup page with multi-method support
  • Add i18n translations for Waffo-related strings across all supported languages (en/fr/ja/ru/vi/zh-TW)

Changes

  • Backend: controller/topup_waffo.go, setting/payment_waffo.go, constant/waffo_pay_method.go (new files)
  • Backend: controller/topup.go (Waffo pay method injection, refactored LockOrder/UnlockOrder with ref-counting)
  • Backend: model/option.go (Waffo settings init/update), model/topup.go (RechargeWaffo function)
  • Backend: router/api-router.go (webhook + pay routes)
  • Frontend: SettingsPaymentGatewayWaffo.jsx (new admin config panel)
  • Frontend: RechargeCard.jsx, topup/index.jsx (Waffo payment UI integration)
  • Frontend: TopupHistoryModal.jsx (Waffo payment method display)
  • i18n: All locale files updated with Waffo-related translations

Test plan

  • Verify Go backend compiles successfully
  • Verify frontend builds without errors
  • Test Waffo payment flow in sandbox mode
  • Verify webhook callback processes correctly
  • Confirm no existing payment methods (Stripe/Creem/Epay) are affected
  • Verify admin settings panel saves/loads Waffo configuration correctly

Summary by CodeRabbit

  • New Features

    • Waffo payment gateway added (Card, Apple Pay, Google Pay) with frontend top-up flow, settings UI, payment-method management, and endpoints to initiate payments and receive webhooks.
  • Bug Fixes / Refactor

    • More robust top-up processing with improved concurrency control and idempotent recharge completion.
  • Chores

    • Added "failed" top-up status and extended history/status mapping; updated translations for multiple locales.

- Add Waffo payment SDK integration (waffo-go v1.3.1)
- Backend: webhook handler, pay endpoint, order lock race-condition fix
- Settings: full Waffo config (API keys, sandbox/prod, currency, pay methods)
- Frontend: Waffo payment buttons in topup page, admin settings panel
- i18n: Waffo-related translations for en/fr/ja/ru/vi/zh-TW

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Mar 17, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds Waffo payment gateway (SDK integration, payment creation, webhook handling, and finalization), new Waffo settings and admin UI, frontend top-up UI/i18n updates, a new TopUpStatusFailed constant, and refactors per-order locking to a reference‑counted mutex. (50 words)

Changes

Cohort / File(s) Summary
Constants & Models
common/constants.go, constant/waffo_pay_method.go
Added TopUpStatusFailed constant and new WaffoPayMethod type plus DefaultWaffoPayMethods.
Waffo Payment Controller & Locking
controller/topup_waffo.go, controller/topup.go
New Waffo handlers (RequestWaffoPay, WaffoWebhook, handleWaffoPayment, webhook response signing), integration into top-up flow, and refactor of per-order locking to a ref‑counted mutex map.
Settings & Option Initialization
setting/payment_waffo.go, model/option.go
Added Waffo configuration variables, getters/setters for Waffo pay methods, option map initialization entries, and option update handling for Waffo keys.
Top-up Model & Recharge
model/topup.go
Added RechargeWaffo(tradeNo string) error implementing idempotent finalization, row-locking, quota calculation and user quota updates.
API Routes
router/api-router.go
Registered POST /waffo/webhook and POST /user/.../waffo/pay endpoints (user route rate-limited).
Dependencies
go.mod
Added github.com/waffo-com/waffo-go v1.3.1; removed an indirect github.com/stretchr/objx.
Frontend — Top-up UI
web/src/components/topup/index.jsx, web/src/components/topup/RechargeCard.jsx, web/src/components/topup/modals/TopupHistoryModal.jsx
Expose enableWaffoTopUp, waffoPayMethods, waffoTopUp handler; render Waffo payment options; add failed status mapping; minor error-log silencing.
Frontend — Admin Settings
web/src/pages/Setting/Payment/SettingsPaymentGatewayWaffo.jsx, web/src/components/settings/PaymentSetting.jsx
New Waffo admin UI for keys/certs/urls/currency/pricing/min-topup and pay-method CRUD with icon uploads and JSON persistence; integrated into PaymentSetting.
Internationalization
web/src/i18n/locales/{en,fr,ja,ru,vi,zh-TW}.json
Added/updated translations for Waffo/RSA keys, pay method fields, and top-up configuration messages.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Client as Frontend Client
    participant Server as Backend Server
    participant Waffo as Waffo Gateway
    participant DB as Database
    participant Recharge as Recharge Logic

    User->>Client: Start Waffo payment (select amount/method)
    Client->>Server: POST /user/waffo/pay (amount, payMethodIndex)
    Server->>DB: Create TopUp record (status=pending)
    Server->>Waffo: Create payment order via SDK
    Waffo-->>Server: Return payment URL & order ID
    Server-->>Client: Return payment URL
    Client->>Waffo: User completes payment
    Waffo->>Server: POST /waffo/webhook (signed)
    Server->>Server: Verify signature
    Server->>Server: Lock order (ref-counted mutex)
    Server->>DB: SELECT ... FOR UPDATE (TopUp)
    Server->>DB: Update TopUp status -> success
    Server->>Recharge: Invoke RechargeWaffo (quota update)
    Recharge->>DB: Update user quota
    Server->>Server: Unlock order
    Server-->>Waffo: Respond to webhook
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • seefs001
  • Calcium-Ion

Poem

🐰 I nibble keys and sign the stream,
Waffo orders wake from dream,
Locks counted, webhooks chime,
Quotas hop up one by one,
Carrots, coins — the job is done.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.83% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(waffo): Waffo payment gateway integration' directly and clearly describes the main change—integration of the Waffo payment gateway. It accurately summarizes the primary objective of the pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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: 9

🧹 Nitpick comments (1)
controller/topup_waffo.go (1)

51-53: Consider using a more descriptive fake email domain.

The examples.com domain is not a reserved domain for testing. Consider using example.com (RFC 2606 reserved) or a domain clearly owned by the service.

Suggested fix
 func getWaffoUserEmail(user *model.User) string {
-	return fmt.Sprintf("%d@examples.com", user.Id)
+	return fmt.Sprintf("%d@example.com", user.Id)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controller/topup_waffo.go` around lines 51 - 53, The getWaffoUserEmail
function currently uses "examples.com" which is not an RFC2606 reserved test
domain; update getWaffoUserEmail (which formats the email from model.User Id) to
use a descriptive, reserved or service-owned domain such as "example.com" or
your service's owned test domain instead, e.g., change the domain string in the
fmt.Sprintf call within getWaffoUserEmail to "example.com" (or another approved
domain).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@common/constants.go`:
- Around line 212-215: The UI mapping for top-up statuses is missing the new
failed status so orders saved with common.TopUpStatusFailed render a raw badge;
update the TopupHistoryModal.jsx status-to-badge mapping to handle the "failed"
status (compare against TopUpStatusFailed or the literal "failed") and return
the appropriate Badge variant/text (match the visual style used for failures
elsewhere, e.g., a danger/error badge and label "Failed") so failed orders
display a consistent styled badge instead of raw text.

In `@controller/topup_waffo.go`:
- Around line 259-267: The code reads orderData.AcquiringOrderID but never
persists it because TopUp has no GatewayOrderId field; to fix, add a
GatewayOrderId string field to the TopUp struct in model/topup.go (or choose an
existing field to repurpose), update any DB mapping/migration as needed, then
before calling topUp.Update() assign topUp.GatewayOrderId =
orderData.AcquiringOrderID so the subsequent topUp.Update() persists the
acquiring order ID and preserves refund functionality.

In `@model/topup.go`:
- Around line 393-395: The Transaction block in RechargeWaffo (in
model/topup.go) uses tx.Set("gorm:query_option", "FOR UPDATE") which breaks on
SQLite; change it to skip the FOR UPDATE clause when running under SQLite (e.g.,
wrap the Set call with a dialect check such as if !common.UsingSQLite {
tx.Set("gorm:query_option", "FOR UPDATE") }) or refactor the logic to an atomic
state-transition UPDATE (UPDATE ... WHERE trade_no = ? AND status = ?) instead
of selecting with FOR UPDATE so the behavior is compatible across SQLite, MySQL
and Postgres; locate the Transaction lambda that calls
tx.Set(...).Where(...).First(topUp) and apply the conditional or replace with
the atomic UPDATE pattern.

In `@web/src/components/topup/index.jsx`:
- Around line 317-321: The UI currently reuses the shared minTopUp for Waffo,
causing server-side rejections; add and use a dedicated waffoMinTopUp
variable/state (e.g., derived from config prop or API) and replace usages of
minTopUp within the Waffo path (specifically inside the waffoTopUp function and
the other Waffo-specific validation block around lines 483-496) to validate
against waffoMinTopUp so the client-side check matches
controller/topup_waffo.go's WaffoMinTopUp requirement.

In `@web/src/components/topup/RechargeCard.jsx`:
- Line 236: The outer JSX conditional currently uses (enableOnlineTopUp ||
enableStripeTopUp || enableWaffoTopUp) which causes the generic "暂无可用的支付方式..."
selector to render when Waffo is the only gateway; update the guard so Waffo
alone doesn't trigger this empty-state — either remove enableWaffoTopUp from
that outer condition or refine it to only include enableWaffoTopUp when
payMethods is empty (e.g. (enableOnlineTopUp || enableStripeTopUp ||
(enableWaffoTopUp && (!payMethods || payMethods.length === 0)))); apply the same
change to the other identical block that spans the 295-363 region so the generic
selector only shows when appropriate.

In `@web/src/i18n/locales/fr.json`:
- Around line 3163-3172: The fr locale is missing several new Waffo translation
keys used by RechargeCard.jsx and SettingsPaymentGatewayWaffo.jsx; add French
entries using the original Chinese source strings as keys (e.g. "Waffo 充值",
"Waffo 设置", "启用 Waffo", "更新 Waffo 设置", "新增支付方式" and any other Waffo-related keys
introduced by those components) into web/src/i18n/locales/fr.json so the UI does
not fall back to Chinese/English—use accurate French translations for each
Chinese key and keep the key names exactly as in the code.

In `@web/src/i18n/locales/zh-TW.json`:
- Line 2903: The translation string for the key
"提示:端点映射仅用于模型广场展示,不会影响模型真实调用。如需配置真实调用,请前往「渠道管理」。" uses 「頻道管理」 but the locale
uses 「管道管理」 elsewhere; update the value to replace 「頻道管理」 with 「管道管理」 so the
JSON entry becomes consistent with the existing zh-TW terminology.

In `@web/src/pages/Setting/Payment/SettingsPaymentGatewayWaffo.jsx`:
- Around line 394-408: The two secret fields currently use Form.TextArea (fields
WaffoPrivateKey and WaffoSandboxPrivateKey) so the type='password' prop is
ignored; replace them with a password-capable input or add an explicit
reveal/hide control: swap Form.TextArea for a password input component (e.g.,
Input.Password or a controlled component that toggles masking) bound to the same
field names WaffoPrivateKey and WaffoSandboxPrivateKey, preserve autosize/visual
needs by providing a monospace font or a toggle that switches between masked
Input.Password and a read-only TextArea for viewing, and ensure the Form
bindings/validation remain unchanged.
- Around line 20-36: The file has a top-level statement breaking ESM: move the
destructuring const { Text } = Typography so that all import statements remain
first (keep imports of React, semi-ui components, API, showError/showSuccess,
and useTranslation before any other code and then add const { Text } =
Typography immediately after those imports); also replace the two occurrences of
Form.TextArea used for RSA private keys (referenced in the JSX where
Form.TextArea is used for secrets) with a Form.Input configured with
type='password' for single-line secrets or implement a secure multi-line
alternative (e.g., a masked custom component or toggleable visibility) so
private keys are not using an unmasked TextArea.

---

Nitpick comments:
In `@controller/topup_waffo.go`:
- Around line 51-53: The getWaffoUserEmail function currently uses
"examples.com" which is not an RFC2606 reserved test domain; update
getWaffoUserEmail (which formats the email from model.User Id) to use a
descriptive, reserved or service-owned domain such as "example.com" or your
service's owned test domain instead, e.g., change the domain string in the
fmt.Sprintf call within getWaffoUserEmail to "example.com" (or another approved
domain).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9b3b8ab5-8914-421d-bb8d-ea33e29ccf1e

📥 Commits

Reviewing files that changed from the base of the PR and between 620e066 and 202a433.

⛔ Files ignored due to path filters (4)
  • go.sum is excluded by !**/*.sum
  • web/public/pay-apple.png is excluded by !**/*.png
  • web/public/pay-card.png is excluded by !**/*.png
  • web/public/pay-google.png is excluded by !**/*.png
📒 Files selected for processing (20)
  • common/constants.go
  • constant/waffo_pay_method.go
  • controller/topup.go
  • controller/topup_waffo.go
  • go.mod
  • model/option.go
  • model/topup.go
  • router/api-router.go
  • setting/payment_waffo.go
  • web/src/components/settings/PaymentSetting.jsx
  • web/src/components/topup/RechargeCard.jsx
  • web/src/components/topup/index.jsx
  • web/src/components/topup/modals/TopupHistoryModal.jsx
  • web/src/i18n/locales/en.json
  • web/src/i18n/locales/fr.json
  • web/src/i18n/locales/ja.json
  • web/src/i18n/locales/ru.json
  • web/src/i18n/locales/vi.json
  • web/src/i18n/locales/zh-TW.json
  • web/src/pages/Setting/Payment/SettingsPaymentGatewayWaffo.jsx

Comment thread common/constants.go
Comment thread controller/topup_waffo.go Outdated
Comment thread model/topup.go
Comment thread web/src/components/topup/index.jsx
Comment thread web/src/components/topup/RechargeCard.jsx
Comment thread web/src/i18n/locales/fr.json
Comment thread web/src/i18n/locales/zh-TW.json Outdated
Comment thread web/src/pages/Setting/Payment/SettingsPaymentGatewayWaffo.jsx
Comment thread web/src/pages/Setting/Payment/SettingsPaymentGatewayWaffo.jsx
zhongyuanzhao-alt and others added 3 commits March 18, 2026 15:55
The backend defines TopUpStatusFailed = "failed" but the frontend
STATUS_CONFIG was missing this status, causing raw text display
instead of a styled danger badge.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The code read orderData.AcquiringOrderID but never assigned it to
any TopUp field before calling Update(), making the block a no-op.
Removed since GatewayOrderId storage is not needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The waffoTopUp function was validating against the shared minTopUp
which could be set by epay/stripe when multiple gateways are enabled,
causing mismatch with backend's WaffoMinTopUp check.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@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: 2

🤖 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_waffo.go`:
- Around line 51-53: The getWaffoUserEmail function currently formats addresses
with the non-reserved domain "examples.com"; change it to use the reserved
testing domain "example.com" by updating the format string in getWaffoUserEmail
(which references user.Id on model.User) so emails are generated as
"%d@example.com" instead of "%d@examples.com".
- Around line 341-353: The code marks a pending top-up as failed but discards
any error from topUp.Update(), hiding DB failures; change the block that calls
model.GetTopUpByTradeNo and updates topUp.Status (the branch using
TopUpStatusPending and setting to TopUpStatusFailed) to capture the returned
error from topUp.Update(), and log it (e.g., via log.Printf or the package
logger) including context like result.MerchantOrderID and the operation
("topUp.Update") so DB update failures are visible; ensure you do this for the
Update() call in this Waffo handler similarly to how other handlers (topup.go,
topup_stripe.go) log Update() errors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e2fe7094-0fe4-4285-b0ee-ebe03fec872b

📥 Commits

Reviewing files that changed from the base of the PR and between 2270f63 and d595ef4.

📒 Files selected for processing (1)
  • controller/topup_waffo.go

Comment thread controller/topup_waffo.go
Comment on lines +51 to +53
func getWaffoUserEmail(user *model.User) string {
return fmt.Sprintf("%d@examples.com", user.Id)
}

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

Typo: Use example.com instead of examples.com.

RFC 2606 reserves example.com for documentation and testing purposes, while examples.com is a potentially purchasable domain. Using the non-reserved domain could inadvertently route emails to a third party.

🔧 Proposed fix
 func getWaffoUserEmail(user *model.User) string {
-	return fmt.Sprintf("%d@examples.com", user.Id)
+	return fmt.Sprintf("%d@example.com", user.Id)
 }
📝 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
func getWaffoUserEmail(user *model.User) string {
return fmt.Sprintf("%d@examples.com", user.Id)
}
func getWaffoUserEmail(user *model.User) string {
return fmt.Sprintf("%d@example.com", user.Id)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controller/topup_waffo.go` around lines 51 - 53, The getWaffoUserEmail
function currently formats addresses with the non-reserved domain
"examples.com"; change it to use the reserved testing domain "example.com" by
updating the format string in getWaffoUserEmail (which references user.Id on
model.User) so emails are generated as "%d@example.com" instead of
"%d@examples.com".

Comment thread controller/topup_waffo.go
Comment on lines +341 to +353
if result.OrderStatus != "PAY_SUCCESS" {
log.Printf("Waffo 订单状态非成功: %s, 订单: %s", result.OrderStatus, result.MerchantOrderID)
// 终态失败订单标记为 failed,避免永远停在 pending
if result.MerchantOrderID != "" {
if topUp := model.GetTopUpByTradeNo(result.MerchantOrderID); topUp != nil &&
topUp.Status == common.TopUpStatusPending {
topUp.Status = common.TopUpStatusFailed
_ = topUp.Update()
}
}
sendWaffoWebhookResponse(c, wh, true, "")
return
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Waffo payment webhook notification order status values documentation

💡 Result:

Waffo (waffo.com), a global payment and monetization platform, supports webhook notifications for real-time updates on payments, refunds, and subscriptions. Official documentation is referenced in their Go SDK (github.com/waffo-com/waffo-go), which handles webhook events including PaymentNotification with OrderStatus and SubscriptionStatusNotification. From the Waffo Go SDK documentation: - Webhook types: PAYMENT_NOTIFICATION (payment result, OrderStatus), REFUND_NOTIFICATION, SUBSCRIPTION_STATUS_NOTIFICATION (subscription status changes like IN_PROGRESS, AUTHORIZATION_REQUIRED, SUCCESS, CLOSED), SUBSCRIPTION_PAYMENT_NOTIFICATION, SUBSCRIPTION_PERIOD_CHANGED_NOTIFICATION. Waffo also has partial docs at docs.waffo.com (e.g., Google Pay integration mentions orderStatus: "Success"). A closely related payment gateway, WaafiPay (docs.waafipay.com/webhooks), explicitly documents payment webhook status values in payment.status: - APPROVED: Transaction succeeded (fulfill order) - FAILED: Technical error (cancel order, log error) - DECLINED: Declined (retry or alternative payment) - CANCELED: Canceled by user - EXPIRED: Session expired - TIMEOUT: Timeout Events: webhook.test, authorization, refund. No complete official Waffo webhook docs found in public searches, but SDK and related sources indicate similar payment/order statuses (e.g., Success, OrderStatus field). Contact Waffo support or check developer portal after signup for full docs. Sites: waffo.com, dev.waffo.com, docs.waffo.com.

Citations:


🏁 Script executed:

# Find TopUp model definition and status constants
fd -e go -x rg -l "TopUpStatus|type TopUp" {} \; | head -20

Repository: QuantumNous/new-api

Length of output: 339


🏁 Script executed:

# Check for status constants and enums
rg -A 5 "TopUpStatusPending|TopUpStatusFailed" --type go

Repository: QuantumNous/new-api

Length of output: 6153


🏁 Script executed:

# Find Stripe webhook handler mentioned in the comment context
rg -B 5 -A 15 "handleStripePaymentEvent|LockOrder.*UnlockOrder" --type go

Repository: QuantumNous/new-api

Length of output: 45


🏁 Script executed:

# Search for Waffo documentation or configuration
rg -i "waffo" --type go -B 2 -A 5 | head -100

Repository: QuantumNous/new-api

Length of output: 6225


🏁 Script executed:

# Find Stripe webhook handler
rg -B 5 -A 30 "func StripeWebhook|handleStripePaymentEvent" --type go | head -80

Repository: QuantumNous/new-api

Length of output: 1924


🏁 Script executed:

# Check the full Waffo webhook handler
rg -B 5 -A 50 "func.*WaffoWebhook|handleWaffoPayment" --type go | head -150

Repository: QuantumNous/new-api

Length of output: 5536


🏁 Script executed:

# Search for any Waffo order status handling or documentation
rg -i "orderStatus|payment.*status|waffo.*status" --type go | grep -i waffo

Repository: QuantumNous/new-api

Length of output: 458


🏁 Script executed:

# Check all Update() error handling patterns in payment handlers
rg "topUp.Update|order.Update" --type go -B 2 -A 2

Repository: QuantumNous/new-api

Length of output: 1801


Log Update() errors to catch database failures.

Multiple Update() calls silently ignore errors (e.g., line 348: _ = topUp.Update()). This prevents visibility into database issues when status transitions fail. Other payment handlers in the codebase (e.g., topup.go, topup_stripe.go) properly log these errors.

🔧 Suggested improvement
 		if topUp := model.GetTopUpByTradeNo(result.MerchantOrderID); topUp != nil &&
 			topUp.Status == common.TopUpStatusPending {
 			topUp.Status = common.TopUpStatusFailed
-			_ = topUp.Update()
+			if err := topUp.Update(); err != nil {
+				log.Printf("Waffo 更新订单状态失败: %v, 订单: %s", err, result.MerchantOrderID)
+			}
 		}
📝 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
if result.OrderStatus != "PAY_SUCCESS" {
log.Printf("Waffo 订单状态非成功: %s, 订单: %s", result.OrderStatus, result.MerchantOrderID)
// 终态失败订单标记为 failed,避免永远停在 pending
if result.MerchantOrderID != "" {
if topUp := model.GetTopUpByTradeNo(result.MerchantOrderID); topUp != nil &&
topUp.Status == common.TopUpStatusPending {
topUp.Status = common.TopUpStatusFailed
_ = topUp.Update()
}
}
sendWaffoWebhookResponse(c, wh, true, "")
return
}
if result.OrderStatus != "PAY_SUCCESS" {
log.Printf("Waffo 订单状态非成功: %s, 订单: %s", result.OrderStatus, result.MerchantOrderID)
// 终态失败订单标记为 failed,避免永远停在 pending
if result.MerchantOrderID != "" {
if topUp := model.GetTopUpByTradeNo(result.MerchantOrderID); topUp != nil &&
topUp.Status == common.TopUpStatusPending {
topUp.Status = common.TopUpStatusFailed
if err := topUp.Update(); err != nil {
log.Printf("Waffo 更新订单状态失败: %v, 订单: %s", err, result.MerchantOrderID)
}
}
}
sendWaffoWebhookResponse(c, wh, true, "")
return
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controller/topup_waffo.go` around lines 341 - 353, The code marks a pending
top-up as failed but discards any error from topUp.Update(), hiding DB failures;
change the block that calls model.GetTopUpByTradeNo and updates topUp.Status
(the branch using TopUpStatusPending and setting to TopUpStatusFailed) to
capture the returned error from topUp.Update(), and log it (e.g., via log.Printf
or the package logger) including context like result.MerchantOrderID and the
operation ("topUp.Update") so DB update failures are visible; ensure you do this
for the Update() call in this Waffo handler similarly to how other handlers
(topup.go, topup_stripe.go) log Update() errors.

@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: 1

🧹 Nitpick comments (2)
web/src/components/topup/index.jsx (2)

573-580: Intentional empty dependency array for redirect handling.

The [] dependency array is correct here for one-time URL parameter processing after payment redirects. If you encounter ESLint exhaustive-deps warnings, consider adding a disable comment with explanation:

// eslint-disable-next-line react-hooks/exhaustive-deps -- intentional: process redirect params once on mount
🤖 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 573 - 580, The useEffect
that checks searchParams and sets setOpenHistory is intentionally meant to run
once on mount but currently triggers an eslint exhaustive-deps warning; update
the useEffect by adding an inline eslint disable comment to suppress
exhaustive-deps with an explanation (e.g., above the useEffect add "//
eslint-disable-next-line react-hooks/exhaustive-deps -- intentional: process
redirect params once on mount") so the one-time redirect handling in useEffect
(uses searchParams, setOpenHistory, setSearchParams) is preserved without linter
noise.

637-639: Consider adding minimal logging for silent failures.

Empty catch blocks make debugging production issues harder. Consider logging at a lower level for observability without impacting UX:

} catch (err) {
  console.debug('Amount fetch failed:', err);
}

Also applies to: 663-665

🤖 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 637 - 639, The catch blocks
silently swallow errors without any logging, making it difficult to trace
failures in production. Update the catch blocks at the specified locations
(including the one handling the amount fetch failure inside the function near
lines 637 and 663) to log the caught errors using console.debug or equivalent
logging at a lower severity so the errors are recorded for observability while
not disrupting user experience.
🤖 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/index.jsx`:
- Around line 339-341: The call showError(res) can receive undefined (e.g.,
network failure) and ends up displaying "undefined"; update the call site (where
showError(res) is invoked) to provide a safe fallback message—either pass
showError(res || "An error occurred while processing your request") or modify
showError to treat a falsy argument as the same fallback; ensure the fallback
text matches the message used on line 337 for consistency and reference the
showError function and the call site that currently does showError(res).

---

Nitpick comments:
In `@web/src/components/topup/index.jsx`:
- Around line 573-580: The useEffect that checks searchParams and sets
setOpenHistory is intentionally meant to run once on mount but currently
triggers an eslint exhaustive-deps warning; update the useEffect by adding an
inline eslint disable comment to suppress exhaustive-deps with an explanation
(e.g., above the useEffect add "// eslint-disable-next-line
react-hooks/exhaustive-deps -- intentional: process redirect params once on
mount") so the one-time redirect handling in useEffect (uses searchParams,
setOpenHistory, setSearchParams) is preserved without linter noise.
- Around line 637-639: The catch blocks silently swallow errors without any
logging, making it difficult to trace failures in production. Update the catch
blocks at the specified locations (including the one handling the amount fetch
failure inside the function near lines 637 and 663) to log the caught errors
using console.debug or equivalent logging at a lower severity so the errors are
recorded for observability while not disrupting user experience.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a8643558-f70e-439a-b0f1-35b37ce71b9a

📥 Commits

Reviewing files that changed from the base of the PR and between d595ef4 and bd09b47.

📒 Files selected for processing (1)
  • web/src/components/topup/index.jsx

Comment on lines +339 to +341
} else {
showError(res);
}

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

Bug: showError(res) displays undefined when response is undefined.

When res is undefined (e.g., network failure), calling showError(res) will show a blank or undefined message. Use a fallback message consistent with line 337.

🐛 Proposed fix
         } else {
-            showError(res);
+            showError(t('支付请求失败'));
         }
📝 Committable suggestion

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

Suggested change
} else {
showError(res);
}
} else {
showError(t('支付请求失败'));
}
🤖 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 339 - 341, The call
showError(res) can receive undefined (e.g., network failure) and ends up
displaying "undefined"; update the call site (where showError(res) is invoked)
to provide a safe fallback message—either pass showError(res || "An error
occurred while processing your request") or modify showError to treat a falsy
argument as the same fallback; ensure the fallback text matches the message used
on line 337 for consistency and reference the showError function and the call
site that currently does showError(res).

zhongyuanzhao-alt and others added 3 commits March 18, 2026 16:12
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ESM requires all import statements before other code.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…te buttons

When only Waffo was enabled, the generic payment method list showed a
"Waffo (Global Payment)" button calling preTopUp (epay flow) instead of
waffoTopUp, while the dedicated "Waffo 充值" section had the correct buttons.

Fix: filter waffo entries from generic list and hide the "选择支付方式"
column when no non-waffo methods exist.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@seefs001
seefs001 merged commit 04f5dd0 into QuantumNous:main Mar 18, 2026
1 check passed
@coderabbitai coderabbitai Bot mentioned this pull request Jul 30, 2026
11 tasks
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.

2 participants