From b0a0124448488d681992b69b1ce5bfd088807854 Mon Sep 17 00:00:00 2001 From: zhx Date: Mon, 20 Jul 2026 15:46:26 +0800 Subject: [PATCH] fix(withdrawal): validate decimal amount input --- h5/hyapp/withdraw-exchange/script.js | 42 ++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/h5/hyapp/withdraw-exchange/script.js b/h5/hyapp/withdraw-exchange/script.js index a1ff79a..de4b868 100644 --- a/h5/hyapp/withdraw-exchange/script.js +++ b/h5/hyapp/withdraw-exchange/script.js @@ -2,6 +2,7 @@ const API_BASE_URL = "https://jvapi.haiyihy.com"; const EXCHANGE_RATE = 80000; const WITHDRAW_MIN_AMOUNT = 50; + const AMOUNT_FRACTION_DIGITS = 2; const supportedLanguages = ["en", "ar", "tr", "id"]; const languageLabels = { en: "EN", @@ -483,7 +484,32 @@ function formatMoneyInput(value) { const number = Number(value); - return Number.isFinite(number) ? number.toFixed(2) : "0.00"; + return Number.isFinite(number) ? number.toFixed(AMOUNT_FRACTION_DIGITS) : "0.00"; + } + + function normalizeAmountValue(value) { + const raw = String(value || "").trim(); + if (!raw) return ""; + // 金额只接受普通十进制正数;不能把 -50 或 1e2 静默清洗成另一笔正数,避免用户看到值与实际提交值不一致。 + if (/[eE+-]/.test(raw)) return ""; + + const sanitized = raw.replace(/[^\d.]/g, ""); + const dotIndex = sanitized.indexOf("."); + if (dotIndex < 0) return sanitized; + + const integerPart = sanitized.slice(0, dotIndex) || "0"; + const fractionPart = sanitized.slice(dotIndex + 1) + .replace(/\./g, "") + .slice(0, AMOUNT_FRACTION_DIGITS); + return `${integerPart}.${fractionPart}`; + } + + function parseAmountValue(value) { + const raw = String(value || "").trim(); + // input 事件之外仍需校验原始文本,防止脚本赋值或浏览器差异绕过两位小数限制后被后端拒绝。 + if (!/^\d+(?:\.\d{0,2})?$/.test(raw)) return Number.NaN; + const amount = Number(raw); + return Number.isFinite(amount) ? amount : Number.NaN; } function moneyNumber(value) { @@ -502,7 +528,7 @@ } function activeAmount() { - return moneyNumber(activeInput()?.value); + return parseAmountValue(activeInput()?.value); } function formatExchangeRate() { @@ -613,12 +639,12 @@ submitButton.textContent = submitLabels[currentMode]; const amount = activeAmount(); + const invalidAmount = !Number.isFinite(amount) || amount <= 0; const exceedsBalance = amount > availableBalance; const missingWithdrawInfo = currentMode === "withdraw" && !selectedPaymentInfo?.id; const missingPayee = currentMode === "transfer" && !selectedPayee?.id; - submitButton.disabled = !amount || - amount <= 0 || + submitButton.disabled = invalidAmount || exceedsBalance || missingWithdrawInfo || missingPayee || @@ -1167,10 +1193,8 @@ function normalizeAmountInput(event) { const input = event?.target || activeInput(); if (!input) return; - const raw = String(input.value || "").replace(/[^\d.]/g, ""); - const parts = raw.split("."); - let nextValue = parts.length > 2 ? `${parts[0]}.${parts.slice(1).join("")}` : raw; - const amount = Number(nextValue); + let nextValue = normalizeAmountValue(input.value); + const amount = parseAmountValue(nextValue); if (Number.isFinite(amount) && amount > availableBalance) nextValue = formatMoneyInput(availableBalance); input.value = nextValue; updateSubmitState(); @@ -1178,7 +1202,7 @@ function validateAmount(requireMinimum) { const amount = activeAmount(); - if (!amount || amount <= 0) return "invalid_amount"; + if (!Number.isFinite(amount) || amount <= 0) return "invalid_amount"; if (requireMinimum && amount < WITHDRAW_MIN_AMOUNT) return "amount_too_low"; if (amount > availableBalance) return "amount_exceeds_balance"; return "";