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 = `