(function () { const API_BASE_URL = "https://jvapi.haiyihy.com"; 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: "Available Income", withdraw: "Withdraw", exchange: "Exchange", withdraw_amount: "Withdraw amount", exchange_amount: "Exchange amount", minimum: "Minimum 50 USD", all: "All", you_receive: "You receive", payment_method: "Payment method", payment_method_subtitle: "The currently used bank card will receive this withdrawal.", manage: "Manage", no_bank_card: "No bank card in use", no_bank_card_copy: "Set a bank card as in use before withdrawing.", change: "Change", withdraw_now: "Withdraw now", exchange_now: "Exchange now", submitting: "Submitting...", payee: "Payee", card_number: "Card", missing_token: "Missing token", load_balance_failed: "Failed to load balance", load_bank_card_failed: "Failed to load bank card", missing_bank_card: "No bank card in use", invalid_amount: "Please enter a valid amount", amount_too_low: "Minimum withdrawal amount is 50 USD", amount_exceeds_balance: "Amount exceeds available income", withdraw_success: "Withdrawal submitted", withdraw_failed: "Withdrawal failed", exchange_unavailable: "Exchange is not available on this page" }, ar: { page_label: "السحب والتبديل", back: "رجوع", title: "سحب/تبديل", language_button_aria: "تغيير اللغة", available_income: "الدخل المتاح", withdraw: "سحب", exchange: "تبديل", withdraw_amount: "مبلغ السحب", exchange_amount: "مبلغ التبديل", minimum: "الحد الأدنى 50 USD", all: "الكل", you_receive: "ستحصل على", payment_method: "طريقة الدفع", payment_method_subtitle: "سيتم إرسال السحب إلى البطاقة المستخدمة حاليًا.", manage: "إدارة", no_bank_card: "لا توجد بطاقة مستخدمة", no_bank_card_copy: "عيّن بطاقة بنكية مستخدمة قبل السحب.", change: "تغيير", withdraw_now: "اسحب الآن", exchange_now: "بدل الآن", submitting: "جار الإرسال...", payee: "المستفيد", card_number: "البطاقة", missing_token: "رمز الدخول مفقود", load_balance_failed: "فشل تحميل الرصيد", load_bank_card_failed: "فشل تحميل البطاقة", missing_bank_card: "لا توجد بطاقة مستخدمة", invalid_amount: "يرجى إدخال مبلغ صالح", amount_too_low: "الحد الأدنى للسحب هو 50 USD", amount_exceeds_balance: "المبلغ يتجاوز الدخل المتاح", withdraw_success: "تم إرسال طلب السحب", withdraw_failed: "فشل السحب", exchange_unavailable: "التبديل غير متاح في هذه الصفحة" }, tr: { page_label: "Çekme ve değişim", back: "Geri", title: "Çek/Değiştir", language_button_aria: "Dili değiştir", available_income: "Kullanılabilir Gelir", withdraw: "Withdraw", exchange: "Exchange", withdraw_amount: "Withdraw amount", exchange_amount: "Exchange amount", minimum: "Minimum 50 USD", all: "All", you_receive: "You receive", payment_method: "Payment method", payment_method_subtitle: "The currently used bank card will receive this withdrawal.", manage: "Manage", no_bank_card: "No bank card in use", no_bank_card_copy: "Set a bank card as in use before withdrawing.", change: "Change", withdraw_now: "Withdraw now", exchange_now: "Exchange now", submitting: "Submitting...", payee: "Payee", card_number: "Card", missing_token: "Missing token", load_balance_failed: "Failed to load balance", load_bank_card_failed: "Failed to load bank card", missing_bank_card: "No bank card in use", invalid_amount: "Please enter a valid amount", amount_too_low: "Minimum withdrawal amount is 50 USD", amount_exceeds_balance: "Amount exceeds available income", withdraw_success: "Withdrawal submitted", withdraw_failed: "Withdrawal failed", exchange_unavailable: "Exchange is not available on this page" }, id: { page_label: "Tarik dan tukar", back: "Kembali", title: "Tarik/Tukar", language_button_aria: "Ubah bahasa", available_income: "Penghasilan Tersedia", withdraw: "Tarik", exchange: "Tukar", withdraw_amount: "Jumlah penarikan", exchange_amount: "Jumlah penukaran", minimum: "Minimum 50 USD", all: "Semua", you_receive: "Anda menerima", payment_method: "Metode pembayaran", payment_method_subtitle: "Kartu bank yang sedang digunakan akan menerima penarikan ini.", manage: "Kelola", no_bank_card: "Tidak ada kartu aktif", no_bank_card_copy: "Atur kartu bank sebagai aktif sebelum menarik.", change: "Ubah", withdraw_now: "Tarik sekarang", exchange_now: "Tukar sekarang", submitting: "Mengirim...", payee: "Penerima", card_number: "Kartu", missing_token: "Token tidak ditemukan", load_balance_failed: "Gagal memuat saldo", load_bank_card_failed: "Gagal memuat kartu bank", missing_bank_card: "Tidak ada kartu aktif", invalid_amount: "Masukkan jumlah yang valid", amount_too_low: "Minimum penarikan adalah 50 USD", amount_exceeds_balance: "Jumlah melebihi penghasilan tersedia", withdraw_success: "Penarikan terkirim", withdraw_failed: "Penarikan gagal", exchange_unavailable: "Penukaran tidak tersedia di halaman ini" } }; const params = readURLParams(); let currentLang = normalizeLanguage(params.get("lang")); let currentMode = "withdraw"; let currentMessages = translations[currentLang] || translations.en; let availableBalance = 0; let selectedBankCard = null; let isSubmitting = false; const amountInput = document.querySelector("#withdrawAmount"); const balanceAmount = document.querySelector("#balanceAmount"); const emptyState = document.querySelector(".empty-address-state"); const boundCard = document.querySelector(".bound-address-card"); const submitButton = document.querySelector(".submit-button"); const toast = document.querySelector(".toast"); 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 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(currentMessages.missing_token || translations.en.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) { throw new Error(data.errorMsg || data.message || `Request failed: ${response.status}`); } 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 maskCardNumber(value) { const text = String(value || "").trim(); if (!text) return "-"; if (text.length <= 8) return text; return `${text.slice(0, 4)} **** ${text.slice(-4)}`; } function activeAmount() { return moneyNumber(amountInput?.value); } 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)); }); updateSubmitState(); } function setMode(mode) { currentMode = mode; document.querySelectorAll(".mode-tab").forEach((tab) => { const active = tab.dataset.mode === mode; tab.classList.toggle("active", active); tab.setAttribute("aria-selected", String(active)); }); document.querySelectorAll(".mode-panel").forEach((panel) => { panel.classList.toggle("active", panel.dataset.panel === mode); }); updateSubmitState(); } function updateSubmitState() { if (!submitButton) return; if (isSubmitting) { submitButton.textContent = message("submitting", "Submitting..."); submitButton.disabled = true; return; } submitButton.textContent = currentMode === "exchange" ? message("exchange_now", "Exchange now") : message("withdraw_now", "Withdraw now"); const amount = activeAmount(); submitButton.disabled = currentMode !== "withdraw" || !selectedBankCard?.id || !amount || amount < 50 || amount > availableBalance; } 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); if (amountInput) amountInput.max = formatMoney(availableBalance); updateSubmitState(); } function renderBankCard(card) { selectedBankCard = card || null; if (!selectedBankCard) { if (emptyState) emptyState.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 = selectedBankCard.cardType || "BANK"; if (network) network.textContent = selectedBankCard.cardType || "Bank card"; if (address) address.textContent = `${message("card_number", "Card")}: ${maskCardNumber(selectedBankCard.cardNo)}`; if (meta) meta.textContent = `${message("payee", "Payee")}: ${selectedBankCard.payee || "-"}`; if (emptyState) emptyState.hidden = true; if (boundCard) boundCard.hidden = false; updateSubmitState(); } async function fetchBalance() { try { const data = await requestJSON("/wallet/bank/balance"); const body = data.body ?? data.data ?? 0; renderBalance(typeof body === "number" ? body : body.currentSalary ?? body.availableBalance ?? body.balance ?? 0); } catch (error) { console.error("Failed to load bank balance:", error); renderBalance(0); showToast("load_balance_failed", error.message); } } async function fetchBankCards() { try { const data = await requestJSON("/wallet/bank-card/list"); const cards = Array.isArray(data.body) ? data.body : Array.isArray(data.data) ? data.data : []; renderBankCard(cards.find((item) => item.use)); } catch (error) { console.error("Failed to load bank card:", error); renderBankCard(null); showToast("load_bank_card_failed", error.message); } } function normalizeAmountInput() { if (!amountInput) return; const raw = String(amountInput.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); amountInput.value = nextValue; updateSubmitState(); } function validateWithdraw() { const amount = activeAmount(); if (!selectedBankCard?.id) return "missing_bank_card"; if (!amount || amount <= 0) return "invalid_amount"; if (amount < 50) return "amount_too_low"; if (amount > availableBalance) return "amount_exceeds_balance"; return ""; } async function submitWithdraw() { if (currentMode !== "withdraw") { showToast("exchange_unavailable"); return; } const errorKey = validateWithdraw(); if (errorKey) { showToast(errorKey); return; } isSubmitting = true; updateSubmitState(); try { await requestJSON("/wallet/bank/withdraw", { method: "POST", body: { amount: activeAmount(), acceptBankCardId: selectedBankCard.id, salaryType: "HOST_SALARY" } }); showToast("withdraw_success"); await fetchBalance(); } catch (error) { console.error("Failed to submit withdrawal:", error); showToast(error.message || message("withdraw_failed", "Withdrawal 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(); window.location.href = `../host-center/index.html${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.querySelector(".text-action")?.addEventListener("click", () => { if (amountInput) amountInput.value = formatMoney(availableBalance); updateSubmitState(); }); amountInput?.addEventListener("input", normalizeAmountInput); submitButton?.addEventListener("click", submitWithdraw); syncBackFallback(); applyLanguage(currentLang); setMode("withdraw"); fetchBalance(); fetchBankCards(); })();