Skip to content

Comments

feat: add max top-up amount limit for Epay and Stripe#2933

Open
worryzyy wants to merge 3 commits intoQuantumNous:mainfrom
worryzyy:feature/add-topup-max-amount-limit
Open

feat: add max top-up amount limit for Epay and Stripe#2933
worryzyy wants to merge 3 commits intoQuantumNous:mainfrom
worryzyy:feature/add-topup-max-amount-limit

Conversation

@worryzyy
Copy link
Contributor

@worryzyy worryzyy commented Feb 12, 2026

Summary

  • Add configurable maximum top-up amount for both Epay and Stripe payment gateways to prevent users from intentionally placing orders with excessive amounts
  • This is an optional feature: MaxTopUp defaults to 0 (no limit), admins can configure it in payment settings if needed
  • Backend validation with i18n error messages (en, zh-CN, zh-TW)
  • Frontend: inline warning + disable payment buttons when exceeded, skip redundant API calls

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced maximum top-up amount limits configuration
    • Added UI controls to set maximum recharge amounts for both general and payment gateway-specific scenarios
    • Frontend now validates and enforces top-up amount constraints
    • Updated payment method availability logic to respect configured limits
  • Internationalization

    • Expanded multi-language support with new top-up validation messages (English, French, Japanese, Russian, Vietnamese, Simplified Chinese, and Traditional Chinese)

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 12, 2026

Walkthrough

This PR introduces a maximum top-up limit feature across the system. Backend controllers now support configurable max top-up amounts with localized validation messages. Frontend components receive and enforce these limits through UI validation and display. Configuration pages allow administrators to set maximum top-up values for standard and Stripe payments. The implementation includes new helper functions, validation logic, and extensive i18n translations across multiple locales.

Changes

Cohort / File(s) Summary
Backend Top-Up Controllers
controller/topup.go, controller/topup_stripe.go
Added getMaxTopup() and getStripeMaxTopup() helpers. Integrated i18n for localized validation messages (amount too low/high). GetTopUpInfo now returns max_topup and stripe_max_topup. Replaced hard-coded bounds with dynamic max-topup validation using localized error messages.
Backend Configuration
setting/payment_stripe.go, setting/operation_setting/payment_setting.go
Added new exported fields: StripeMaxTopUp and MaxTopUp (both default to 0 meaning no limit).
Option Management
model/option.go
Extended option initialization and update to handle MaxTopUp and StripeMaxTopUp configuration keys.
Backend i18n Keys
i18n/keys.go
Added two new message constants: MsgTopupAmountTooLow and MsgTopupAmountTooHigh.
Backend Translation Files
i18n/locales/en.yaml, i18n/locales/zh-CN.yaml, i18n/locales/zh-TW.yaml
Added localized translations for "topup.amount_too_low" and "topup.amount_too_high" messages with {{.Min}} and {{.Max}} placeholders.
Frontend Top-Up Components
web/src/components/topup/RechargeCard.jsx, web/src/components/topup/index.jsx
Added maxTopUp prop and state, conditional placeholder display for max bounds, validation logic preventing amounts exceeding max, updated disabled states and tooltip messages. Top-up amount validation now enforces max constraints in multiple flows (preTopUp, getAmount, getStripeAmount, onlineTopUp).
Frontend Admin Settings
web/src/pages/Setting/Payment/SettingsPaymentGateway.jsx, web/src/pages/Setting/Payment/SettingsPaymentGatewayStripe.jsx
Added MaxTopUp and StripeMaxTopUp configuration input fields with cross-field validation (max cannot be less than min). Included fields in form submission payload.
Frontend i18n Translations
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-CN.json, web/src/i18n/locales/zh-TW.json
Added five new localization keys per locale: amount cannot exceed max, max cannot be less than min, formatting separator, maximum label, and no-limit indicator.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related PRs

Suggested reviewers

  • seefs001
  • Calcium-Ion

Poem

🐰 A limit most fair, we now declare,
Max top-ups constrained with utmost care,
i18n speaks in tongues so true,
Validation flows through old and new,
The rabbit hops with config delight! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 PR title clearly and specifically describes the main change: adding a configurable maximum top-up amount limit for both Epay and Stripe payment gateways, which aligns with the changeset's primary objective and implementation across backend and frontend.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
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.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
model/option.go (1)

343-356: ⚠️ Potential issue | 🟡 Minor

Clamp negative MaxTopUp values to avoid blocking all top-ups.
Because 0 means “no limit,” a negative value (e.g., misconfigured -1) would effectively reject every top-up. Consider clamping to 0 (or rejecting) when parsing.

✅ Proposed fix (clamp to 0)
 case "MaxTopUp":
