Skip to content

修正了充值页“原价 / 实付 / 折扣 / 充值分组倍率”相关的展示不一致问题#4644

Open
laiyonghao wants to merge 5 commits intoQuantumNous:mainfrom
laiyonghao:bugfix/topup-display-price-ratio
Open

修正了充值页“原价 / 实付 / 折扣 / 充值分组倍率”相关的展示不一致问题#4644
laiyonghao wants to merge 5 commits intoQuantumNous:mainfrom
laiyonghao:bugfix/topup-display-price-ratio

Conversation

@laiyonghao
Copy link
Copy Markdown

@laiyonghao laiyonghao commented May 6, 2026

⚠️ 提交说明 / PR Notice

把充值页在 classic 和 default 两套前端里的显示和计算逻辑对齐,重点修正了“原价 / 实付 / 折扣 / 充值分组倍率”相关的展示不一致问题。改动范围只涉及充值相关逻辑,没有夹带无关修改。

📝 变更描述 / Description

这次修改主要做了三件事:

  1. 后端在充值信息接口里补充返回当前用户生效的充值分组倍率,前端不用再自己猜,展示口径直接和后端实际结算保持一致。
  2. classic 充值页修正了预设卡、输入框下方金额说明、以及选择预设后第一次手动修改金额不刷新的问题。
  3. default 充值页同步做了同样的金额口径和展示调整,保证两套前端在相同配置下显示一致。

现在无论是预设卡、输入框下方金额说明,还是支付确认弹窗,原价和实付金额都按同一套规则计算:
充值数量 × 充值价格 × 充值分组倍率,再叠加充值金额折扣。

🚀 变更类型 / 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

本地已完成以下验证:

  • go test ./controller/... 通过
  • web/classicbun run build 通过
  • web/defaultbun run build 通过
  • 重启后端后,classic / default 两套前端都已加载最新改动
  • 手动验证通过:
    • 预设卡原价、实付、折扣显示正确
    • 输入框下方金额说明可显示原价
    • classic 中选择预设后,第一次手动修改金额即可正确刷新显示
    • classic 与 default 两套前端行为一致

Summary by CodeRabbit

  • New Features

    • Backend-driven group ratio applied to top-up pricing; UI shows original price, discount, and final payable amount.
    • Multi-currency preset pricing with dynamic conversions and clearer currency/rate display.
    • New "Amount to pay" block showing effective original amount for custom selections.
  • Bug Fixes

    • Preset synchronization lock to prevent rapid duplicate top-up updates.
  • Refactor

    • Pricing logic refined for consistent discount, group-ratio, and currency calculations.

Copilot AI review requested due to automatic review settings May 6, 2026 10:49
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 6, 2026

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7a47c02c-874a-43b5-b752-131d4413e3ef

📥 Commits

Reviewing files that changed from the base of the PR and between e8792c7 and 1c8eae5.

📒 Files selected for processing (3)
  • controller/topup.go
  • web/classic/src/components/topup/index.jsx
  • web/default/src/features/wallet/lib/format.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • controller/topup.go
  • web/classic/src/components/topup/index.jsx
  • web/default/src/features/wallet/lib/format.ts

Walkthrough

The PR exposes a backend-computed topup_group_ratio, parses and stores discounts safely on the frontend, extends preset pricing to include the group ratio and USD rate, and updates classic and default UIs to render original price, payable amount, discounts, and a preset synchronization lock.

Changes

Group-Based Top-Up Discount Aggregation

