921 lines
33 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

(function () {
const API_BASE_URL = "https://jvapi.haiyihy.com";
const EXCHANGE_RATE = 80000;
const supportedLanguages = ["en", "ar", "tr", "id"];
const languageLabels = {
en: "EN",
ar: "AR",
tr: "TR",
id: "ID"
};
const translations = {
en: {
page_label: "Withdraw and exchange",
back: "Back",
title: "Withdraw/Exchange",
language_button_aria: "Change language",
available_income: "salary",
bd_salary: "BD salary",
bd_leader_salary: "BD Leader salary",
withdraw: "Withdraw",
exchange: "Exchange",
transfer: "Transfer",
withdraw_amount: "Withdraw amount",
exchange_amount: "Exchange amount",
transfer_amount: "Transfer amount",
transfer_to: "Transfer to",
search: "Search",
searching: "Searching...",
minimum: "Minimum 50 USD",
all: "All",
exchange_rate: "Exchange rate",
payment_method: "USDT TRC20 address",
payment_method_subtitle: "Withdrawals are sent to your TRC20 USDT address.",
no_payment_info: "No USDT address",
no_payment_info_copy: "Fill in a TRC20 USDT address before withdrawing.",
trc20_address: "TRC20 address",
save_address: "Save address",
saving: "Saving...",
usdt_only: "USDT only",
withdraw_now: "Withdraw now",
exchange_now: "Exchange now",
transfer_now: "Transfer now",
submitting: "Submitting...",
payee: "Payee",
uid_prefix: "UID:",
missing_token: "Missing token",
load_balance_failed: "Failed to load balance",
load_payment_failed: "Failed to load payment info",
missing_payment_info: "Please add a TRC20 USDT address",
invalid_address: "Please enter a valid TRC20 USDT address",
invalid_amount: "Please enter a valid amount",
amount_too_low: "Minimum withdrawal amount is 50 USD",
amount_exceeds_balance: "Amount exceeds salary",
payee_not_found: "User not found",
missing_payee: "Please search and select a user",
address_saved: "Address saved",
withdraw_success: "Withdrawal submitted",
withdraw_failed: "Withdrawal failed",
exchange_success: "Exchange success",
exchange_failed: "Exchange failed",
transfer_success: "Transfer success",
transfer_failed: "Transfer failed"
},
ar: {
page_label: "السحب والتبديل",
back: "رجوع",
title: "سحب/تبديل",
language_button_aria: "تغيير اللغة",
available_income: "الراتب",
bd_salary: "راتب BD",
bd_leader_salary: "راتب قائد BD",
withdraw: "سحب",
exchange: "تبديل",
transfer: "تحويل",
withdraw_amount: "مبلغ السحب",
exchange_amount: "مبلغ التبديل",
transfer_amount: "مبلغ التحويل",
transfer_to: "تحويل إلى",
search: "بحث",
searching: "جار البحث...",
minimum: "الحد الأدنى 50 USD",
all: "الكل",
exchange_rate: "سعر التبديل",
payment_method: "عنوان USDT TRC20",
payment_method_subtitle: "يتم إرسال السحب إلى عنوان USDT TRC20.",
no_payment_info: "لا يوجد عنوان USDT",
no_payment_info_copy: "أدخل عنوان USDT TRC20 قبل السحب.",
trc20_address: "عنوان TRC20",
save_address: "حفظ العنوان",
saving: "جار الحفظ...",
usdt_only: "USDT فقط",
withdraw_now: "اسحب الآن",
exchange_now: "بدل الآن",
transfer_now: "حوّل الآن",
submitting: "جار الإرسال...",
payee: "المستفيد",
uid_prefix: "UID:",
missing_token: "رمز الدخول مفقود",
load_balance_failed: "فشل تحميل الرصيد",
load_payment_failed: "فشل تحميل معلومات الدفع",
missing_payment_info: "يرجى إضافة عنوان USDT TRC20",
invalid_address: "يرجى إدخال عنوان USDT TRC20 صالح",
invalid_amount: "يرجى إدخال مبلغ صالح",
amount_too_low: "الحد الأدنى للسحب هو 50 USD",
amount_exceeds_balance: "المبلغ يتجاوز الراتب",
payee_not_found: "لم يتم العثور على المستخدم",
missing_payee: "يرجى البحث واختيار مستخدم",
address_saved: "تم حفظ العنوان",
withdraw_success: "تم إرسال طلب السحب",
withdraw_failed: "فشل السحب",
exchange_success: "تم التبديل",
exchange_failed: "فشل التبديل",
transfer_success: "تم التحويل",
transfer_failed: "فشل التحويل"
},
tr: {
page_label: "Çekme ve değişim",
back: "Geri",
title: "Çek/Değiştir",
language_button_aria: "Dili değiştir",
available_income: "Maaş",
bd_salary: "BD maaşı",
bd_leader_salary: "BD Leader maaşı",
withdraw: "Withdraw",
exchange: "Exchange",
transfer: "Transfer",
withdraw_amount: "Withdraw amount",
exchange_amount: "Exchange amount",
transfer_amount: "Transfer amount",
transfer_to: "Transfer to",
search: "Search",
searching: "Searching...",
minimum: "Minimum 50 USD",
all: "All",
exchange_rate: "Exchange rate",
payment_method: "USDT TRC20 address",
payment_method_subtitle: "Withdrawals are sent to your TRC20 USDT address.",
no_payment_info: "No USDT address",
no_payment_info_copy: "Fill in a TRC20 USDT address before withdrawing.",
trc20_address: "TRC20 address",
save_address: "Save address",
saving: "Saving...",
usdt_only: "USDT only",
withdraw_now: "Withdraw now",
exchange_now: "Exchange now",
transfer_now: "Transfer now",
submitting: "Submitting...",
payee: "Payee",
uid_prefix: "UID:",
missing_token: "Missing token",
load_balance_failed: "Failed to load balance",
load_payment_failed: "Failed to load payment info",
missing_payment_info: "Please add a TRC20 USDT address",
invalid_address: "Please enter a valid TRC20 USDT address",
invalid_amount: "Please enter a valid amount",
amount_too_low: "Minimum withdrawal amount is 50 USD",
amount_exceeds_balance: "Amount exceeds salary",
payee_not_found: "User not found",
missing_payee: "Please search and select a user",
address_saved: "Address saved",
withdraw_success: "Withdrawal submitted",
withdraw_failed: "Withdrawal failed",
exchange_success: "Exchange success",
exchange_failed: "Exchange failed",
transfer_success: "Transfer success",
transfer_failed: "Transfer failed"
},
id: {
page_label: "Tarik dan tukar",
back: "Kembali",
title: "Tarik/Tukar",
language_button_aria: "Ubah bahasa",
available_income: "Gaji",
bd_salary: "Gaji BD",
bd_leader_salary: "Gaji BD Leader",
withdraw: "Tarik",
exchange: "Tukar",
transfer: "Transfer",
withdraw_amount: "Jumlah penarikan",
exchange_amount: "Jumlah penukaran",
transfer_amount: "Jumlah transfer",
transfer_to: "Transfer ke",
search: "Cari",
searching: "Mencari...",
minimum: "Minimum 50 USD",
all: "Semua",
exchange_rate: "Kurs penukaran",
payment_method: "Alamat USDT TRC20",
payment_method_subtitle: "Penarikan dikirim ke alamat USDT TRC20 Anda.",
no_payment_info: "Tidak ada alamat USDT",
no_payment_info_copy: "Isi alamat USDT TRC20 sebelum menarik.",
trc20_address: "Alamat TRC20",
save_address: "Simpan alamat",
saving: "Menyimpan...",
usdt_only: "Hanya USDT",
withdraw_now: "Tarik sekarang",
exchange_now: "Tukar sekarang",
transfer_now: "Transfer sekarang",
submitting: "Mengirim...",
payee: "Penerima",
uid_prefix: "UID:",
missing_token: "Token tidak ditemukan",
load_balance_failed: "Gagal memuat saldo",
load_payment_failed: "Gagal memuat informasi pembayaran",
missing_payment_info: "Tambahkan alamat USDT TRC20",
invalid_address: "Masukkan alamat USDT TRC20 yang valid",
invalid_amount: "Masukkan jumlah yang valid",
amount_too_low: "Minimum penarikan adalah 50 USD",
amount_exceeds_balance: "Jumlah melebihi gaji",
payee_not_found: "Pengguna tidak ditemukan",
missing_payee: "Cari dan pilih pengguna",
address_saved: "Alamat disimpan",
withdraw_success: "Penarikan terkirim",
withdraw_failed: "Penarikan gagal",
exchange_success: "Penukaran berhasil",
exchange_failed: "Penukaran gagal",
transfer_success: "Transfer berhasil",
transfer_failed: "Transfer gagal"
}
};
const params = readURLParams();
let currentLang = normalizeLanguage(params.get("lang"));
let currentMode = normalizeMode(params.get("mode")) || "withdraw";
const pageIdentity = normalizeIdentity(params.get("identity"));
let currentSalaryType = normalizeSalaryType(params.get("salaryType")) || defaultSalaryTypeForIdentity(pageIdentity);
let currentMessages = translations[currentLang] || translations.en;
let availableBalance = 0;
let salaryBalances = {};
let selectedPaymentInfo = null;
let selectedPayee = null;
let currentProfile = null;
let isSubmitting = false;
let isSavingAddress = false;
let isSearchingPayee = false;
const inputs = {
withdraw: document.querySelector("#withdrawAmount"),
exchange: document.querySelector("#exchangeAmount"),
transfer: document.querySelector("#transferAmount")
};
const balanceAmount = document.querySelector("#balanceAmount");
const emptyState = document.querySelector(".empty-address-state");
const addressForm = document.querySelector("#addressForm");
const trc20AddressInput = document.querySelector("#trc20Address");
const saveAddressButton = document.querySelector("#saveAddressButton");
const boundCard = document.querySelector(".bound-address-card");
const paymentSection = document.querySelector("[data-payment-section]");
const submitButton = document.querySelector(".submit-button");
const toast = document.querySelector(".toast");
const payeeResult = document.querySelector("#payeeResult");
const transferAccountInput = document.querySelector("#transferAccount");
const searchPayeeButton = document.querySelector("#searchPayeeButton");
const exchangeRateText = document.querySelector("#exchangeRateText");
const salarySelector = document.querySelector("#salarySelector");
function readURLParams() {
const result = new URLSearchParams(window.location.search);
const hashQuery = window.location.hash.includes("?") ? window.location.hash.split("?")[1] : "";
const hashParams = new URLSearchParams(hashQuery);
hashParams.forEach((value, key) => {
if (!result.has(key)) result.set(key, value);
});
return result;
}
function readRawParam(name) {
const queries = [window.location.search.replace(/^\?/, "")];
if (window.location.hash.includes("?")) queries.push(window.location.hash.split("?")[1]);
for (const query of queries) {
for (const part of query.split("&")) {
if (!part) continue;
const equalIndex = part.indexOf("=");
const rawKey = equalIndex >= 0 ? part.slice(0, equalIndex) : part;
const rawValue = equalIndex >= 0 ? part.slice(equalIndex + 1) : "";
let decodedKey = "";
try {
decodedKey = decodeURIComponent(rawKey);
} catch (error) {
continue;
}
if (decodedKey !== name) continue;
try {
return decodeURIComponent(rawValue.replace(/\+/g, "%2B"));
} catch (error) {
return rawValue;
}
}
}
return "";
}
function normalizeLanguage(lang) {
const value = String(lang || "en").toLowerCase();
return supportedLanguages.includes(value) ? value : "en";
}
function normalizeMode(mode) {
return ["withdraw", "exchange", "transfer"].includes(mode) ? mode : "";
}
function normalizeIdentity(identity) {
const value = String(identity || "").trim().toUpperCase();
return ["HOST", "BD", "BD_LEADER", "ADMIN"].includes(value) ? value : "";
}
function normalizeSalaryType(type) {
const value = String(type || "").trim().toUpperCase();
return ["HOST_SALARY", "BD_SALARY", "BD_LEADER_SALARY", "ADMIN_SALARY"].includes(value) ? value : "";
}
function defaultSalaryTypeForIdentity(identity) {
if (identity === "BD_LEADER") return "BD_LEADER_SALARY";
if (identity === "BD") return "BD_SALARY";
if (identity === "ADMIN") return "ADMIN_SALARY";
return "";
}
function isSalaryAccountMode() {
return Boolean(currentSalaryType && currentSalaryType !== "HOST_SALARY");
}
function activeSalaryType() {
return currentSalaryType || "HOST_SALARY";
}
function responseBody(data) {
return data?.body ?? data?.data ?? null;
}
function apiBaseURL() {
const override = window.HYAPP_API_BASE_URL || params.get("apiBase");
if (override) return String(override).replace(/\/+$/, "");
return API_BASE_URL;
}
function normalizeAuthorization(token) {
const value = String(token || "").trim();
if (!value) return "";
return /^bearer\s+/i.test(value) ? value : `Bearer ${value}`;
}
function authHeadersFromToken(token, hasBody) {
const headers = {
Authorization: normalizeAuthorization(token),
"req-lang": currentLang,
"req-client": "H5",
"req-version": "V2",
"req-zone": Intl.DateTimeFormat().resolvedOptions().timeZone || "Asia/Shanghai",
"req-app-intel": "version=1.0.0;build=1;model=H5;sysVersion=Web;channel=Web",
"req-sys-origin": "origin=ATYOU;originChild=ATYOU"
};
if (hasBody) headers["content-type"] = "application/json";
return headers;
}
async function requestJSON(path, options = {}) {
const token = readRawParam("token");
if (!token) throw new Error(message("missing_token", "Missing token"));
const response = await fetch(`${apiBaseURL()}${path}`, {
method: options.method || "GET",
cache: "no-store",
headers: authHeadersFromToken(token, options.body !== undefined),
body: options.body === undefined ? undefined : JSON.stringify(options.body)
});
const data = await response.json().catch(() => ({}));
if (!response.ok || data.status === false) {
const error = new Error(data.errorMsg || data.message || `Request failed: ${response.status}`);
error.response = data;
throw error;
}
return data;
}
function message(key, fallback) {
return currentMessages[key] || translations.en[key] || fallback || key;
}
function formatMoney(value) {
const number = Number(value);
return Number.isFinite(number) ? number.toFixed(2) : "0.00";
}
function moneyNumber(value) {
const number = Number(value);
return Number.isFinite(number) ? number : 0;
}
function activeInput() {
return inputs[currentMode] || null;
}
function activeAmount() {
return moneyNumber(activeInput()?.value);
}
function formatExchangeRate() {
return `1$ = ${EXCHANGE_RATE} golds`;
}
function salaryBalanceValue(type) {
const balance = salaryBalances[type] || {};
return moneyNumber(balance.availableBalance ?? balance.balance ?? balance.currentSalary ?? 0);
}
function renderSalarySelector() {
if (!salarySelector) return;
const showSelector = pageIdentity === "BD_LEADER";
salarySelector.hidden = !showSelector;
if (!showSelector) return;
salarySelector.querySelectorAll("[data-salary-type]").forEach((button) => {
const type = normalizeSalaryType(button.dataset.salaryType);
const active = type === currentSalaryType;
const label = message(type === "BD_SALARY" ? "bd_salary" : "bd_leader_salary");
button.classList.toggle("active", active);
button.setAttribute("aria-pressed", String(active));
button.textContent = `${label} $${formatMoney(salaryBalanceValue(type))}`;
});
}
function setActiveSalaryType(type) {
const nextType = normalizeSalaryType(type);
if (!nextType || nextType === currentSalaryType) return;
currentSalaryType = nextType;
renderBalance(salaryBalanceValue(nextType));
}
function isValidTrc20Address(value) {
return /^T[1-9A-HJ-NP-Za-km-z]{33}$/.test(String(value || "").trim());
}
function maskAddress(value) {
const text = String(value || "").trim();
if (text.length <= 12) return text || "-";
return `${text.slice(0, 6)}...${text.slice(-6)}`;
}
function applyLanguage(lang) {
currentLang = normalizeLanguage(lang);
currentMessages = translations[currentLang] || translations.en;
document.documentElement.lang = currentLang;
document.documentElement.dir = currentLang === "ar" ? "rtl" : "ltr";
document.querySelectorAll("[data-i18n]").forEach((node) => {
const value = currentMessages[node.dataset.i18n];
if (value) node.textContent = value;
});
document.querySelectorAll("[data-i18n-aria]").forEach((node) => {
const value = currentMessages[node.dataset.i18nAria];
if (value) node.setAttribute("aria-label", value);
});
const languageButton = document.querySelector(".language-button");
if (languageButton) {
languageButton.textContent = languageLabels[currentLang];
languageButton.setAttribute("aria-label", message("language_button_aria", "Change language"));
}
document.querySelectorAll(".language-menu [data-lang]").forEach((button) => {
const isActive = button.dataset.lang === currentLang;
button.classList.toggle("is-active", isActive);
button.setAttribute("aria-pressed", String(isActive));
});
if (exchangeRateText) exchangeRateText.textContent = formatExchangeRate();
renderSalarySelector();
renderPayee();
updateSubmitState();
}
function setMode(mode) {
const nextMode = normalizeMode(mode) || "withdraw";
currentMode = nextMode;
document.querySelectorAll(".mode-tab").forEach((tab) => {
const active = tab.dataset.mode === nextMode;
tab.classList.toggle("active", active);
tab.setAttribute("aria-selected", String(active));
});
document.querySelectorAll(".mode-panel").forEach((panel) => {
panel.classList.toggle("active", panel.dataset.panel === nextMode);
});
if (paymentSection) paymentSection.hidden = nextMode !== "withdraw";
updateSubmitState();
}
function updateSubmitState() {
if (!submitButton) return;
if (isSubmitting) {
submitButton.textContent = message("submitting", "Submitting...");
submitButton.disabled = true;
return;
}
const submitLabels = {
withdraw: message("withdraw_now", "Withdraw now"),
exchange: message("exchange_now", "Exchange now"),
transfer: message("transfer_now", "Transfer now")
};
submitButton.textContent = submitLabels[currentMode];
const amount = activeAmount();
const exceedsBalance = amount > availableBalance;
const missingWithdrawInfo = currentMode === "withdraw" && !selectedPaymentInfo?.id;
const missingPayee = currentMode === "transfer" && !selectedPayee?.id;
submitButton.disabled = !amount ||
amount <= 0 ||
exceedsBalance ||
missingWithdrawInfo ||
missingPayee ||
(currentMode === "withdraw" && amount < 50);
}
function showToast(keyOrText, fallback) {
if (!toast) return;
toast.textContent = message(keyOrText, fallback || keyOrText);
toast.hidden = false;
window.clearTimeout(showToast.timer);
showToast.timer = window.setTimeout(() => {
toast.hidden = true;
}, 2200);
}
function renderBalance(value) {
availableBalance = moneyNumber(value);
if (balanceAmount) balanceAmount.textContent = formatMoney(availableBalance);
renderSalarySelector();
Object.values(inputs).forEach((input) => {
if (input) input.max = formatMoney(availableBalance);
});
updateSubmitState();
}
function renderPaymentInfo(paymentInfo) {
selectedPaymentInfo = paymentInfo || null;
if (!selectedPaymentInfo) {
if (emptyState) emptyState.hidden = false;
if (addressForm) addressForm.hidden = false;
if (boundCard) boundCard.hidden = true;
updateSubmitState();
return;
}
const badge = boundCard?.querySelector(".network-badge");
const network = boundCard?.querySelector(".bound-network");
const address = boundCard?.querySelector(".bound-address");
const meta = boundCard?.querySelector(".bound-meta");
if (badge) badge.textContent = "TRC20";
if (network) network.textContent = "USDT TRC20";
if (address) address.textContent = maskAddress(selectedPaymentInfo.cardNo);
if (meta) meta.textContent = message("usdt_only", "USDT only");
if (emptyState) emptyState.hidden = true;
if (addressForm) addressForm.hidden = true;
if (boundCard) boundCard.hidden = false;
updateSubmitState();
}
function renderPayee() {
if (!payeeResult) return;
if (!selectedPayee) {
payeeResult.hidden = true;
payeeResult.innerHTML = "";
updateSubmitState();
return;
}
const name = selectedPayee.userNickname || selectedPayee.name || "-";
const account = selectedPayee.ownSpecialId?.account || selectedPayee.account || selectedPayee.id || "-";
const avatar = selectedPayee.userAvatar || selectedPayee.avatar || "";
payeeResult.hidden = false;
payeeResult.innerHTML = `
<div class="payee-avatar">${avatar ? `<img src="${escapeHTML(avatar)}" alt="">` : escapeHTML(name.slice(0, 1) || "U")}</div>
<div class="payee-copy">
<div class="payee-name">${escapeHTML(name)}</div>
<div class="payee-id">${escapeHTML(message("uid_prefix", "UID:"))} ${escapeHTML(account)}</div>
</div>
`;
updateSubmitState();
}
function escapeHTML(value) {
return String(value || "").replace(/[&<>"']/g, (char) => ({
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#39;"
})[char]);
}
async function fetchBalance() {
try {
if (pageIdentity === "BD_LEADER") {
const salaryTypes = ["BD_SALARY", "BD_LEADER_SALARY"];
const results = await Promise.allSettled(
salaryTypes.map((type) => requestJSON(`/wallet/salary-account/balance?salaryType=${encodeURIComponent(type)}`))
);
salaryBalances = {};
results.forEach((result, index) => {
const type = salaryTypes[index];
salaryBalances[type] = result.status === "fulfilled" ? responseBody(result.value) || {} : {};
if (result.status === "rejected") console.warn(`Failed to load ${type} balance:`, result.reason);
});
if (results.every((result) => result.status === "rejected")) throw results[0].reason;
if (!currentSalaryType) currentSalaryType = "BD_LEADER_SALARY";
renderBalance(salaryBalanceValue(currentSalaryType));
return;
}
if (isSalaryAccountMode()) {
const data = await requestJSON(`/wallet/salary-account/balance?salaryType=${encodeURIComponent(currentSalaryType)}`);
const body = responseBody(data) || {};
salaryBalances = { [currentSalaryType]: body };
renderBalance(body.availableBalance ?? body.balance ?? body.currentSalary ?? 0);
return;
}
const data = await requestJSON("/wallet/bank/balance");
const body = responseBody(data) ?? 0;
renderBalance(typeof body === "number" ? body : body.currentSalary ?? body.availableBalance ?? body.balance ?? 0);
} catch (error) {
console.error("Failed to load balance:", error);
renderBalance(0);
showToast("load_balance_failed", error.message);
}
}
async function fetchUserProfile() {
try {
const data = await requestJSON("/team/member/profile");
currentProfile = data.body ?? data.data ?? null;
} catch (error) {
console.error("Failed to load user profile:", error);
currentProfile = null;
}
}
async function fetchPaymentInfo() {
try {
const data = await requestJSON("/wallet/bank-card/list");
const cards = Array.isArray(data.body) ? data.body : Array.isArray(data.data) ? data.data : [];
const usdtCards = cards.filter((item) => item.cardType === "USDT");
renderPaymentInfo(usdtCards.find((item) => item.use) || usdtCards[0] || null);
} catch (error) {
console.error("Failed to load payment info:", error);
renderPaymentInfo(null);
showToast("load_payment_failed", error.message);
}
}
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);
if (Number.isFinite(amount) && amount > availableBalance) nextValue = formatMoney(availableBalance);
input.value = nextValue;
updateSubmitState();
}
function validateAmount(requireMinimum) {
const amount = activeAmount();
if (!amount || amount <= 0) return "invalid_amount";
if (requireMinimum && amount < 50) return "amount_too_low";
if (amount > availableBalance) return "amount_exceeds_balance";
return "";
}
async function saveAddress() {
const address = String(trc20AddressInput?.value || "").trim();
if (!isValidTrc20Address(address)) {
showToast("invalid_address");
return;
}
if (!currentProfile) await fetchUserProfile();
isSavingAddress = true;
if (saveAddressButton) {
saveAddressButton.disabled = true;
saveAddressButton.textContent = message("saving", "Saving...");
}
try {
const data = await requestJSON("/wallet/bank-card/add", {
method: "POST",
body: {
userId: currentProfile?.id || currentProfile?.userId || "",
cardType: "USDT",
cardName: "USDT",
cardNo: address,
payee: "USDT-TRC20"
}
});
const created = data.body || data.data || null;
if (created?.id) {
renderPaymentInfo({ ...created, cardType: "USDT", cardNo: address });
await requestJSON("/wallet/bank-card/use", {
method: "POST",
body: { id: created.id }
}).catch((error) => console.warn("Failed to set USDT address in use:", error));
}
await fetchPaymentInfo();
showToast("address_saved");
} catch (error) {
console.error("Failed to save TRC20 address:", error);
showToast(error.message || message("load_payment_failed", "Failed to load payment info"));
} finally {
isSavingAddress = false;
if (saveAddressButton) {
saveAddressButton.disabled = false;
saveAddressButton.textContent = message("save_address", "Save address");
}
}
}
async function searchPayee() {
const account = String(transferAccountInput?.value || "").trim();
if (!account) {
showToast("missing_payee");
return;
}
selectedPayee = null;
renderPayee();
isSearchingPayee = true;
if (searchPayeeButton) {
searchPayeeButton.disabled = true;
searchPayeeButton.textContent = message("searching", "Searching...");
}
try {
const path = `/wallet/bank/search/user-profile?account=${encodeURIComponent(account)}&type=AGENT&searchType=TRANSFER`;
const data = await requestJSON(path);
const body = data.body ?? data.data ?? null;
if (!body?.id) {
showToast("payee_not_found");
return;
}
selectedPayee = body;
renderPayee();
} catch (error) {
console.error("Failed to search payee:", error);
showToast(error.message || message("payee_not_found", "User not found"));
} finally {
isSearchingPayee = false;
if (searchPayeeButton) {
searchPayeeButton.disabled = false;
searchPayeeButton.textContent = message("search", "Search");
}
}
}
function validateWithdraw() {
if (!selectedPaymentInfo?.id) return "missing_payment_info";
return validateAmount(true);
}
function validateTransfer() {
if (!selectedPayee?.id) return "missing_payee";
return validateAmount(false);
}
async function submitWithdraw() {
const errorKey = validateWithdraw();
if (errorKey) {
showToast(errorKey);
return;
}
await requestJSON("/wallet/bank/withdraw", {
method: "POST",
body: {
amount: activeAmount(),
acceptBankCardId: selectedPaymentInfo.id,
salaryType: activeSalaryType()
}
});
showToast("withdraw_success");
}
async function submitExchange() {
const errorKey = validateAmount(false);
if (errorKey) {
showToast(errorKey);
return;
}
await requestJSON(isSalaryAccountMode() ? "/wallet/salary-account/exchange/gold" : "/wallet/bank/exchange/gold", {
method: "POST",
body: isSalaryAccountMode()
? { amount: activeAmount(), salaryType: activeSalaryType() }
: { amount: activeAmount() }
});
showToast("exchange_success");
}
async function submitTransfer() {
const errorKey = validateTransfer();
if (errorKey) {
showToast(errorKey);
return;
}
await requestJSON(isSalaryAccountMode() ? "/wallet/salary-account/transferGold" : "/wallet/bank/transferGold", {
method: "POST",
body: isSalaryAccountMode()
? { amount: activeAmount(), acceptUserId: selectedPayee.id, salaryType: activeSalaryType() }
: { amount: activeAmount(), acceptUserId: selectedPayee.id, acceptMethod: "BANK_TRANSFER" }
});
showToast("transfer_success");
}
async function submitCurrentMode() {
if (isSubmitting) return;
isSubmitting = true;
updateSubmitState();
try {
if (currentMode === "withdraw") await submitWithdraw();
if (currentMode === "exchange") await submitExchange();
if (currentMode === "transfer") await submitTransfer();
const input = activeInput();
if (input) input.value = "";
await fetchBalance();
} catch (error) {
console.error(`Failed to submit ${currentMode}:`, error);
const fallback = {
withdraw: "withdraw_failed",
exchange: "exchange_failed",
transfer: "transfer_failed"
}[currentMode];
showToast(error.message || message(fallback, "Submit failed"));
} finally {
isSubmitting = false;
updateSubmitState();
}
}
function syncBackFallback() {
document.querySelector(".back-button")?.addEventListener("click", () => {
if (window.history.length > 1) {
window.history.back();
return;
}
const nextParams = new URLSearchParams(params);
const token = readRawParam("token");
if (token) nextParams.set("token", token);
if (currentLang) nextParams.set("lang", currentLang);
const query = nextParams.toString();
const fallbackPage = pageIdentity === "BD_LEADER" ? "../bd-leader-center/index.html" : "../host-center/index.html";
window.location.href = `${fallbackPage}${query ? `?${query}` : ""}`;
});
}
function setLanguageMenuOpen(open) {
const languageButton = document.querySelector(".language-button");
const languageMenu = document.querySelector(".language-menu");
if (!languageButton || !languageMenu) return;
languageMenu.hidden = !open;
languageButton.setAttribute("aria-expanded", String(open));
}
document.querySelector(".language-button")?.addEventListener("click", (event) => {
event.stopPropagation();
const languageMenu = document.querySelector(".language-menu");
setLanguageMenuOpen(Boolean(languageMenu?.hidden));
});
document.querySelectorAll(".language-menu [data-lang]").forEach((button) => {
button.addEventListener("click", (event) => {
event.stopPropagation();
setLanguageMenuOpen(false);
applyLanguage(button.dataset.lang);
});
});
document.addEventListener("click", () => {
setLanguageMenuOpen(false);
});
document.querySelectorAll(".mode-tab").forEach((tab) => {
tab.addEventListener("click", () => setMode(tab.dataset.mode));
});
document.querySelectorAll("[data-salary-type]").forEach((button) => {
button.addEventListener("click", () => setActiveSalaryType(button.dataset.salaryType));
});
document.querySelector(".text-action")?.addEventListener("click", () => {
if (inputs.withdraw) inputs.withdraw.value = formatMoney(availableBalance);
updateSubmitState();
});
Object.values(inputs).forEach((input) => input?.addEventListener("input", normalizeAmountInput));
transferAccountInput?.addEventListener("input", () => {
selectedPayee = null;
renderPayee();
});
transferAccountInput?.addEventListener("keydown", (event) => {
if (event.key === "Enter") searchPayee();
});
searchPayeeButton?.addEventListener("click", searchPayee);
saveAddressButton?.addEventListener("click", saveAddress);
submitButton?.addEventListener("click", submitCurrentMode);
syncBackFallback();
applyLanguage(currentLang);
setMode(currentMode);
fetchUserProfile();
fetchBalance();
fetchPaymentInfo();
})();