feat(payment): enable Waffo Pancake pay#4904
Conversation
# Conflicts: # controller/topup_waffo_pancake.go # model/payment_method_guard_test.go
# Conflicts: # web/classic/public/waffo-pancake-logo.svg # web/src/components/settings/PaymentSetting.jsx
WalkthroughThis PR adds Waffo Pancake order metadata to checkout, removes quota-based amount normalization, tightens webhook signature and environment checks, records caller IP in recharge audit logs, enables API routes and UI, and applies formatting and struct-tag updates. ChangesWaffo Pancake Payment Flow
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 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: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
controller/topup_waffo_pancake.go (1)
143-181:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winFail fast when
WaffoPancakeCurrencyis empty.This code now sends
currencyin both the checkout request and embedded metadata, but the config guard still doesn't require it. With an empty setting, users get an upstream session-creation failure instead of a local configuration error.Suggested fix
tradeNo := fmt.Sprintf("WAFFO_PANCAKE-%d-%d-%s", id, time.Now().UnixMilli(), randstr.String(6)) currency := strings.ToUpper(strings.TrimSpace(setting.WaffoPancakeCurrency)) + if currency == "" { + c.JSON(http.StatusOK, gin.H{"message": "error", "data": "Waffo Pancake 配置不完整"}) + return + } topUp := &model.TopUp{ UserId: id, Amount: req.Amount,🤖 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_waffo_pancake.go` around lines 143 - 181, The code does not validate setting.WaffoPancakeCurrency so an empty value causes upstream session creation to fail; after computing currency := strings.ToUpper(strings.TrimSpace(setting.WaffoPancakeCurrency)) (used in CreateWaffoPancakeCheckoutSession and buildWaffoPancakeOrderMetadata) add a guard that if currency == "" you log an error (include user id/tradeNo) and return a JSON error response (e.g., "配置错误: 缺少 WaffoPancakeCurrency") without calling service.CreateWaffoPancakeCheckoutSession, failing fast on the missing config. Ensure you reference the same symbols: WaffoPancakeCurrency, currency, CreateWaffoPancakeCheckoutSession, and buildWaffoPancakeOrderMetadata.
🧹 Nitpick comments (1)
web/default/src/features/wallet/lib/ui.tsx (1)
31-46: ⚡ Quick winReplace inline icon styles with utility classes in
web/defaultUI code.Use Tailwind classes (and
cn()if needed) instead ofCSSPropertiesstyle objects for this icon branch to stay consistent with the frontend styling standard.Proposed refactor
-import { type CSSProperties, type ReactNode } from 'react' +import { type ReactNode } from 'react' @@ -const WAFFO_PANCAKE_ICON_BOX_STYLE: CSSProperties = { - display: 'inline-flex', - width: 18, - height: 18, - overflow: 'hidden', - alignItems: 'center', - justifyContent: 'flex-start', - flex: '0 0 18px', -} -const WAFFO_PANCAKE_ICON_IMAGE_STYLE: CSSProperties = { - display: 'block', - height: 22, - width: 'auto', - maxWidth: 'none', - transform: 'translateY(-2px)', -} @@ case PAYMENT_TYPES.WAFFO_PANCAKE: return ( <span - className={className} - style={WAFFO_PANCAKE_ICON_BOX_STYLE} + className={`${className} inline-flex h-[18px] w-[18px] shrink-0 items-center justify-start overflow-hidden`} aria-hidden='true' > <img src={WAFFO_PANCAKE_LOGO} alt='' - style={WAFFO_PANCAKE_ICON_IMAGE_STYLE} + className='block h-[22px] w-auto max-w-none -translate-y-[2px]' /> </span> )As per coding guidelines, "Use Tailwind utility classes as the primary styling approach; merge dynamic class names using
cn(); avoid inline styles for dynamic scenarios".Also applies to: 142-152
🤖 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/wallet/lib/ui.tsx` around lines 31 - 46, Replace the inline CSSProperties objects WAFFO_PANCAKE_ICON_BOX_STYLE and WAFFO_PANCAKE_ICON_IMAGE_STYLE with Tailwind utility class strings and apply them via className (use cn() to merge if dynamic); map the current properties (display:inline-flex, width/height 18, overflow:hidden, alignItems/justifyContent/flex, and the image height/transform/maxWidth rules) to equivalent Tailwind classes (e.g., inline-flex, w-4.5/h-4.5 or nearest, overflow-hidden, items-center, justify-start, flex-none, block, h-5.5/auto width, -translate-y-0.5, max-w-none), remove the CSSProperties constants and update the JSX to use className={cn(...)}; repeat the same refactor for the other inline icon style objects used elsewhere in this file (the other pancake/icon style constants).
🤖 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 `@service/waffo_pancake.go`:
- Around line 186-203: ResolveWaffoPancakeTradeNo currently skips checking
orderMetadata.payment_method; update the function to validate
metadata.PaymentMethod against the stored topUp.PaymentMethod (and/or against
model.PaymentMethodWaffoPancake) after loading topUp and before returning
tradeNo, returning an error on mismatch; use the existing symbols
metadata.PaymentMethod and topUp.PaymentMethod (and normalize/convert types if
needed) so a webhook cannot succeed with a mismatched payment method.
---
Outside diff comments:
In `@controller/topup_waffo_pancake.go`:
- Around line 143-181: The code does not validate setting.WaffoPancakeCurrency
so an empty value causes upstream session creation to fail; after computing
currency := strings.ToUpper(strings.TrimSpace(setting.WaffoPancakeCurrency))
(used in CreateWaffoPancakeCheckoutSession and buildWaffoPancakeOrderMetadata)
add a guard that if currency == "" you log an error (include user id/tradeNo)
and return a JSON error response (e.g., "配置错误: 缺少 WaffoPancakeCurrency") without
calling service.CreateWaffoPancakeCheckoutSession, failing fast on the missing
config. Ensure you reference the same symbols: WaffoPancakeCurrency, currency,
CreateWaffoPancakeCheckoutSession, and buildWaffoPancakeOrderMetadata.
---
Nitpick comments:
In `@web/default/src/features/wallet/lib/ui.tsx`:
- Around line 31-46: Replace the inline CSSProperties objects
WAFFO_PANCAKE_ICON_BOX_STYLE and WAFFO_PANCAKE_ICON_IMAGE_STYLE with Tailwind
utility class strings and apply them via className (use cn() to merge if
dynamic); map the current properties (display:inline-flex, width/height 18,
overflow:hidden, alignItems/justifyContent/flex, and the image
height/transform/maxWidth rules) to equivalent Tailwind classes (e.g.,
inline-flex, w-4.5/h-4.5 or nearest, overflow-hidden, items-center,
justify-start, flex-none, block, h-5.5/auto width, -translate-y-0.5,
max-w-none), remove the CSSProperties constants and update the JSX to use
className={cn(...)}; repeat the same refactor for the other inline icon style
objects used elsewhere in this file (the other pancake/icon style constants).
🪄 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: 9420f651-a253-4795-bcae-074fef61fcb1
⛔ Files ignored due to path filters (2)
web/classic/public/waffo-pancake-logo.svgis excluded by!**/*.svgweb/default/public/waffo-pancake-logo.svgis excluded by!**/*.svg
📒 Files selected for processing (15)
controller/token_test.gocontroller/topup_waffo_pancake.gocontroller/topup_waffo_pancake_test.godto/gemini.gomodel/log.gomodel/payment_method_guard_test.gomodel/topup.gopkg/billingexpr/compile.gopkg/billingexpr/run.gorouter/api-router.goservice/waffo_pancake.goservice/waffo_pancake_test.goweb/classic/src/components/settings/PaymentSetting.jsxweb/classic/src/components/topup/RechargeCard.jsxweb/default/src/features/wallet/lib/ui.tsx
| metadata := event.Data.OrderMetadata | ||
| tradeNo := strings.TrimSpace(metadata.TradeNo) | ||
| if tradeNo == "" { | ||
| return "", fmt.Errorf("missing webhook orderMetadata.trade_no") | ||
| } | ||
|
|
||
| topUp := model.GetTopUpByTradeNo(tradeNo) | ||
| if topUp == nil { | ||
| return "", fmt.Errorf("waffo pancake order not found for metadata trade_no=%s", tradeNo) | ||
| } | ||
| if topUp.PaymentMethod != model.PaymentMethodWaffoPancake { | ||
| return "", fmt.Errorf("waffo pancake payment method mismatch trade_no=%s", tradeNo) | ||
| } | ||
| if metadata.UserID != topUp.UserId { | ||
| return "", fmt.Errorf("webhook orderMetadata.user_id mismatch") | ||
| } | ||
|
|
||
| return "", fmt.Errorf("missing webhook orderId") | ||
| return tradeNo, nil |
There was a problem hiding this comment.
Validate orderMetadata.payment_method before accepting the webhook.
ResolveWaffoPancakeTradeNo now trusts orderMetadata for the local lookup, but it never compares metadata.PaymentMethod with the stored top-up. A signed webhook with the right trade_no/user_id and a mismatched payment method still gets treated as valid, which weakens the mapping hardening in this PR.
Suggested fix
if topUp.PaymentMethod != model.PaymentMethodWaffoPancake {
return "", fmt.Errorf("waffo pancake payment method mismatch trade_no=%s", tradeNo)
}
if metadata.UserID != topUp.UserId {
return "", fmt.Errorf("webhook orderMetadata.user_id mismatch")
}
+ if strings.TrimSpace(metadata.PaymentMethod) != topUp.PaymentMethod {
+ return "", fmt.Errorf("webhook orderMetadata.payment_method mismatch")
+ }
return tradeNo, nil📝 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.
| metadata := event.Data.OrderMetadata | |
| tradeNo := strings.TrimSpace(metadata.TradeNo) | |
| if tradeNo == "" { | |
| return "", fmt.Errorf("missing webhook orderMetadata.trade_no") | |
| } | |
| topUp := model.GetTopUpByTradeNo(tradeNo) | |
| if topUp == nil { | |
| return "", fmt.Errorf("waffo pancake order not found for metadata trade_no=%s", tradeNo) | |
| } | |
| if topUp.PaymentMethod != model.PaymentMethodWaffoPancake { | |
| return "", fmt.Errorf("waffo pancake payment method mismatch trade_no=%s", tradeNo) | |
| } | |
| if metadata.UserID != topUp.UserId { | |
| return "", fmt.Errorf("webhook orderMetadata.user_id mismatch") | |
| } | |
| return "", fmt.Errorf("missing webhook orderId") | |
| return tradeNo, nil | |
| metadata := event.Data.OrderMetadata | |
| tradeNo := strings.TrimSpace(metadata.TradeNo) | |
| if tradeNo == "" { | |
| return "", fmt.Errorf("missing webhook orderMetadata.trade_no") | |
| } | |
| topUp := model.GetTopUpByTradeNo(tradeNo) | |
| if topUp == nil { | |
| return "", fmt.Errorf("waffo pancake order not found for metadata trade_no=%s", tradeNo) | |
| } | |
| if topUp.PaymentMethod != model.PaymentMethodWaffoPancake { | |
| return "", fmt.Errorf("waffo pancake payment method mismatch trade_no=%s", tradeNo) | |
| } | |
| if metadata.UserID != topUp.UserId { | |
| return "", fmt.Errorf("webhook orderMetadata.user_id mismatch") | |
| } | |
| if strings.TrimSpace(metadata.PaymentMethod) != topUp.PaymentMethod { | |
| return "", fmt.Errorf("webhook orderMetadata.payment_method mismatch") | |
| } | |
| return tradeNo, 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 `@service/waffo_pancake.go` around lines 186 - 203, ResolveWaffoPancakeTradeNo
currently skips checking orderMetadata.payment_method; update the function to
validate metadata.PaymentMethod against the stored topUp.PaymentMethod (and/or
against model.PaymentMethodWaffoPancake) after loading topUp and before
returning tradeNo, returning an error on mismatch; use the existing symbols
metadata.PaymentMethod and topUp.PaymentMethod (and normalize/convert types if
needed) so a webhook cannot succeed with a mismatched payment method.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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
`@web/default/src/features/system-settings/integrations/payment-settings-section.tsx`:
- Around line 1471-1482: The wrapper currently uses pointer-events-none and
aria-disabled which do not prevent keyboard or assistive interactions; update
the wrapper div (the element that wraps WaffoSettingsSection and
WaffoPancakeSettingsSection) to include the inert attribute when
complianceConfirmed is false (e.g. inert={ !complianceConfirmed }), and also set
aria-hidden={ !complianceConfirmed } to hide content from assistive tech; keep
aria-disabled for explicit state but add inert/aria-hidden to fully block
keyboard focus and screen-reader access; if TypeScript flags the native inert
prop, either cast the props to any or add a JSX.IntrinsicElements declaration
for inert.
🪄 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: 2919601e-673b-4e3c-a8a4-50bccf47f079
📒 Files selected for processing (1)
web/default/src/features/system-settings/integrations/payment-settings-section.tsx
| <div | ||
| className={cn(!complianceConfirmed && 'pointer-events-none opacity-40')} | ||
| aria-disabled={!complianceConfirmed} | ||
| > | ||
| <WaffoSettingsSection defaultValues={waffoDefaultValues} /> | ||
|
|
||
| <Separator /> | ||
|
|
||
| <WaffoPancakeSettingsSection | ||
| defaultValues={waffoPancakeDefaultValues} | ||
| /> | ||
| </div> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n web/default/src/features/system-settings/integrations/payment-settings-section.tsx | sed -n '1465,1490p'Repository: QuantumNous/new-api
Length of output: 713
🏁 Script executed:
rg -A 10 -B 10 'WaffoSettingsSection.*defaultValues' web/default/src/features/system-settings/integrations/payment-settings-section.tsxRepository: QuantumNous/new-api
Length of output: 557
🏁 Script executed:
# Check if inert is used elsewhere in the codebase
rg 'inert=' web/default/src --type tsx --type tsRepository: QuantumNous/new-api
Length of output: 91
🏁 Script executed:
# Search for other accessibility patterns handling disabled state
rg 'pointer-events-none.*opacity' web/default/src --type tsx -A 3 -B 3Repository: QuantumNous/new-api
Length of output: 91
🏁 Script executed:
# Search for inert attribute usage - corrected
rg 'inert' web/default/src --glob '*.tsx' --glob '*.ts'Repository: QuantumNous/new-api
Length of output: 45
🏁 Script executed:
# Search for other pointer-events-none patterns with disabling logic
rg 'pointer-events-none' web/default/src --glob '*.tsx' -B 2 -A 2Repository: QuantumNous/new-api
Length of output: 48520
🏁 Script executed:
# Check the component definitions to understand what they contain
fd -e tsx web/default/src && grep -l "WaffoSettingsSection\|WaffoPancakeSettingsSection" web/default/src/features/system-settings/integrations/*.tsxRepository: QuantumNous/new-api
Length of output: 429
🏁 Script executed:
# Look for the actual component definitions
cat -n web/default/src/features/system-settings/integrations/payment-settings-section.tsx | grep -i "waffo" | head -20Repository: QuantumNous/new-api
Length of output: 654
Disabled wrapper allows keyboard interaction; nested controls remain focusable despite compliance gate.
The pointer-events-none class only blocks mouse/pointer events; it does not prevent keyboard navigation or focus. The nested WaffoSettingsSection and WaffoPancakeSettingsSection remain fully operable via keyboard when complianceConfirmed is false, bypassing the intended compliance lock. Additionally, aria-disabled on a non-interactive container element does not automatically disable or hide nested interactive controls from assistive technologies.
Add the inert attribute to fully prevent all interaction (pointer and keyboard) when the compliance gate is active:
Proposed fix
<div
className={cn(!complianceConfirmed && 'pointer-events-none opacity-40')}
aria-disabled={!complianceConfirmed}
+ inert={!complianceConfirmed ? '' : undefined}
>📝 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.
| <div | |
| className={cn(!complianceConfirmed && 'pointer-events-none opacity-40')} | |
| aria-disabled={!complianceConfirmed} | |
| > | |
| <WaffoSettingsSection defaultValues={waffoDefaultValues} /> | |
| <Separator /> | |
| <WaffoPancakeSettingsSection | |
| defaultValues={waffoPancakeDefaultValues} | |
| /> | |
| </div> | |
| <div | |
| className={cn(!complianceConfirmed && 'pointer-events-none opacity-40')} | |
| aria-disabled={!complianceConfirmed} | |
| inert={!complianceConfirmed ? '' : undefined} | |
| > | |
| <WaffoSettingsSection defaultValues={waffoDefaultValues} /> | |
| <Separator /> | |
| <WaffoPancakeSettingsSection | |
| defaultValues={waffoPancakeDefaultValues} | |
| /> | |
| </div> |
🤖 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 1471 - 1482, The wrapper currently uses pointer-events-none and
aria-disabled which do not prevent keyboard or assistive interactions; update
the wrapper div (the element that wraps WaffoSettingsSection and
WaffoPancakeSettingsSection) to include the inert attribute when
complianceConfirmed is false (e.g. inert={ !complianceConfirmed }), and also set
aria-hidden={ !complianceConfirmed } to hide content from assistive tech; keep
aria-disabled for explicit state but add inert/aria-hidden to fully block
keyboard focus and screen-reader access; if TypeScript flags the native inert
prop, either cast the props to any or add a JSX.IntrinsicElements declaration
for inert.
Important
📝 变更描述 / Description
(简述:做了什么?为什么这样改能生效?请基于你对代码逻辑的理解来写,避免粘贴未经整理的内容)
🚀 变更类型 / Type of change
🔗 关联任务 / Related Issue
✅ 提交前检查项 / Checklist
Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。📸 运行证明 / Proof of Work
(请在此粘贴截图、关键日志或测试报告,以证明变更生效)
Summary by CodeRabbit
New Features
Bug Fixes
Tests