Layer / File(s) Summary
Data Shape & Pricing API
web/default/src/features/wallet/types.ts, web/default/src/features/wallet/lib/format.ts
Add optional topup_group_ratio to TopupInfo; extend calculatePresetPricing signature with topupGroupRatio and usdExchangeRate; originalPrice multiplies by topupGroupRatio.
Backend Computation
controller/topup.go
GetTopUpInfo loads the user's group, computes topupGroupRatio, returns topup_group_ratio in response, and errors if group load fails.
Discount Parsing & State
web/classic/src/components/topup/index.jsx
Add parseDiscountMap to normalize backend discount; store parsed numeric discount map and topup_group_ratio in topupInfo; build numeric preset values and per-amount discounts.
Classic UI Preset Rendering
web/classic/src/components/topup/RechargeCard.jsx
Add preset sync lock and skeleton handling; rework preset rendering for multi-currency pricing using per-preset discount and topup_group_ratio; display original price, you-pay amount, savings, and discount badges.
Default UI Form Integration
web/default/src/features/wallet/components/recharge-form-card.tsx
Compute topupGroupRatio and currentOriginalAmount; normalize discounts from multiple sources; call extended pricing API; update preset buttons and custom amount UI to show original and payable prices and discount state.

Sequence Diagram

sequenceDiagram
    participant Client as Client/Frontend
    participant Controller as Backend Controller
    participant UserGroup as User Group Service
    participant PricingCalc as Pricing Calculator
    participant UI as UI Renderer

    Client->>Controller: GetTopUpInfo(userId)
    Controller->>UserGroup: LoadUserGroup(userId)
    UserGroup-->>Controller: Group Info
    Controller->>Controller: Compute topupGroupRatio
    Controller-->>Client: TopupInfo {topup_group_ratio, discount, ...}
    
    Client->>PricingCalc: SelectPreset(presetValue)
    Note over Client: Parse discounts<br/>Extract topup_group_ratio
    Client->>PricingCalc: CalculatePresetPricing(presetValue, priceRatio, discount, topupGroupRatio)
    PricingCalc->>PricingCalc: originalPrice = presetValue × priceRatio × topupGroupRatio
    PricingCalc->>PricingCalc: actualPrice = originalPrice × discount
    PricingCalc-->>Client: {originalPrice, actualPrice, displayValue}
    
    Client->>UI: Render Preset Card
    Note over UI: Show original price,<br/>actual payable amount,<br/>savings/discount
    UI-->>Client: Updated UI with pricing
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~35 minutes

Possibly Related PRs

Suggested Reviewers

  • seefs001
  • Calcium-Ion

Poem

A rabbit hops through pricing trees,
Ratios whisper in the breeze,
Discounts multiply and neatly align,
Presets sync, currencies shine fine.
Hop on — the topup flows, calm and kind 🐰

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main purpose of the PR: fixing display inconsistencies for original price, actual payment amount, discount, and top-up group ratio on the recharge page. It directly aligns with the changeset modifications across both frontend implementations.
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.

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

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

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

本 PR 旨在对齐 classic 与 default 两套前端在充值页中“原价 / 实付 / 折扣 / 充值分组倍率”的展示与计算口径,并由后端明确下发当前用户生效的充值分组倍率,从而减少前端自行推断导致的不一致。

Changes:

  • 后端 /api/user/topup/info 补充返回当前用户的 topup_group_ratio,用于前端展示与计算对齐。
  • default 前端:预设卡与输入框说明补充“原价/实付”展示,并在预设金额定价计算中纳入分组倍率。
  • classic 前端:解析折扣映射、展示原价/实付,并通过锁避免选择预设后首次手动修改不刷新的问题。

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
controller/topup.go 在充值信息接口中返回当前用户生效的充值分组倍率 topup_group_ratio
web/default/src/features/wallet/types.ts TopupInfo 增加 topup_group_ratio 字段类型定义
web/default/src/features/wallet/lib/format.ts 预设金额定价计算纳入 topupGroupRatio
web/default/src/features/wallet/components/recharge-form-card.tsx 预设卡与输入框下方金额说明增加“原价/实付”展示,并纳入分组倍率
web/classic/src/components/topup/RechargeCard.jsx classic 充值页展示原价/实付、修复预设后首次手动修改不刷新的同步问题,并对齐倍率/折扣口径
web/classic/src/components/topup/index.jsx 解析并规范化折扣映射,存储并使用后端下发的 topup_group_ratio,选择预设时按新口径计算

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +205 to +207
topupInfo?.discount?.[preset.value] ??
topupInfo?.discount?.[String(preset.value)] ??
preset.discount
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
controller/topup.go (1)