-	operation_setting.MaxTopUp, _ = strconv.Atoi(value)
+	maxVal, _ := strconv.Atoi(value)
+	if maxVal < 0 {
+		maxVal = 0
+	}
+	operation_setting.MaxTopUp = maxVal
@@
 case "StripeMaxTopUp":
-	setting.StripeMaxTopUp, _ = strconv.Atoi(value)
+	stripeMax, _ := strconv.Atoi(value)
+	if stripeMax < 0 {
+		stripeMax = 0
+	}
+	setting.StripeMaxTopUp = stripeMax
controller/topup.go (1)

55-65: ⚠️ Potential issue | 🟠 Major

Potential mismatch between API response values and backend validation when QuotaDisplayType is TOKENS.

max_topup (line 59) returns the raw operation_setting.MaxTopUp value (in USD units), but getMaxTopup() (used for server-side validation) converts it to token units when the display type is TOKENS. The frontend uses the raw API value for client-side validation against user input that's in display units, which would cause a mismatch when the display type is TOKENS.

This appears to be a pre-existing pattern (same issue exists for min_topup on line 58), so it's not a regression introduced by this PR, but worth noting for correctness.

Consider returning the converted values in the API response:

Suggested fix
 		"min_topup":           operation_setting.MinTopUp,
-		"max_topup":           operation_setting.MaxTopUp,
+		"max_topup":           getMaxTopup(),
 		"stripe_min_topup":    setting.StripeMinTopUp,
-		"stripe_max_topup":    setting.StripeMaxTopUp,
+		"stripe_max_topup":    getStripeMaxTopup(),
🤖 Fix all issues with AI agents
In `@setting/operation_setting/payment_setting_old.go`:
- Line 18: Remove the stray global variable MaxTopUp from payment_setting_old.go
and add it as a field on the PaymentSetting struct in payment_setting.go (e.g.,
MaxTopUp int with same semantics: 0 means no limit); update any constructors or
JSON/YAML tags and defaulting logic for PaymentSetting to initialize/serialize
this new field and update any code references to use paymentSetting.MaxTopUp
instead of the removed global variable.

In `@web/src/components/topup/index.jsx`:
- Around line 200-203: Add an upper-bound guard to prevent submissions when the
amount exceeds maxTopUp: in the block that currently checks topUpCount <
minTopUp (using variables topUpCount, minTopUp, maxTopUp and function
showError), also validate if topUpCount > maxTopUp, call showError with a
suitable message and return early to avoid opening the confirm modal or sending
requests; apply the same maxTopUp guard inside preTopUp so the pre-check and
confirm flow both reject values above maxTopUp.

In `@web/src/components/topup/RechargeCard.jsx`:
- Around line 347-354: The tooltip uses the raw maxTopUp while elsewhere (line
~292) the UI uses renderQuotaWithAmount(maxTopUp); update the Tooltip content to
use renderQuotaWithAmount(maxTopUp) instead of raw maxTopUp so the max limit is
consistently formatted; locate the Tooltip in RechargeCard.jsx and replace the
unformatted maxTopUp reference with a call to renderQuotaWithAmount(maxTopUp)
(keep the same i18n string concatenation logic).

In `@web/src/pages/Setting/Payment/SettingsPaymentGateway.jsx`:
- Around line 164-166: Before adding MaxTopUp to the options array, validate
that inputs.MaxTopUp is not less than inputs.MinTopUp; if it is, prevent
submission and surface a user-facing error (e.g., set a form validation error or
show a toast) so the invalid configuration cannot be saved. Update the logic
around the options.push for 'MaxTopUp' in SettingsPaymentGateway.jsx to check
inputs.MinTopUp and inputs.MaxTopUp, and only push the MaxTopUp option when
MaxTopUp >= MinTopUp; otherwise block the save flow and display the validation
message.

In `@web/src/pages/Setting/Payment/SettingsPaymentGatewayStripe.jsx`:
- Around line 127-135: Before pushing the StripeMaxTopUp option, validate that
inputs.StripeMaxTopUp is not a positive number smaller than
inputs.StripeMinTopUp; if inputs.StripeMaxTopUp > 0 && inputs.StripeMaxTopUp <
inputs.StripeMinTopUp, prevent the save (do not push into options) and surface a
clear validation error to the user using the component's existing error/display
mechanism (e.g., set form error or show toast). Update the block around
inputs.StripeMaxTopUp/options so the validation runs prior to adding the option
and returns early on failure with a user-facing message like "Max top-up must be
>= Min top-up."

Copy link
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.

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)
web/src/pages/Setting/Payment/SettingsPaymentGateway.jsx (1)

146-173: ⚠️ Potential issue | 🟡 Minor

null value from cleared InputNumber would crash on .toString().

Semi UI's InputNumber can yield null when the user clears the field. The guard on line 171 checks !== '' and !== undefined but not !== null, so null.toString() at line 172 would throw a runtime error.

