const API_BASE_URL = "https://jvapi.haiyihy.com"; const icons = { back: "../host-center/icons/back.svg", arrow: "../host-center/icons/arrow.svg", coin: "./icons/coin.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" }; const coinOptions = [ { 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 i18n = { en: { exchange_gold_coins: "Exchange Coins", information: "Information", details: "Details", available_salaries: "Available salaries", loading: "Loading", exchange: "Exchange", tips: "Tips", confirm_exchange: "Confirm exchange?", cancel: "Cancel", confirm: "Confirm", failed_to_load_balance: "Failed to load balance.", exchange_not_available: "Exchange is not available.", request_not_acceptable: "Request not acceptable.", failed_to_load_user_info: "Failed to load user info.", please_select_coin_amount: "Please select coin amount.", insufficient_balance: "Insufficient balance. Balance: {balance}, price: {price}.", exchange_success: "Exchange success: ${price}.", exchange_failed: "Exchange failed.", exchange_error_occurred: "Exchange error occurred." }, ar: { exchange_gold_coins: "استبدال العملات", information: "معلومات", details: "التفاصيل", available_salaries: "الرواتب المتاحة", loading: "جار التحميل", exchange: "استبدال", tips: "نصائح", confirm_exchange: "تأكيد الاستبدال؟", cancel: "إلغاء", confirm: "تأكيد", failed_to_load_balance: "فشل تحميل الرصيد.", exchange_not_available: "الاستبدال غير متاح.", request_not_acceptable: "الطلب غير مقبول.", failed_to_load_user_info: "فشل تحميل معلومات المستخدم.", please_select_coin_amount: "يرجى اختيار كمية العملات.", insufficient_balance: "الرصيد غير كاف. الرصيد: {balance}، السعر: {price}.", exchange_success: "تم الاستبدال بنجاح: ${price}.", exchange_failed: "فشل الاستبدال.", exchange_error_occurred: "حدث خطأ في الاستبدال." }, tr: { exchange_gold_coins: "Coin Değiştir", information: "Bilgi", details: "Detaylar", available_salaries: "Kullanılabilir maaşlar", loading: "Yükleniyor", exchange: "Değiştir", tips: "İpuçları", confirm_exchange: "Değişimi onaylıyor musunuz?", cancel: "İptal", confirm: "Onayla", failed_to_load_balance: "Bakiye yüklenemedi.", exchange_not_available: "Değişim kullanılamıyor.", request_not_acceptable: "İstek kabul edilemez.", failed_to_load_user_info: "Kullanıcı bilgisi yüklenemedi.", please_select_coin_amount: "Lütfen coin miktarı seçin.", insufficient_balance: "Yetersiz bakiye. Bakiye: {balance}, fiyat: {price}.", exchange_success: "Değişim başarılı: ${price}.", exchange_failed: "Değişim başarısız.", exchange_error_occurred: "Değişim hatası oluştu." }, bn: { exchange_gold_coins: "কয়েন এক্সচেঞ্জ", information: "তথ্য", details: "বিস্তারিত", available_salaries: "উপলভ্য বেতন", loading: "লোড হচ্ছে", exchange: "এক্সচেঞ্জ", tips: "টিপস", confirm_exchange: "এক্সচেঞ্জ নিশ্চিত করবেন?", cancel: "বাতিল", confirm: "নিশ্চিত", failed_to_load_balance: "ব্যালেন্স লোড ব্যর্থ।", exchange_not_available: "এক্সচেঞ্জ উপলভ্য নয়।", request_not_acceptable: "রিকোয়েস্ট গ্রহণযোগ্য নয়।", failed_to_load_user_info: "ইউজার তথ্য লোড ব্যর্থ।", please_select_coin_amount: "কয়েন পরিমাণ নির্বাচন করুন।", insufficient_balance: "ব্যালেন্স অপর্যাপ্ত। ব্যালেন্স: {balance}, মূল্য: {price}।", exchange_success: "এক্সচেঞ্জ সফল: ${price}।", exchange_failed: "এক্সচেঞ্জ ব্যর্থ।", exchange_error_occurred: "এক্সচেঞ্জ ত্রুটি ঘটেছে।" } }; const state = { lang: resolveLanguage(), balance: "0", selectedId: null, isBalanceLoading: false, isUserLoading: false, role: "" }; const $ = (selector) => document.querySelector(selector); const $$ = (selector) => Array.from(document.querySelectorAll(selector)); function translate(key, params = {}) { let text = (i18n[state.lang] && i18n[state.lang][key]) || i18n.en[key] || key; Object.entries(params).forEach(([name, value]) => { text = text.replace(`{${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(null); return; } let attempt = 0; const poll = () => { attempt += 1; if (window.app && typeof window.app.getAccessOrigin === "function") { resolve(window.app.getAccessOrigin()); return; } if (attempt < 50) { window.setTimeout(poll, 100); return; } if (window.FlutterApp && typeof window.FlutterApp.postMessage === "function") { window.FlutterApp.postMessage("requestAccessOrigin"); } resolve(null); }; poll(); }).then((raw) => { if (raw) setHeadersFromApp(parseAccessOrigin(raw)); }); } async function apiGet(path) { const response = await fetch(`${API_BASE_URL}${path}`, { method: "GET", headers: commonHeaders }); return parseApiResponse(response); } async function apiPost(path, body) { const response = await fetch(`${API_BASE_URL}${path}`, { method: "POST", headers: { ...commonHeaders, "content-type": "application/json" }, body: JSON.stringify(body || {}) }); return parseApiResponse(response); } async function parseApiResponse(response) { const data = await response.json().catch(() => ({})); if (!response.ok || data.status === false) { const error = new Error(data.errorMsg || data.message || `HTTP Error: ${response.status}`); error.status = response.status; error.response = data; error.errorCode = data.errorCode; throw error; } return data; } function navigateTo(route) { const url = new URL(route, window.location.origin); const lang = new URLSearchParams(window.location.search).get("lang"); if (lang && !url.searchParams.has("lang")) url.searchParams.set("lang", lang); window.location.href = `${url.pathname}${url.search}${url.hash}`; } 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 showModal() { $("#confirmModal").hidden = false; } function hideModal() { $("#confirmModal").hidden = true; } async function fetchBalance() { state.isBalanceLoading = 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(translate("failed_to_load_balance")); } finally { state.isBalanceLoading = false; renderBalance(); } } async function checkExchangeAvailable() { state.isUserLoading = true; try { const result = await apiGet("/wallet/bank/check/exchange-gold"); if (result.status && result.body) { state.role = result.body.role || ""; console.debug("用户角色:", state.role); return; } state.role = ""; closePage(); } catch (error) { if (error.errorCode === 1083) { showToast(translate("exchange_not_available")); } else if (error.message && error.message.includes("HTTP Error: 406")) { showToast(translate("request_not_acceptable")); } else { showToast(translate("failed_to_load_user_info")); } closePage(); state.role = ""; } finally { state.isUserLoading = false; } } async function confirmExchange() { hideModal(); if (!state.selectedId) { showToast(translate("please_select_coin_amount")); return; } const option = coinOptions.find((item) => item.id === state.selectedId); const price = option.price; const balance = parseFloat(state.balance); if (Number.isNaN(balance) || balance < price) { showToast(translate("insufficient_balance", { balance: balance.toFixed(2), price })); return; } try { const result = await apiPost("/wallet/bank/exchange/gold", { coinId: option.id, amount: option.price, price: option.price }); if (result.status === true || result.errorCode === 0) { showToast(translate("exchange_success", { price: option.price })); await fetchBalance(); state.selectedId = null; renderCoins(); } else { showToast(result.message || translate("exchange_failed")); } } catch (error) { console.error("Exchange error:", error); showToast(translate("exchange_error_occurred")); } } 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 = translate(node.dataset.i18n); }); } function renderIcons() { $("#backIcon").src = icons.back; $("#detailsArrowIcon").src = icons.arrow; } function renderBalance() { $("#balanceLoading").hidden = !state.isBalanceLoading; $("#balanceValue").hidden = state.isBalanceLoading; $("#balanceValue").textContent = state.balance; } function renderCoins() { const grid = $("#coinGrid"); grid.innerHTML = ""; coinOptions.forEach((option) => { const item = document.createElement("button"); item.type = "button"; item.className = `coin-item${state.selectedId === option.id ? " selected" : ""}`; item.innerHTML = `
$${option.price}
`; item.addEventListener("click", () => { state.selectedId = state.selectedId === option.id ? null : option.id; renderCoins(); }); grid.appendChild(item); }); $("#exchangeButton").disabled = !state.selectedId; } function bindEvents() { $("[data-action='back']").addEventListener("click", closePage); $("[data-action='language']").addEventListener("click", () => navigateTo("/language")); $$("[data-route]").forEach((node) => node.addEventListener("click", () => navigateTo(node.dataset.route))); $("#exchangeButton").addEventListener("click", () => { if (state.selectedId) showModal(); }); $("#confirmModal").addEventListener("click", hideModal); $(".modal-card").addEventListener("click", (event) => event.stopPropagation()); $("[data-action='cancel']").addEventListener("click", hideModal); $("[data-action='confirm']").addEventListener("click", confirmExchange); } async function init() { renderI18n(); renderIcons(); renderBalance(); renderCoins(); bindEvents(); await connectToApp(); checkExchangeAvailable(); fetchBalance(); console.log("document.documentElement.getAttribute('dir') === 'rtl'", document.documentElement.getAttribute("dir") === "rtl"); } init();