26-32: ⚡ Quick win

Graceful fallback recommended for GetUserGroup failures.

GetTopUpInfo is purely informational — it powers the topup page. If model.GetUserGroup returns an error (e.g., transient DB hiccup, stale session, or Redis<->DB inconsistency), the entire topup page now fails to load instead of falling back to a sane default, which is a UX regression compared to the pre-PR behavior where this lookup wasn't required at all.

Since common.GetTopupGroupRatio already returns 1 for unknown groups and getPayMoney defensively resets 0 ratios to 1, mirroring that defensiveness here keeps the page functional and only loses the (cosmetic) per-user discount preview.

♻️ Proposed graceful fallback
 func GetTopUpInfo(c *gin.Context) {
 	id := c.GetInt("id")
-	group, err := model.GetUserGroup(id, true)
-	if err != nil {
-		common.ApiError(c, err)
-		return
-	}
-	topupGroupRatio := common.GetTopupGroupRatio(group)
+	topupGroupRatio := 1.0
+	if group, err := model.GetUserGroup(id, true); err == nil {
+		if r := common.GetTopupGroupRatio(group); r > 0 {
+			topupGroupRatio = r
+		}
+	} else {
+		common.SysLog(fmt.Sprintf("GetTopUpInfo: failed to load user group for id=%d, falling back to ratio=1: %s", id, err.Error()))
+	}
🤖 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 26 - 32, GetUserGroup errors should not
abort GetTopUpInfo; instead fall back to a nil/unknown group so the page still
renders. Replace the current error branch in controller/topup.go (the call to
model.GetUserGroup and the ApiError return) with logic that on error logs the
failure (or ignores it) and sets group to a nil/unknown value so you can still
call common.GetTopupGroupRatio(group) to obtain the safe default
(common.GetTopupGroupRatio already returns 1 for unknown groups). Keep the
function names model.GetUserGroup and common.GetTopupGroupRatio to locate the
change.
web/classic/src/components/topup/index.jsx (1)

720-723: 💤 Low value

Inconsistent fallback for missing discounts (|| vs ??).

Line 722 uses parsedDiscounts[amount] || 1.0 while the new selectPresetAmount (lines 896-902) uses ?? with a Number.isFinite guard. With parseDiscountMap guaranteeing values are finite numbers, both paths agree on undefined → 1.0, but they disagree on 0 (treated as "missing → 1.0" by || and "valid 0% price" by ??). The same || is reused on line 951 for discountRate. While a discount of 0 is unusual, the divergence is a code smell that will eventually bite if such a config is introduced.

♻️ Proposed alignment
-    discount: parsedDiscounts[amount] || 1.0,
+    discount: Number.isFinite(parsedDiscounts[amount])
+      ? parsedDiscounts[amount]
+      : 1.0,

And similarly for the discountRate prop on line 951.

Also applies to: 891-907

🤖 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/classic/src/components/topup/index.jsx` around lines 720 - 723, Replace
the loose-fallback (||) with the same finite-number guard used in
selectPresetAmount so zero discounts are preserved: where customPresets builds
discount using parsedDiscounts[amount] and where discountRate is derived, change
the logic to use Number.isFinite(parsedDiscounts[amount]) ?
parsedDiscounts[amount] : 1.0 (mirroring selectPresetAmount and parseDiscountMap
assumptions) so undefined maps to 1.0 but 0 remains a valid discount.
web/classic/src/components/topup/RechargeCard.jsx (1)

440-491: ⚡ Quick win

Hoist topupGroupRatio and topupRate to avoid recomputation per preset.

topupGroupRatio is already computed at component scope on line 135 as currentTopupGroupRatio, and the topupRate calculation appears identically at lines 440-444 (currency-rate display) and 487-491 (inside the preset .map). Recomputing them on every preset render is harmless but adds visual noise and risks the two definitions drifting apart in future edits — for example, if someone tightens the validation in one place but not the other, presets and the rate display could disagree.

♻️ Proposed refactor
   const currentTopupGroupRatio = Number(topupInfo?.topup_group_ratio) || 1;
+  const safeTopupRate =
+    Number.isFinite(Number(priceRatio)) && Number(priceRatio) > 0
+      ? Number(priceRatio)
+      : 1;
   const currentOriginalAmount =
-    Number(topUpCount || 0) * Number(priceRatio || 0) * currentTopupGroupRatio;
+    Number(topUpCount || 0) * safeTopupRate * currentTopupGroupRatio;

Then in the rate-display IIFE and inside the preset .map, replace the inline topupRate/topupGroupRatio definitions with the hoisted safeTopupRate/currentTopupGroupRatio values.

🤖 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/classic/src/components/topup/RechargeCard.jsx` around lines 440 - 491,
Hoist the repeated computations for topupGroupRatio and topupRate so presets and
the rate display use a single source of truth: remove the duplicated local
definitions inside the IIFE rate-display and inside the presetAmounts.map
callback and reference the component-scoped currentTopupGroupRatio and a hoisted
safeTopupRate (compute once using getCurrencyConfig()/priceRatio validation
logic) instead; update places that currently declare topupGroupRatio and
topupRate to use currentTopupGroupRatio and safeTopupRate (or rename
consistently) so the rate display and per-preset calculations (in the
presetAmounts.map) share the same validated values.
🤖 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.

Nitpick comments:
In `@controller/topup.go`:
- Around line 26-32: GetUserGroup errors should not abort GetTopUpInfo; instead
fall back to a nil/unknown group so the page still renders. Replace the current
error branch in controller/topup.go (the call to model.GetUserGroup and the
ApiError return) with logic that on error logs the failure (or ignores it) and
sets group to a nil/unknown value so you can still call
common.GetTopupGroupRatio(group) to obtain the safe default
(common.GetTopupGroupRatio already returns 1 for unknown groups). Keep the
function names model.GetUserGroup and common.GetTopupGroupRatio to locate the
change.

In `@web/classic/src/components/topup/index.jsx`:
- Around line 720-723: Replace the loose-fallback (||) with the same
finite-number guard used in selectPresetAmount so zero discounts are preserved:
where customPresets builds discount using parsedDiscounts[amount] and where
discountRate is derived, change the logic to use
Number.isFinite(parsedDiscounts[amount]) ? parsedDiscounts[amount] : 1.0
(mirroring selectPresetAmount and parseDiscountMap assumptions) so undefined
maps to 1.0 but 0 remains a valid discount.

In `@web/classic/src/components/topup/RechargeCard.jsx`:
- Around line 440-491: Hoist the repeated computations for topupGroupRatio and
topupRate so presets and the rate display use a single source of truth: remove
the duplicated local definitions inside the IIFE rate-display and inside the
presetAmounts.map callback and reference the component-scoped
currentTopupGroupRatio and a hoisted safeTopupRate (compute once using
getCurrencyConfig()/priceRatio validation logic) instead; update places that
currently declare topupGroupRatio and topupRate to use currentTopupGroupRatio
and safeTopupRate (or rename consistently) so the rate display and per-preset
calculations (in the presetAmounts.map) share the same validated values.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 49067ab2-6480-4f53-afcb-11a64edb388d

📥 Commits

Reviewing files that changed from the base of the PR and between 446a842 and e8792c7.

📒 Files selected for processing (6)
  • controller/topup.go
  • web/classic/src/components/topup/RechargeCard.jsx
  • web/classic/src/components/topup/index.jsx
  • web/default/src/features/wallet/components/recharge-form-card.tsx
  • web/default/src/features/wallet/lib/format.ts
  • web/default/src/features/wallet/types.ts

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