Proposed fix
-    if (inputs.MaxTopUp !== '' && inputs.MaxTopUp !== undefined) {
+    if (inputs.MaxTopUp != null && inputs.MaxTopUp !== '') {
       options.push({ key: 'MaxTopUp', value: inputs.MaxTopUp.toString() });
     }

Same null guard should be applied to the validation block:

-    if (inputs.MaxTopUp !== '' && inputs.MaxTopUp !== undefined) {
+    if (inputs.MaxTopUp != null && inputs.MaxTopUp !== '') {
       if (inputs.MaxTopUp > 0 && inputs.MaxTopUp < inputs.MinTopUp) {
🤖 Fix all issues with AI agents
In `@web/src/components/topup/index.jsx`:
- Around line 588-590: The check that returns early when value > maxTopUp leaves
the previous computed amount displayed; update the amount state to a cleared
value before returning so the UI doesn't show a stale payment amount (i.e., set
the component's amount/state to an empty/zero value whenever value > maxTopUp),
and apply the same change to the getStripeAmount function where it does the same
value > maxTopUp early return; ensure you reference and update the shared amount
state (the amount variable/state setter) in both places before the return.
🧹 Nitpick comments (3)
controller/topup.go (1)

151-158: getMinTopup() is called twice — consider caching in a local variable.

Both getMinTopup() (lines 151-152) and getMaxTopup() (line 155) perform setting lookups and potential arithmetic. Storing the result avoids redundant computation and a theoretical TOCTOU inconsistency if the setting changes between calls.

The same pattern appears in RequestAmount (lines 343-348).

♻️ Suggested refactor
 func RequestEpay(c *gin.Context) {
 	var req EpayRequest
 	err := c.ShouldBindJSON(&req)
 	if err != nil {
 		c.JSON(200, gin.H{"message": "error", "data": "参数错误"})
 		return
 	}
-	if req.Amount < getMinTopup() {
-		c.JSON(200, gin.H{"message": "error", "data": i18n.T(c, i18n.MsgTopupAmountTooLow, map[string]any{"Min": getMinTopup()})})
+	minTopup := getMinTopup()
+	if req.Amount < minTopup {
+		c.JSON(200, gin.H{"message": "error", "data": i18n.T(c, i18n.MsgTopupAmountTooLow, map[string]any{"Min": minTopup})})
 		return
 	}
web/src/i18n/locales/ja.json (1)

438-442: New i18n keys break the file's existing sort order.

The five new keys are grouped together near "充值数量不能小于", but the rest of the file is sorted. For example, "0 表示不限制" should be near line 31–33 with other "0…" keys, and "最高充值数量" belongs near other "最…" entries (around line 512). Consider running the project's i18n sort tooling (bun run i18n:*) to re-sort the file and keep it consistent with existing conventions.

web/src/components/topup/index.jsx (1)

204-211: Inconsistent formatting between min and max error messages.

The min-topup error displays a raw number (minTopUp), while the max-topup error uses renderQuotaWithAmount(maxTopUp) which prepends a currency symbol (e.g., $5). The same inconsistency exists in preTopUp (lines 176 vs 180). Consider using renderQuotaWithAmount for both or neither, so users see a uniform format.

Proposed fix
     if (topUpCount < minTopUp) {
-      showError(t('充值数量不能小于') + minTopUp);
+      showError(t('充值数量不能小于') + ' ' + renderQuotaWithAmount(minTopUp));
       return;
     }

Apply the same in preTopUp at line 176.

Comment on lines +588 to +590
if (maxTopUp > 0 && value > maxTopUp) {
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Stale amount displayed when the entered value exceeds the max.

When value > maxTopUp, the function returns without updating amount. If the user previously entered a valid value (e.g., $50 → amount shows ¥365), then changes to an invalid value exceeding maxTopUp, the old payment amount remains displayed — potentially misleading.

Consider resetting the amount before returning:

Proposed fix
     if (maxTopUp > 0 && value > maxTopUp) {
+      setAmount(0);
       return;
     }

Apply the same in getStripeAmount (line 617–619).

📝 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 (maxTopUp > 0 && value > maxTopUp) {
return;
}
if (maxTopUp > 0 && value > maxTopUp) {
setAmount(0);
return;
}
🤖 Prompt for AI Agents
In `@web/src/components/topup/index.jsx` around lines 588 - 590, The check that
returns early when value > maxTopUp leaves the previous computed amount
displayed; update the amount state to a cleared value before returning so the UI
doesn't show a stale payment amount (i.e., set the component's amount/state to
an empty/zero value whenever value > maxTopUp), and apply the same change to the
getStripeAmount function where it does the same value > maxTopUp early return;
ensure you reference and update the shared amount state (the amount
variable/state setter) in both places before the return.

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