const API_BASE_URL = "https://jvapi.haiyihy.com"; const DEFAULT_AVATAR = "/assets/defaultAvatar-CdxWBK1k-1776148661448.png"; const PAYEE_KEY = "transfer.payee"; const icons = { back: "../host-center/icons/back.svg", arrow: "../host-center/icons/arrow.svg", dollar: "./icons/dollar.svg" }; let commonHeaders = { authorization: "Bearer undefined", "req-app-intel": "version=1.0.0;build=1;model=SM-G9550;sysVersion=9;channel=Google", "req-client": "Android", "req-imei": "8fa54d728ab449e04f9292329ed44bda", "req-lang": "id-ID", "req-sys-origin": "origin=ATYOU;originChild=ATYOU", "req-version": "V2", "req-zone": "Asia/Shanghai" }; let preservePayeeOnLeave = false; const i18n = { en: { transfer: "Transfer", information: "Information", details: "Details", available_salaries: "Available salaries", loading: "Loading", transfer_to_others: "Transfer to others", search: "Search", change: "Change", user_id_prefix: "ID:", failed_to_load_balance: "Failed to load balance", please_select_amount: "Please select amount", please_select_payee: "Please select payee", transfer_dollar_success: "Transfer ${price} to {name} successfully" }, ar: { transfer: "تحويل", information: "المعلومات", details: "التفاصيل", available_salaries: "الرواتب المتاحة", loading: "تحميل", transfer_to_others: "تحويل للآخرين", search: "بحث", change: "تغيير", user_id_prefix: "ID:", failed_to_load_balance: "فشل تحميل الرصيد", please_select_amount: "يرجى اختيار المبلغ", please_select_payee: "يرجى اختيار المستلم", transfer_dollar_success: "تم تحويل ${price} إلى {name} بنجاح" }, tr: { transfer: "Transfer", information: "Bilgi", details: "Detaylar", available_salaries: "Kullanılabilir maaşlar", loading: "Yükleniyor", transfer_to_others: "Başkalarına transfer", search: "Ara", change: "Değiştir", user_id_prefix: "ID:", failed_to_load_balance: "Bakiye yüklenemedi", please_select_amount: "Lütfen tutar seçin", please_select_payee: "Lütfen alıcı seçin", transfer_dollar_success: "${price}, {name} kullanıcısına başarıyla aktarıldı" }, bn: { transfer: "ট্রান্সফার", information: "তথ্য", details: "বিস্তারিত", available_salaries: "উপলভ্য বেতন", loading: "লোড হচ্ছে", transfer_to_others: "অন্যদের ট্রান্সফার", search: "সার্চ", change: "পরিবর্তন", user_id_prefix: "ID:", failed_to_load_balance: "ব্যালেন্স লোড করতে ব্যর্থ", please_select_amount: "পরিমাণ নির্বাচন করুন", please_select_payee: "প্রাপক নির্বাচন করুন", transfer_dollar_success: "${price} {name} কে সফলভাবে ট্রান্সফার হয়েছে" } }; const state = { lang: resolveLanguage(), balance: "0.00", loading: false, selectedId: null, payee: { id: null, account: "", name: "", avatar: "", type: "", isHost: false, isAgency: false, hasSalary: false }, amounts: [ { id: 1, amount: 10500, price: 1 }, { id: 2, amount: 52500, price: 5 }, { id: 3, amount: 105000, price: 10 }, { id: 4, amount: 525000, price: 50 }, { id: 5, amount: 1050000, price: 100 }, { id: 6, amount: 2100000, price: 200 } ] }; const $ = (selector) => document.querySelector(selector); const $$ = (selector) => Array.from(document.querySelectorAll(selector)); const t = (key, params = {}) => { let text = (i18n[state.lang] && i18n[state.lang][key]) || i18n.en[key] || key; Object.entries(params).forEach(([name, value]) => { text = text.replaceAll(`{${name}}`, value); }); return text; }; function resolveLanguage() { const params = new URLSearchParams(window.location.search); const lang = params.get("lang") || navigator.language || "en"; if (lang.startsWith("ar")) return "ar"; if (lang.startsWith("tr")) return "tr"; if (lang.startsWith("bn")) return "bn"; return "en"; } function splitHeaderPairs(value) { const result = {}; if (!value) return result; value.split(";").forEach((item) => { const index = item.indexOf("="); if (index > -1) result[item.slice(0, index)] = item.slice(index + 1); }); return result; } function parseAccessOrigin(raw) { const data = JSON.parse(raw); const appIntel = splitHeaderPairs(data["Req-App-Intel"]); const sysOrigin = splitHeaderPairs(data["Req-Sys-Origin"]); return { reqLang: data["Req-Lang"], authorization: data.Authorization, buildVersion: appIntel.build, appVersion: appIntel.version, appChannel: appIntel.channel, reqImei: appIntel["Req-Imei"], sysOrigin: sysOrigin.origin, sysOriginChild: sysOrigin.child }; } function setHeadersFromApp(headers) { const next = {}; if (headers.authorization) next.authorization = headers.authorization; if (headers.reqLang) next["req-lang"] = headers.reqLang; if (headers.appVersion || headers.buildVersion || headers.appChannel) { const intel = []; if (headers.appVersion) intel.push(`version=${headers.appVersion}`); if (headers.buildVersion) intel.push(`build=${headers.buildVersion}`); if (headers.appChannel) intel.push(`channel=${headers.appChannel}`); next["req-app-intel"] = intel.join(";"); } if (headers.reqImei) next["req-imei"] = headers.reqImei; if (headers.sysOrigin) { const origin = [`origin=${headers.sysOrigin}`]; if (headers.sysOriginChild) origin.push(`child=${headers.sysOriginChild}`); next["req-sys-origin"] = origin.join(";"); } commonHeaders = { ...commonHeaders, ...next }; } function connectToApp() { return new Promise((resolve) => { window.renderData = resolve; window.getIosAccessOriginParam = resolve; if (!(window.app || window.webkit || window.FlutterPageControl)) { setHeadersFromApp({ authorization: "", reqLang: state.lang, appVersion: "1.0.0", buildVersion: "1", appChannel: "Web", reqImei: "H5-STATIC", sysOrigin: "LIKEI", sysOriginChild: "LIKEI" }); resolve({ success: true }); return; } let attempt = 0; const poll = () => { attempt += 1; if (window.app && typeof window.app.getAccessOrigin === "function") { const raw = window.app.getAccessOrigin(); if (raw) setHeadersFromApp(parseAccessOrigin(raw)); resolve({ success: true }); return; } if (attempt < 50) { window.setTimeout(poll, 100); return; } if (window.FlutterApp && typeof window.FlutterApp.postMessage === "function") { window.FlutterApp.postMessage("requestAccessOrigin"); } resolve({ success: true }); }; poll(); }).then((result) => { if (typeof result === "string") setHeadersFromApp(parseAccessOrigin(result)); return result; }); } async function request(method, path, body) { const response = await fetch(`${API_BASE_URL}${path}`, { method, headers: { ...commonHeaders, "content-type": "application/json" }, body: body === undefined ? undefined : JSON.stringify(body) }); const data = await response.json().catch(() => ({})); if (!response.ok || data.status === false) { const error = new Error(data.errorMsg || data.message || `HTTP ${response.status}`); error.response = data; throw error; } return data; } const apiGet = (path) => request("GET", path); const apiPost = (path, body) => request("POST", path, body); function formatMoney(value) { const number = Number(value || 0); return Number.isFinite(number) ? number.toFixed(2) : "0.00"; } function getStoredPayee() { try { const payee = JSON.parse(sessionStorage.getItem(PAYEE_KEY) || "null"); if (payee) Object.assign(state.payee, payee); } catch (error) { console.error("Failed to parse transfer payee:", error); } } function clearPayee() { Object.assign(state.payee, { id: null, account: "", name: "", avatar: "", type: "", isHost: false, isAgency: false, hasSalary: false }); sessionStorage.removeItem(PAYEE_KEY); } async function fetchBalance() { state.loading = true; renderBalance(); try { const result = await apiGet("/wallet/bank/balance"); state.balance = result.status && typeof result.body === "number" ? result.body.toFixed(2) : "0.00"; } catch (error) { console.error("Failed to fetch bank balance:", error); state.balance = "0.00"; showToast(t("failed_to_load_balance")); } finally { state.loading = false; renderBalance(); } } async function submitTransfer() { if (!state.selectedId) { showToast(t("please_select_amount")); return; } if (!state.payee.id) { showToast(t("please_select_payee")); return; } const selected = state.amounts.find((item) => item.id === state.selectedId); const payload = { amount: selected.price, acceptUserId: state.payee.id, acceptMethod: "BANK_TRANSFER" }; try { const result = await apiPost("/wallet/bank/transferGold", payload); if (result.status) { showToast(t("transfer_dollar_success", { price: selected.price, name: state.payee.name })); state.selectedId = null; await fetchBalance(); renderAmounts(); } } catch (error) { console.error("Transfer failed:", error); showToast(error.response?.errorMsg || error.message); } } function normalizeRoute(target) { const url = new URL(target, window.location.origin); const currentLang = new URLSearchParams(window.location.search).get("lang"); if (currentLang && !url.searchParams.has("lang")) url.searchParams.set("lang", currentLang); return `${url.pathname}${url.search}${url.hash}`; } function navigateTo(target) { window.location.href = normalizeRoute(target); } function closePage() { if (window.history.length > 1) { window.history.back(); return; } if (window.app && typeof window.app.closePage === "function") { window.app.closePage(); return; } if (window.FlutterPageControl && typeof window.FlutterPageControl.postMessage === "function") { window.FlutterPageControl.postMessage("close_page"); } } function showToast(message) { const toast = $("#toast"); toast.textContent = message || "Request failed"; toast.hidden = false; window.clearTimeout(showToast.timer); showToast.timer = window.setTimeout(() => { toast.hidden = true; }, 2500); } function renderI18n() { document.documentElement.lang = state.lang; document.documentElement.dir = state.lang === "ar" ? "rtl" : "ltr"; $(".lang-button").textContent = state.lang.toUpperCase(); $$("[data-i18n]").forEach((node) => { node.textContent = t(node.dataset.i18n); }); } function renderIcons() { $("#backIcon").src = icons.back; $("#detailsArrow").src = icons.arrow; } function renderBalance() { $("#balanceLoading").hidden = !state.loading; $("#balanceValue").hidden = state.loading; $("#balanceValue").textContent = state.balance; } function renderPayee() { const hasPayee = Boolean(state.payee.id); $("#payeeSelected").hidden = !hasPayee; $("#payeeEmpty").hidden = hasPayee; if (!hasPayee) return; $("#payeeName").textContent = state.payee.name; $("#payeeAccount").textContent = state.payee.goodID || state.payee.account || "-"; const avatar = $("#payeeAvatar"); avatar.src = state.payee.avatar || ""; avatar.alt = state.payee.name || ""; } function renderAmounts() { const root = $("#amountGrid"); root.innerHTML = ""; state.amounts.forEach((item) => { const button = document.createElement("button"); button.type = "button"; button.className = `coin-item${state.selectedId === item.id ? " selected" : ""}`; button.innerHTML = `
$${item.price}
`; button.addEventListener("click", () => { state.selectedId = state.selectedId === item.id ? null : item.id; renderAmounts(); }); root.appendChild(button); }); $("#transferButton").disabled = !state.selectedId || !state.payee.id; } function render() { renderI18n(); renderIcons(); renderBalance(); renderPayee(); renderAmounts(); } function bindEvents() { $("[data-action='back']").addEventListener("click", closePage); $(".lang-button").addEventListener("click", () => navigateTo("/language")); $$("[data-route]").forEach((node) => { node.addEventListener("click", () => navigateTo(node.dataset.route)); }); $$("[data-action='search-payee']").forEach((node) => { node.addEventListener("click", () => { clearPayee(); preservePayeeOnLeave = true; navigateTo("/search-payee?from=/transfer"); }); }); $("#transferButton").addEventListener("click", submitTransfer); window.addEventListener("pagehide", () => { if (!preservePayeeOnLeave) sessionStorage.removeItem(PAYEE_KEY); }); $("#payeeAvatar").addEventListener("error", (event) => { event.target.onerror = null; event.target.src = DEFAULT_AVATAR; }); } async function init() { getStoredPayee(); bindEvents(); render(); await connectToApp(); await fetchBalance(); } init();