fix(withdrawal): validate decimal amount input

This commit is contained in:
zhx 2026-07-20 15:46:26 +08:00
parent 22c188b84f
commit b0a0124448

View File

@ -2,6 +2,7 @@
const API_BASE_URL = "https://jvapi.haiyihy.com"; const API_BASE_URL = "https://jvapi.haiyihy.com";
const EXCHANGE_RATE = 80000; const EXCHANGE_RATE = 80000;
const WITHDRAW_MIN_AMOUNT = 50; const WITHDRAW_MIN_AMOUNT = 50;
const AMOUNT_FRACTION_DIGITS = 2;
const supportedLanguages = ["en", "ar", "tr", "id"]; const supportedLanguages = ["en", "ar", "tr", "id"];
const languageLabels = { const languageLabels = {
en: "EN", en: "EN",
@ -483,7 +484,32 @@
function formatMoneyInput(value) { function formatMoneyInput(value) {
const number = Number(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) { function moneyNumber(value) {
@ -502,7 +528,7 @@
} }
function activeAmount() { function activeAmount() {
return moneyNumber(activeInput()?.value); return parseAmountValue(activeInput()?.value);
} }
function formatExchangeRate() { function formatExchangeRate() {
@ -613,12 +639,12 @@
submitButton.textContent = submitLabels[currentMode]; submitButton.textContent = submitLabels[currentMode];
const amount = activeAmount(); const amount = activeAmount();
const invalidAmount = !Number.isFinite(amount) || amount <= 0;
const exceedsBalance = amount > availableBalance; const exceedsBalance = amount > availableBalance;
const missingWithdrawInfo = currentMode === "withdraw" && !selectedPaymentInfo?.id; const missingWithdrawInfo = currentMode === "withdraw" && !selectedPaymentInfo?.id;
const missingPayee = currentMode === "transfer" && !selectedPayee?.id; const missingPayee = currentMode === "transfer" && !selectedPayee?.id;
submitButton.disabled = !amount || submitButton.disabled = invalidAmount ||
amount <= 0 ||
exceedsBalance || exceedsBalance ||
missingWithdrawInfo || missingWithdrawInfo ||
missingPayee || missingPayee ||
@ -1167,10 +1193,8 @@
function normalizeAmountInput(event) { function normalizeAmountInput(event) {
const input = event?.target || activeInput(); const input = event?.target || activeInput();
if (!input) return; if (!input) return;
const raw = String(input.value || "").replace(/[^\d.]/g, ""); let nextValue = normalizeAmountValue(input.value);
const parts = raw.split("."); const amount = parseAmountValue(nextValue);
let nextValue = parts.length > 2 ? `${parts[0]}.${parts.slice(1).join("")}` : raw;
const amount = Number(nextValue);
if (Number.isFinite(amount) && amount > availableBalance) nextValue = formatMoneyInput(availableBalance); if (Number.isFinite(amount) && amount > availableBalance) nextValue = formatMoneyInput(availableBalance);
input.value = nextValue; input.value = nextValue;
updateSubmitState(); updateSubmitState();
@ -1178,7 +1202,7 @@
function validateAmount(requireMinimum) { function validateAmount(requireMinimum) {
const amount = activeAmount(); 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 (requireMinimum && amount < WITHDRAW_MIN_AMOUNT) return "amount_too_low";
if (amount > availableBalance) return "amount_exceeds_balance"; if (amount > availableBalance) return "amount_exceeds_balance";
return ""; return "";