(function () { const API_BASE_URL = "https://jvapi.haiyihy.com"; const RECHARGE_APPLICATION_ID = "1963531459019739137"; const supportedLanguages = ["en", "ar", "tr", "id"]; const languageLabels = { en: "EN", ar: "AR", tr: "TR", id: "ID" }; const messages = { en: { document_title: "Recharge Center", page_label: "Recharge Center", back: "Back", title: "Recharge Center", language_button_aria: "Change language", help: "Help", close: "Close", my_account: "My account", profile_card: "User profile", whatsapp_label: "WhatsApp:", add_whatsapp: "+ add whatsapp", add_whatsapp_title: "Add WhatsApp", edit_whatsapp: "Edit", edit_whatsapp_title: "Edit WhatsApp", whatsapp_placeholder: "Enter WhatsApp", whatsapp_required: "Please enter WhatsApp", whatsapp_too_long: "WhatsApp cannot exceed 64 characters", whatsapp_saved: "WhatsApp saved", save: "Save", saving: "Saving...", balance_title: "Coins", history: "History", search_title: "Recharge user", search_subtitle: "Search by user ID", search_placeholder: "Enter user ID", search: "Search", cancel: "Cancel", searching: "Searching...", target_user: "Target user", uid_prefix: "UID:", change: "Change", coin_recharge_title: "Recharge coins", coin_recharge_subtitle: "Enter coins amount", coin_amount_placeholder: "Enter coins amount", confirm_recharge: "Confirm recharge", auto_verify_title: "Auto recharge verification", auto_verify_subtitle: "Verify Binance transfer and issue coins", open_auto_verify: "Open", close_auto_verify: "Close", order_no: "Order no.", order_no_placeholder: "Enter order no.", transfer_amount_usdt: "Transfer amount", transfer_amount_placeholder: "Enter USDT amount", transfer_type: "Transfer type", binance_pay_transfer: "Binance internal", chain_transfer: "On-chain transfer", bill_no: "Bill no.", bill_no_placeholder: "Enter bill no. or txId", verify_recharge: "Verify recharge", verifying: "Verifying...", auto_verify_success: "Verification success", please_enter_order_no: "Please enter order no.", please_enter_bill_no: "Please enter bill no.", loading_data: "Loading...", loading_mask: "loading....", no_country: "No supported country", no_packages: "No recharge packages", user_not_found: "User info not found", missing_token: "Missing token", failed_to_load: "Failed to load data", recharging: "Recharging...", recharge_success: "Recharge success", please_enter_valid_amount: "Please enter a valid amount", transaction_records: "Transaction records", income: "Income", expenditure: "Expenditure", enter_user_id: "Enter user ID", coins: "coins", no_transaction_records: "No transaction records", no_selling_records_yet: "No records yet", load_more: "Load more", payment_not_available: "Payment not available", creating_order: "Creating order...", redirecting_payment: "Redirecting to payment...", order_created: "Order created", faq_title: "FAQ", faq_question_1: "How to recharge?", faq_answer_1: "Enter the user's ID, enter the coins amount, then confirm recharge.", faq_question_2: "How to check recharge result?", faq_answer_2: "After payment, return to the app and check the user's wallet balance." }, ar: { document_title: "مركز الشحن", page_label: "مركز الشحن", back: "رجوع", title: "مركز الشحن", language_button_aria: "تغيير اللغة", help: "مساعدة", close: "إغلاق", my_account: "حسابي", profile_card: "ملف المستخدم", whatsapp_label: "WhatsApp:", add_whatsapp: "+ إضافة واتساب", add_whatsapp_title: "إضافة واتساب", edit_whatsapp: "تعديل", edit_whatsapp_title: "تعديل واتساب", whatsapp_placeholder: "أدخل واتساب", whatsapp_required: "يرجى إدخال واتساب", whatsapp_too_long: "لا يمكن أن يتجاوز واتساب 64 حرفا", whatsapp_saved: "تم حفظ واتساب", save: "حفظ", saving: "جار الحفظ...", balance_title: "العملات", history: "السجل", search_title: "شحن مستخدم", search_subtitle: "ابحث بواسطة معرف المستخدم", search_placeholder: "أدخل معرف المستخدم", search: "بحث", cancel: "إلغاء", searching: "جار البحث...", target_user: "المستخدم المستهدف", uid_prefix: "UID:", change: "تغيير", coin_recharge_title: "شحن العملات", coin_recharge_subtitle: "أدخل عدد العملات", coin_amount_placeholder: "أدخل عدد العملات", confirm_recharge: "تأكيد الشحن", auto_verify_title: "تحقق الشحن التلقائي", auto_verify_subtitle: "تحقق من تحويل Binance وأرسل العملات", open_auto_verify: "فتح", close_auto_verify: "إغلاق", order_no: "رقم الطلب", order_no_placeholder: "أدخل رقم الطلب", transfer_amount_usdt: "مبلغ التحويل", transfer_amount_placeholder: "أدخل مبلغ USDT", transfer_type: "نوع التحويل", binance_pay_transfer: "تحويل داخل Binance", chain_transfer: "تحويل على الشبكة", bill_no: "رقم الفاتورة", bill_no_placeholder: "أدخل رقم الفاتورة أو txId", verify_recharge: "تحقق من الشحن", verifying: "جار التحقق...", auto_verify_success: "تم التحقق بنجاح", please_enter_order_no: "يرجى إدخال رقم الطلب", please_enter_bill_no: "يرجى إدخال رقم الفاتورة", loading_data: "جار التحميل...", loading_mask: "جار التحميل....", no_country: "لا توجد دولة مدعومة", no_packages: "لا توجد باقات شحن", user_not_found: "لم يتم العثور على المستخدم", missing_token: "التوكن مفقود", failed_to_load: "فشل تحميل البيانات", recharging: "جار الشحن...", recharge_success: "تم الشحن بنجاح", please_enter_valid_amount: "يرجى إدخال مبلغ صالح", transaction_records: "سجلات التحويل", income: "الوارد", expenditure: "الصادر", enter_user_id: "أدخل معرف المستخدم", coins: "عملات", no_transaction_records: "لا توجد سجلات تحويل", no_selling_records_yet: "لا توجد سجلات بعد", load_more: "تحميل المزيد", payment_not_available: "الدفع غير متاح", creating_order: "جار إنشاء الطلب...", redirecting_payment: "جار الانتقال إلى الدفع...", order_created: "تم إنشاء الطلب", faq_title: "الأسئلة الشائعة", faq_question_1: "كيف أشحن؟", faq_answer_1: "أدخل معرف المستخدم، ثم أدخل عدد العملات واضغط تأكيد الشحن.", faq_question_2: "كيف أتحقق من نتيجة الشحن؟", faq_answer_2: "بعد الشحن، تحقق من رصيد العملات في حساب المستخدم." }, tr: { document_title: "Yukleme Merkezi", page_label: "Yukleme Merkezi", back: "Geri", title: "Yukleme Merkezi", language_button_aria: "Dili degistir", help: "Yardim", close: "Kapat", my_account: "Hesabim", profile_card: "Kullanici profili", whatsapp_label: "WhatsApp:", add_whatsapp: "+ WhatsApp ekle", add_whatsapp_title: "WhatsApp ekle", edit_whatsapp: "Duzenle", edit_whatsapp_title: "WhatsApp duzenle", whatsapp_placeholder: "WhatsApp gir", whatsapp_required: "Lutfen WhatsApp girin", whatsapp_too_long: "WhatsApp 64 karakteri gecemez", whatsapp_saved: "WhatsApp kaydedildi", save: "Kaydet", saving: "Kaydediliyor...", balance_title: "Coinler", history: "Gecmis", search_title: "Kullaniciya yukleme", search_subtitle: "Kullanici ID ile ara", search_placeholder: "Kullanici ID gir", search: "Ara", cancel: "Iptal", searching: "Araniyor...", target_user: "Hedef kullanici", uid_prefix: "UID:", change: "Degistir", coin_recharge_title: "Coin yukle", coin_recharge_subtitle: "Coin miktari gir", coin_amount_placeholder: "Coin miktari gir", confirm_recharge: "Yuklemeyi onayla", auto_verify_title: "Otomatik yukleme dogrulama", auto_verify_subtitle: "Binance transferini dogrula ve coin ver", open_auto_verify: "Ac", close_auto_verify: "Kapat", order_no: "Siparis no.", order_no_placeholder: "Siparis no. gir", transfer_amount_usdt: "Transfer tutari", transfer_amount_placeholder: "USDT tutari gir", transfer_type: "Transfer tipi", binance_pay_transfer: "Binance ici", chain_transfer: "Zincir uzeri", bill_no: "Fatura no.", bill_no_placeholder: "Fatura no. veya txId gir", verify_recharge: "Yuklemeyi dogrula", verifying: "Dogrulaniyor...", auto_verify_success: "Dogrulama basarili", please_enter_order_no: "Lutfen siparis no. girin", please_enter_bill_no: "Lutfen fatura no. girin", loading_data: "Yukleniyor...", loading_mask: "yukleniyor....", no_country: "Desteklenen ulke yok", no_packages: "Yukleme paketi yok", user_not_found: "Kullanici bulunamadi", missing_token: "Token eksik", failed_to_load: "Veri yuklenemedi", recharging: "Yukleniyor...", recharge_success: "Yukleme basarili", please_enter_valid_amount: "Gecerli bir miktar girin", transaction_records: "Islem kayitlari", income: "Gelir", expenditure: "Gider", enter_user_id: "Kullanici ID gir", coins: "coin", no_transaction_records: "Islem kaydi yok", no_selling_records_yet: "Henuz kayit yok", load_more: "Daha fazla yukle", payment_not_available: "Odeme kullanilamiyor", creating_order: "Siparis olusturuluyor...", redirecting_payment: "Odeme sayfasina yonlendiriliyor...", order_created: "Siparis olusturuldu", faq_title: "SSS", faq_question_1: "Nasil yukleme yapilir?", faq_answer_1: "Kullanici ID girin, coin miktarini yazin ve yuklemeyi onaylayin.", faq_question_2: "Yukleme sonucu nasil kontrol edilir?", faq_answer_2: "Yuklemeden sonra kullanicinin coin bakiyesini kontrol edin." }, id: { document_title: "Pusat Isi Ulang", page_label: "Pusat Isi Ulang", back: "Kembali", title: "Pusat Isi Ulang", language_button_aria: "Ubah bahasa", help: "Bantuan", close: "Tutup", my_account: "Akun saya", profile_card: "Profil pengguna", whatsapp_label: "WhatsApp:", add_whatsapp: "+ tambah whatsapp", add_whatsapp_title: "Tambah WhatsApp", edit_whatsapp: "Ubah", edit_whatsapp_title: "Ubah WhatsApp", whatsapp_placeholder: "Masukkan WhatsApp", whatsapp_required: "Masukkan WhatsApp", whatsapp_too_long: "WhatsApp tidak boleh lebih dari 64 karakter", whatsapp_saved: "WhatsApp tersimpan", save: "Simpan", saving: "Menyimpan...", balance_title: "Koin", history: "Riwayat", search_title: "Isi ulang pengguna", search_subtitle: "Cari dengan ID pengguna", search_placeholder: "Masukkan ID pengguna", search: "Cari", cancel: "Batal", searching: "Mencari...", target_user: "Pengguna tujuan", uid_prefix: "UID:", change: "Ganti", coin_recharge_title: "Isi ulang koin", coin_recharge_subtitle: "Masukkan jumlah koin", coin_amount_placeholder: "Masukkan jumlah koin", confirm_recharge: "Konfirmasi isi ulang", auto_verify_title: "Verifikasi isi ulang otomatis", auto_verify_subtitle: "Verifikasi transfer Binance dan kirim koin", open_auto_verify: "Buka", close_auto_verify: "Tutup", order_no: "No. pesanan", order_no_placeholder: "Masukkan no. pesanan", transfer_amount_usdt: "Jumlah transfer", transfer_amount_placeholder: "Masukkan jumlah USDT", transfer_type: "Jenis transfer", binance_pay_transfer: "Internal Binance", chain_transfer: "Transfer on-chain", bill_no: "No. tagihan", bill_no_placeholder: "Masukkan no. tagihan atau txId", verify_recharge: "Verifikasi isi ulang", verifying: "Memverifikasi...", auto_verify_success: "Verifikasi berhasil", please_enter_order_no: "Masukkan no. pesanan", please_enter_bill_no: "Masukkan no. tagihan", loading_data: "Memuat...", loading_mask: "memuat....", no_country: "Negara tidak tersedia", no_packages: "Paket isi ulang tidak tersedia", user_not_found: "User tidak ditemukan", missing_token: "Token tidak ditemukan", failed_to_load: "Gagal memuat data", recharging: "Mengisi ulang...", recharge_success: "Isi ulang berhasil", please_enter_valid_amount: "Masukkan jumlah yang valid", transaction_records: "Riwayat transaksi", income: "Masuk", expenditure: "Keluar", enter_user_id: "Masukkan ID pengguna", coins: "koin", no_transaction_records: "Tidak ada riwayat transaksi", no_selling_records_yet: "Belum ada riwayat", load_more: "Muat lagi", payment_not_available: "Pembayaran tidak tersedia", creating_order: "Membuat pesanan...", redirecting_payment: "Membuka pembayaran...", order_created: "Pesanan dibuat", faq_title: "FAQ", faq_question_1: "Bagaimana cara isi ulang?", faq_answer_1: "Masukkan ID pengguna, isi jumlah koin, lalu konfirmasi isi ulang.", faq_question_2: "Bagaimana cara memeriksa hasil isi ulang?", faq_answer_2: "Setelah isi ulang, periksa saldo koin pengguna." } }; const params = readURLParams(); let currentLang = normalizeLanguage(params.get("lang")); let currentMessages = messages[currentLang] || messages.en; const state = { query: params.get("account") || params.get("targetAccount") || params.get("target") || "", searchLocked: false, loadingConfig: false, ordering: false, activeOrderKey: "", autoVerifyOpen: false, autoVerifyLocked: false, autoVerifyStatusText: "", autoVerifyStatusType: "", autoVerifyForm: { orderNo: "", transferAmount: "", transferType: "BINANCE_PAY", billNo: "" }, rechargeAmount: "", rechargeLocked: false, rechargeStatusText: "", rechargeStatusType: "", historyOpen: false, historyLoading: false, historyTab: 0, historySearch: "", historyStatusText: "", historyStatusType: "", historyIncome: [], historyExpenditure: [], historyHasMore: { 0: true, 1: true }, searchStatusText: "", searchStatusType: "", paymentStatusText: "", paymentStatusType: "", selfProfile: null, sellerProfile: null, whatsapp: "", whatsappOpen: false, whatsappDraft: "", whatsappSaving: false, whatsappStatusText: "", whatsappStatusType: "", coins: 0, profile: null, application: null, appCode: "", rechargeType: "GOLD", payProfile: null, countryList: [], selectedCountryId: "", regionId: "", commodity: [], channels: [] }; 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(); if (value.startsWith("ar")) return "ar"; if (value.startsWith("tr")) return "tr"; if (value.startsWith("id")) return "id"; return supportedLanguages.includes(value) ? value : "en"; } function message(key, fallback = "") { return currentMessages[key] || messages.en[key] || fallback || key; } function renderMessages(dictionary) { document.querySelectorAll("[data-i18n]").forEach((node) => { const value = dictionary[node.dataset.i18n] || messages.en[node.dataset.i18n]; if (value) node.textContent = value; }); document.querySelectorAll("[data-i18n-aria]").forEach((node) => { const value = dictionary[node.dataset.i18nAria] || messages.en[node.dataset.i18nAria]; if (value) node.setAttribute("aria-label", value); }); document.querySelectorAll("[data-i18n-placeholder]").forEach((node) => { const value = dictionary[node.dataset.i18nPlaceholder] || messages.en[node.dataset.i18nPlaceholder]; if (value) node.setAttribute("placeholder", value); }); } function apiBaseURL() { const override = window.HYAPP_API_BASE_URL || params.get("apiBase"); if (override) return String(override).replace(/\/+$/, ""); return API_BASE_URL.replace(/\/+$/, ""); } function goApiBaseURL() { const override = window.HYAPP_GO_API_BASE_URL || params.get("goApiBase") || params.get("goBase"); if (override) return String(override).replace(/\/+$/, ""); return apiBaseURL(); } function normalizeAuthorization(token) { const value = String(token || "").trim(); if (!value) return ""; return /^bearer\s+/i.test(value) ? value : `Bearer ${value}`; } function authHeadersFromToken(token) { return { 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" }; } function buildRequestURL(baseURL, path) { const base = `${String(baseURL || API_BASE_URL).replace(/\/+$/, "")}/`; const target = String(path || "").replace(/^\/+/, ""); return new URL(target, base).toString(); } async function requestBaseJSON(baseURL, path, options = {}) { const token = readRawParam("token"); if (!token) throw new Error(message("missing_token")); const headers = authHeadersFromToken(token); const request = { method: options.method || "GET", cache: "no-store", headers }; if (options.body !== undefined) { headers["Content-Type"] = "application/json"; request.body = JSON.stringify(options.body); } const response = await fetch(buildRequestURL(baseURL, path), request); 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.body ?? data.data ?? null; } async function requestJSON(path, options = {}) { return requestBaseJSON(apiBaseURL(), path, options); } async function requestGoJSON(path, options = {}) { return requestBaseJSON(goApiBaseURL(), path, options); } function endpointOpenSearch(account) { return `/user/user-profile/open-search?account=${encodeURIComponent(account)}`; } function endpointSelfProfile() { return "/team/member/profile"; } function endpointSellerProfile() { return "/app/h5/recharge-agency/profile"; } function endpointSellerWhatsapp() { return "/app/h5/recharge-agency/profile/whatsapp"; } function endpointCoinBalance() { return "/wallet/freight/balance"; } function endpointFreightShip() { return "/wallet/freight/ship"; } function endpointAutoVerify() { return "/app/h5/binance-recharge/verify"; } function endpointTransferHistory({ userId, type, searchId = "", lastId = "" }) { const query = new URLSearchParams({ userId: String(userId || ""), type: String(type), searchId: String(searchId || ""), lastId: String(lastId || "") }); return `/wallet/freight/balance/running/water/client/flowByUserId?${query.toString()}`; } function endpointApplication() { return `/order/web/pay/application/${encodeURIComponent(RECHARGE_APPLICATION_ID)}`; } function endpointPayProfile(appCode, account, type) { const query = new URLSearchParams({ sysOrigin: appCode, account, type }); return `/order/web/pay/user-profile?${query.toString()}`; } function endpointCommodity(countryId, regionId, type) { const query = new URLSearchParams({ applicationId: RECHARGE_APPLICATION_ID, payCountryId: countryId, regionId: regionId || "", type }); return `/order/web/pay/commodity?${query.toString()}`; } function setLoading(isLoading) { const root = document.querySelector(".recharge-center"); const mask = document.querySelector("#loadingMask"); if (root) root.dataset.loading = isLoading ? "true" : "false"; if (mask) mask.hidden = !isLoading; } function updateModalLock() { const hasOpen = Array.from(document.querySelectorAll(".help-modal, .history-modal, .whatsapp-modal")).some((modal) => !modal.hidden); document.body.classList.toggle("modal-open", hasOpen); } function setModalOpen(selector, open) { const modal = document.querySelector(selector); if (!modal) return; modal.hidden = !open; updateModalLock(); } function showToast(text) { const toast = document.querySelector("#toast"); if (!toast) return; toast.textContent = text || ""; toast.hidden = !text; window.clearTimeout(showToast.timer); showToast.timer = window.setTimeout(() => { toast.hidden = true; }, 2500); } function replaceChildren(node, children) { while (node.firstChild) node.removeChild(node.firstChild); children.forEach((child) => node.appendChild(child)); } function formatMoney(value) { const number = Number(value); if (!Number.isFinite(number)) return "0.00"; return number.toFixed(2); } function pickCoinAmount(body) { if (typeof body === "number" || typeof body === "string") return body; if (body && typeof body === "object") { return body.gold ?? body.goldBalance ?? body.coin ?? body.coins ?? body.balance ?? body.amount ?? 0; } return 0; } function pickWhatsapp(body) { if (typeof body === "string") return body.trim(); if (body && typeof body === "object") { return String(body.whatsapp || body.whatsApp || body.contactInfo || "").trim(); } return ""; } function formatCoins(value) { const number = Number(value); if (!Number.isFinite(number)) return "0"; return Number.isInteger(number) ? String(number) : String(Number(number.toFixed(2))); } function normalizeRecordList(body) { if (Array.isArray(body)) return body; if (Array.isArray(body?.records)) return body.records; if (Array.isArray(body?.list)) return body.list; if (Array.isArray(body?.content)) return body.content; if (Array.isArray(body?.rows)) return body.rows; return []; } function formatRecordTime(value) { if (!value) return "-"; const date = new Date(Number(value) || value); if (Number.isNaN(date.getTime())) return String(value); const pad = (item) => String(item).padStart(2, "0"); return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`; } function pickSpecialAccount(profile) { return profile?.ownSpecialId?.account || profile?.specialId?.account || profile?.specialAccount || profile?.shortAccount || profile?.shortId || ""; } function pickProfileName(profile) { return profile?.userNickname || profile?.name || "-"; } function pickDisplayAccount(profile) { return pickSpecialAccount(profile) || profile?.account || profile?.id || "-"; } function pickApiAccount(profile) { return profile?.account || state.query || pickSpecialAccount(profile) || ""; } function pickProfileId(profile) { return profile?.id || profile?.userId || ""; } function pickRecordUser(record) { if (Number(record?.type) === 1) { return record?.acceptUserAccount || record?.acceptUser || record?.targetUser || {}; } return record?.userAccount || record?.user || record?.sourceUser || {}; } function commodityType(profile = state.profile) { if (profile?.isSuperFreightAgent) return "FREIGHT_GOLD_SUPER"; if (profile?.isFreightAgent) return "FREIGHT_GOLD"; return "GOLD"; } function setSearchStatus(text = "", type = "") { state.searchStatusText = text; state.searchStatusType = type; const status = document.querySelector("#searchStatus"); if (!status) return; status.textContent = text; status.hidden = !text; status.classList.toggle("is-error", type === "error"); } function setPaymentStatus(text = "", type = "") { state.paymentStatusText = text; state.paymentStatusType = type; const status = document.querySelector("#paymentStatus"); if (!status) return; status.textContent = text; status.hidden = !text; status.classList.toggle("is-error", type === "error"); } function setRechargeStatus(text = "", type = "") { state.rechargeStatusText = text; state.rechargeStatusType = type; const status = document.querySelector("#coinRechargeStatus"); if (!status) return; status.textContent = text; status.hidden = !text; status.classList.toggle("is-error", type === "error"); } function setAutoVerifyStatus(text = "", type = "") { state.autoVerifyStatusText = text; state.autoVerifyStatusType = type; const status = document.querySelector("#autoVerifyStatus"); if (!status) return; status.textContent = text; status.hidden = !text; status.classList.toggle("is-error", type === "error"); } function setHistoryStatus(text = "", type = "") { state.historyStatusText = text; state.historyStatusType = type; const status = document.querySelector("#historyStatus"); if (!status) return; status.textContent = text; status.hidden = !text; status.classList.toggle("is-error", type === "error"); } function setWhatsappStatus(text = "", type = "") { state.whatsappStatusText = text; state.whatsappStatusType = type; const status = document.querySelector("#whatsappStatus"); if (!status) return; status.textContent = text; status.hidden = !text; status.classList.toggle("is-error", type === "error"); } function sanitizeCoinAmount(value) { return String(value || "").replace(/\D/g, "").replace(/^0+(?=\d)/, ""); } function sanitizeMoneyAmount(value) { const raw = String(value || "").replace(/[^\d.]/g, ""); const parts = raw.split("."); const integerPart = parts.shift() || ""; const decimalPart = parts.join("").slice(0, 8); const normalizedInteger = integerPart.replace(/^0+(?=\d)/, ""); return decimalPart ? `${normalizedInteger || "0"}.${decimalPart}` : normalizedInteger; } function renderSearch() { const input = document.querySelector("#accountInput"); const button = document.querySelector("#searchButton"); if (input && document.activeElement !== input) input.value = state.query; if (button) { const hasQuery = Boolean(String(state.query || "").trim()); button.disabled = state.searchLocked || !hasQuery; button.textContent = state.searchLocked ? message("searching") : message("search"); } setSearchStatus(state.searchStatusText, state.searchStatusType); } function renderProfileAvatar(profile, imageSelector, fallbackSelector, defaultLetter = "U") { const image = document.querySelector(imageSelector); const fallback = document.querySelector(fallbackSelector); if (!image || !fallback) return; const name = pickProfileName(profile); const avatar = profile?.userAvatar || profile?.avatar || ""; fallback.textContent = String(name || defaultLetter).slice(0, 1).toUpperCase() || defaultLetter; if (!avatar) { image.hidden = true; image.removeAttribute("src"); fallback.hidden = false; return; } image.alt = name; fallback.hidden = true; image.hidden = false; image.onerror = () => { image.hidden = true; image.removeAttribute("src"); fallback.hidden = false; }; image.src = avatar; } function renderSelfProfile() { const profile = state.selfProfile || {}; const nameNode = document.querySelector("#selfName"); const uidNode = document.querySelector("#selfUid"); if (nameNode) nameNode.textContent = pickProfileName(profile); if (uidNode) uidNode.textContent = `${message("uid_prefix")} ${pickDisplayAccount(profile)}`.trim(); renderProfileAvatar(profile, "#selfAvatar", "#selfAvatarFallback"); renderSelfWhatsapp(); } function renderSelfWhatsapp() { const icon = document.querySelector("#selfWhatsappIcon"); const text = document.querySelector("#selfWhatsappText"); const button = document.querySelector("#addWhatsappButton"); const whatsapp = String(state.whatsapp || "").trim(); if (icon) icon.hidden = !whatsapp; if (text) { text.textContent = whatsapp; text.hidden = !whatsapp; text.disabled = !whatsapp; if (whatsapp) { text.setAttribute("aria-label", `${message("edit_whatsapp")}: ${whatsapp}`); } else { text.removeAttribute("aria-label"); } } if (button) { button.hidden = false; button.textContent = whatsapp ? message("edit_whatsapp") : message("add_whatsapp"); } } function renderBalance() { const node = document.querySelector("#selfBalance"); if (node) node.textContent = formatCoins(state.coins); } function renderTarget() { const card = document.querySelector("#targetCard"); if (card) card.hidden = !state.profile; if (!state.profile) return; const nameNode = document.querySelector("#targetName"); const uidNode = document.querySelector("#targetUid"); if (nameNode) nameNode.textContent = pickProfileName(state.profile); if (uidNode) uidNode.textContent = `${message("uid_prefix")} ${pickDisplayAccount(state.profile)}`.trim(); renderProfileAvatar(state.profile, "#targetAvatar", "#targetAvatarFallback"); } function renderAutoVerify() { const card = document.querySelector("#autoVerifyCard"); const form = document.querySelector("#autoVerifyForm"); const toggle = document.querySelector("#autoVerifyToggle"); const orderInput = document.querySelector("#autoOrderInput"); const amountInput = document.querySelector("#autoAmountInput"); const typeSelect = document.querySelector("#autoTransferType"); const billInput = document.querySelector("#autoBillInput"); const button = document.querySelector("#autoVerifyButton"); const values = state.autoVerifyForm; const amount = Number(values.transferAmount); const canSubmit = Boolean( state.profile && !state.autoVerifyLocked && String(values.orderNo || "").trim() && Number.isFinite(amount) && amount > 0 && String(values.billNo || "").trim() ); if (card) card.hidden = !state.profile; if (form) form.hidden = !state.autoVerifyOpen || !state.profile; if (toggle) { toggle.hidden = !state.profile; toggle.textContent = state.autoVerifyOpen ? message("close_auto_verify") : message("open_auto_verify"); } if (orderInput && document.activeElement !== orderInput) orderInput.value = values.orderNo; if (amountInput && document.activeElement !== amountInput) amountInput.value = values.transferAmount; if (typeSelect && document.activeElement !== typeSelect) typeSelect.value = values.transferType; if (billInput && document.activeElement !== billInput) billInput.value = values.billNo; if (button) { button.disabled = !canSubmit; button.textContent = state.autoVerifyLocked ? message("verifying") : message("verify_recharge"); } setAutoVerifyStatus(state.autoVerifyStatusText, state.autoVerifyStatusType); } function renderCoinRecharge() { const card = document.querySelector("#coinRechargeCard"); const input = document.querySelector("#coinAmountInput"); const button = document.querySelector("#confirmRechargeButton"); const amount = Number(state.rechargeAmount); const canSubmit = Boolean(state.profile && Number.isFinite(amount) && amount > 0 && !state.rechargeLocked); if (card) card.hidden = !state.profile; if (input && document.activeElement !== input) input.value = state.rechargeAmount; if (button) { button.disabled = !canSubmit; button.textContent = state.rechargeLocked ? message("recharging") : message("confirm_recharge"); } setRechargeStatus(state.rechargeStatusText, state.rechargeStatusType); } function activeHistoryList() { return state.historyTab === 1 ? state.historyExpenditure : state.historyIncome; } function setActiveHistoryList(records) { if (state.historyTab === 1) state.historyExpenditure = records; else state.historyIncome = records; } function createHistoryAvatar(user) { const avatar = document.createElement("div"); avatar.className = "history-avatar"; const url = user?.userAvatar || user?.avatar || ""; const name = user?.userNickName || user?.userNickname || user?.name || "-"; if (url) { const image = document.createElement("img"); image.src = url; image.alt = ""; image.addEventListener("error", () => { avatar.textContent = String(name).slice(0, 1).toUpperCase() || "U"; }); avatar.appendChild(image); } else { avatar.textContent = String(name).slice(0, 1).toUpperCase() || "U"; } return avatar; } function createHistoryRecord(record) { const user = pickRecordUser(record); const item = document.createElement("article"); item.className = "history-record"; const userWrap = document.createElement("div"); userWrap.className = "history-record-user"; const copy = document.createElement("div"); copy.className = "history-record-copy"; const name = document.createElement("div"); name.className = "history-record-name"; name.textContent = user?.userNickName || user?.userNickname || user?.name || "-"; const account = document.createElement("div"); account.className = "history-record-account"; account.textContent = `${message("uid_prefix")} ${user?.account || user?.id || "-"}`.trim(); copy.append(name, account); userWrap.append(createHistoryAvatar(user), copy); const side = document.createElement("div"); side.className = "history-record-side"; const amount = document.createElement("div"); const isIncome = Number(record?.type) === 0; amount.className = `history-record-amount ${isIncome ? "is-income" : "is-expense"}`; amount.textContent = `${isIncome ? "+" : "-"}${formatCoins(record?.quantity)} ${message("coins")}`; const time = document.createElement("div"); time.className = "history-record-time"; time.textContent = formatRecordTime(record?.createTime); side.append(amount, time); item.append(userWrap, side); return item; } function renderHistoryModal() { const modal = document.querySelector("#historyModal"); const input = document.querySelector("#historySearchInput"); const list = document.querySelector("#historyList"); const loadMore = document.querySelector("#historyLoadMore"); if (modal) modal.hidden = !state.historyOpen; if (input && document.activeElement !== input) input.value = state.historySearch; document.querySelectorAll("[data-history-tab]").forEach((button) => { const active = Number(button.dataset.historyTab) === state.historyTab; button.classList.toggle("is-active", active); button.setAttribute("aria-selected", String(active)); }); setHistoryStatus(state.historyStatusText, state.historyStatusType); if (list) { const records = activeHistoryList(); if (state.historyLoading && !records.length) { const loading = document.createElement("div"); loading.className = "history-empty"; loading.textContent = message("loading_data"); replaceChildren(list, [loading]); } else if (!records.length) { const empty = document.createElement("div"); empty.className = "history-empty"; const title = document.createElement("strong"); title.textContent = message("no_transaction_records"); const text = document.createElement("span"); text.textContent = message("no_selling_records_yet"); empty.append(title, text); replaceChildren(list, [empty]); } else { replaceChildren(list, records.map(createHistoryRecord)); } } if (loadMore) { const hasRecords = activeHistoryList().length > 0; loadMore.hidden = state.historyLoading || !hasRecords || !state.historyHasMore[state.historyTab]; loadMore.disabled = state.historyLoading; } updateModalLock(); } function renderWhatsappModal() { const modal = document.querySelector("#whatsappModal"); const title = document.querySelector("#whatsappTitle"); const input = document.querySelector("#whatsappInput"); const button = document.querySelector("#saveWhatsappButton"); if (modal) modal.hidden = !state.whatsappOpen; if (title) title.textContent = state.whatsapp ? message("edit_whatsapp_title") : message("add_whatsapp_title"); if (input && document.activeElement !== input) input.value = state.whatsappDraft; if (button) { button.disabled = state.whatsappSaving || !String(state.whatsappDraft || "").trim(); button.textContent = state.whatsappSaving ? message("saving") : message("save"); } setWhatsappStatus(state.whatsappStatusText, state.whatsappStatusType); updateModalLock(); } function countryName(item) { return item?.countryName || item?.country?.aliasName || item?.country?.countryName || item?.name || "-"; } function countryFlag(item) { return item?.nationalFlag || item?.country?.nationalFlag || item?.flag || ""; } function selectedCountry() { return state.countryList.find((item) => String(item?.id) === String(state.selectedCountryId)) || state.countryList[0] || null; } function createCountryChip(item) { const button = document.createElement("button"); button.className = "country-chip"; button.type = "button"; const active = String(item?.id) === String(state.selectedCountryId); button.classList.toggle("is-active", active); button.disabled = state.loadingConfig || state.ordering; button.addEventListener("click", () => selectCountry(item?.id)); const flag = document.createElement("span"); flag.className = "country-flag"; const flagUrl = countryFlag(item); if (flagUrl) { const image = document.createElement("img"); image.src = flagUrl; image.alt = ""; image.addEventListener("error", () => { flag.textContent = String(countryName(item)).slice(0, 2).toUpperCase(); }); flag.appendChild(image); } else { flag.textContent = String(countryName(item)).slice(0, 2).toUpperCase(); } const name = document.createElement("span"); name.className = "country-name"; name.textContent = countryName(item); button.append(flag, name); return button; } function renderCountries() { const card = document.querySelector("#countryCard"); const list = document.querySelector("#countryList"); const subtitle = document.querySelector("#countrySubtitle"); if (card) card.hidden = !state.profile; if (!list || !state.profile) return; const country = selectedCountry(); if (subtitle) subtitle.textContent = country ? countryName(country) : message("no_country"); if (state.loadingConfig && !state.countryList.length) { const loading = document.createElement("div"); loading.className = "empty-state"; loading.textContent = message("loading_data"); replaceChildren(list, [loading]); return; } if (!state.countryList.length) { const empty = document.createElement("div"); empty.className = "empty-state"; empty.textContent = message("no_country"); replaceChildren(list, [empty]); return; } replaceChildren(list, state.countryList.map(createCountryChip)); } function channelCode(item) { return item?.channel?.channelCode || item?.details?.channelCode || item?.channelCode || ""; } function channelName(item) { return item?.channel?.channelName || item?.channelName || channelCode(item) || "-"; } function channelIcon(item) { return item?.channel?.channelIcon || item?.channelIcon || ""; } function createChannelIcon(item) { const icon = document.createElement("div"); icon.className = "channel-icon"; const url = channelIcon(item); if (!url) { icon.textContent = String(channelName(item)).slice(0, 1).toUpperCase(); return icon; } const image = document.createElement("img"); image.src = url; image.alt = ""; image.addEventListener("error", () => { icon.textContent = String(channelName(item)).slice(0, 1).toUpperCase(); }); icon.appendChild(image); return icon; } function packageKey(channel, goods) { return `${channelCode(channel)}:${goods?.id || ""}`; } function createPackageButton(channel, goods) { const button = document.createElement("button"); button.className = "package-button"; button.type = "button"; const key = packageKey(channel, goods); button.disabled = state.ordering || state.loadingConfig; button.addEventListener("click", () => placeOrder(channel, goods)); const coin = document.createElement("span"); coin.className = "coin-mark gold-coin-icon"; const shine = document.createElement("span"); shine.className = "gold-coin-shine"; coin.appendChild(shine); const content = document.createElement("span"); content.className = "package-content"; content.textContent = goods?.content || goods?.awardContent || "-"; const price = document.createElement("span"); price.className = "package-price"; price.textContent = state.ordering && state.activeOrderKey === key ? message("creating_order") : `$${formatMoney(goods?.amountUsd)}`; button.append(coin, content, price); return button; } function createChannelCard(item) { const card = document.createElement("article"); card.className = "channel-card"; const head = document.createElement("div"); head.className = "channel-head"; const name = document.createElement("div"); name.className = "channel-name"; name.textContent = channelName(item); head.append(createChannelIcon(item), name); const grid = document.createElement("div"); grid.className = "package-grid"; state.commodity.forEach((goods) => grid.appendChild(createPackageButton(item, goods))); card.append(head, grid); return card; } function renderPayment() { const card = document.querySelector("#paymentCard"); const list = document.querySelector("#channelList"); if (card) card.hidden = !state.profile; if (!list || !state.profile) return; setPaymentStatus(state.paymentStatusText, state.paymentStatusType); if (state.loadingConfig) { const loading = document.createElement("div"); loading.className = "empty-state"; loading.textContent = message("loading_data"); replaceChildren(list, [loading]); return; } if (!state.channels.length || !state.commodity.length) { const empty = document.createElement("div"); empty.className = "empty-state"; empty.textContent = message("no_packages"); replaceChildren(list, [empty]); return; } replaceChildren(list, state.channels.map(createChannelCard)); } function renderAll() { renderSelfProfile(); renderBalance(); renderSearch(); renderTarget(); renderAutoVerify(); renderCoinRecharge(); renderWhatsappModal(); } async function fetchSelfProfile() { state.selfProfile = await requestJSON(endpointSelfProfile()) || {}; renderSelfProfile(); return state.selfProfile; } async function fetchSellerProfile() { state.sellerProfile = await requestGoJSON(endpointSellerProfile()) || {}; state.whatsapp = pickWhatsapp(state.sellerProfile); renderSelfWhatsapp(); return state.sellerProfile; } async function fetchCoinBalance() { const body = await requestJSON(endpointCoinBalance()); state.coins = pickCoinAmount(body); renderBalance(); return state.coins; } function resetHistoryCache() { state.historyIncome = []; state.historyExpenditure = []; state.historyHasMore = { 0: true, 1: true }; state.historyStatusText = ""; state.historyStatusType = ""; } async function fetchHistoryRecords({ append = false } = {}) { const userId = pickProfileId(state.selfProfile); if (!userId) { setHistoryStatus(message("user_not_found"), "error"); renderHistoryModal(); return; } const currentRecords = activeHistoryList(); const lastId = append && currentRecords.length ? currentRecords[currentRecords.length - 1]?.id || "" : ""; state.historyLoading = true; setHistoryStatus(message("loading_data")); renderHistoryModal(); try { const body = await requestJSON(endpointTransferHistory({ userId, type: state.historyTab, searchId: state.historySearch, lastId })); const records = normalizeRecordList(body); setActiveHistoryList(append ? currentRecords.concat(records) : records); state.historyHasMore[state.historyTab] = records.length > 0; setHistoryStatus(""); } catch (error) { console.error("Failed to load transfer history:", error); const text = error.response?.errorMsg || error.message || message("failed_to_load"); setHistoryStatus(text, "error"); showToast(text); } finally { state.historyLoading = false; renderHistoryModal(); } } async function fetchApplication() { state.application = await requestJSON(endpointApplication()) || {}; state.appCode = state.application.appCode || ""; return state.application; } async function fetchRechargeProfile() { if (!state.application || !state.appCode) await fetchApplication(); const account = pickApiAccount(state.profile); state.rechargeType = commodityType(state.profile); const data = await requestJSON(endpointPayProfile(state.appCode, account, state.rechargeType)); state.payProfile = data?.userProfile || null; state.countryList = Array.isArray(data?.countryList) ? data.countryList : []; state.regionId = data?.regionId || ""; if (state.payProfile) { state.profile = { ...state.profile, ...state.payProfile, isFreightAgent: state.profile?.isFreightAgent, isSuperFreightAgent: state.profile?.isSuperFreightAgent }; } if (!state.countryList.some((item) => String(item?.id) === String(state.selectedCountryId))) { state.selectedCountryId = state.countryList[0]?.id ? String(state.countryList[0].id) : ""; } } async function fetchCommodities() { const country = selectedCountry(); if (!country?.id) { state.commodity = []; state.channels = []; return; } const data = await requestJSON(endpointCommodity(String(country.id), state.regionId, state.rechargeType)); state.commodity = Array.isArray(data?.commodity) ? data.commodity : []; state.channels = Array.isArray(data?.channels) ? data.channels : []; if (data?.application) state.application = data.application; } async function loadRechargeOptions() { state.loadingConfig = true; state.paymentStatusText = message("loading_data"); state.paymentStatusType = ""; state.countryList = []; state.selectedCountryId = ""; state.commodity = []; state.channels = []; renderAll(); try { await fetchRechargeProfile(); await fetchCommodities(); setPaymentStatus(""); } catch (error) { console.error("Failed to load recharge options:", error); state.paymentStatusText = error.response?.errorMsg || error.message || message("failed_to_load"); state.paymentStatusType = "error"; showToast(state.paymentStatusText); } finally { state.loadingConfig = false; renderAll(); } } async function searchTarget(event) { if (event) event.preventDefault(); if (state.searchLocked) return; const input = document.querySelector("#accountInput"); const query = String(input?.value || state.query || "").trim(); state.query = query; if (!query) { setSearchStatus(""); renderAll(); return; } state.searchLocked = true; state.profile = null; state.payProfile = null; state.autoVerifyOpen = false; state.autoVerifyForm = { orderNo: "", transferAmount: "", transferType: "BINANCE_PAY", billNo: "" }; state.autoVerifyStatusText = ""; state.autoVerifyStatusType = ""; state.rechargeAmount = ""; state.rechargeStatusText = ""; state.rechargeStatusType = ""; state.countryList = []; state.selectedCountryId = ""; state.commodity = []; state.channels = []; setSearchStatus(message("searching")); setPaymentStatus(""); renderAll(); try { const result = await requestJSON(endpointOpenSearch(query)); const profile = Array.isArray(result) ? result[0] : result; if (!profile || !pickProfileId(profile)) throw new Error(message("user_not_found")); state.profile = profile; state.autoVerifyForm = { orderNo: "", transferAmount: "", transferType: "BINANCE_PAY", billNo: "" }; state.autoVerifyStatusText = ""; state.autoVerifyStatusType = ""; state.rechargeAmount = ""; state.rechargeStatusText = ""; state.rechargeStatusType = ""; setSearchStatus(""); renderAll(); } catch (error) { console.error("Recharge target search failed:", error); const text = error.response?.errorMsg || error.message || message("user_not_found"); setSearchStatus(text, "error"); showToast(text); } finally { state.searchLocked = false; renderAll(); } } async function selectCountry(id) { if (!id || String(id) === String(state.selectedCountryId) || state.loadingConfig || state.ordering) return; state.selectedCountryId = String(id); state.loadingConfig = true; state.commodity = []; state.channels = []; state.paymentStatusText = message("loading_data"); state.paymentStatusType = ""; renderAll(); try { await fetchCommodities(); setPaymentStatus(""); } catch (error) { console.error("Failed to switch recharge country:", error); state.paymentStatusText = error.response?.errorMsg || error.message || message("failed_to_load"); state.paymentStatusType = "error"; showToast(state.paymentStatusText); } finally { state.loadingConfig = false; renderAll(); } } async function verifyAutoRecharge(event) { if (event) event.preventDefault(); if (state.autoVerifyLocked) return; const values = state.autoVerifyForm; values.orderNo = String(document.querySelector("#autoOrderInput")?.value || values.orderNo || "").trim(); values.transferAmount = sanitizeMoneyAmount(document.querySelector("#autoAmountInput")?.value || values.transferAmount); values.transferType = String(document.querySelector("#autoTransferType")?.value || values.transferType || "BINANCE_PAY"); values.billNo = String(document.querySelector("#autoBillInput")?.value || values.billNo || "").trim(); const userId = pickProfileId(state.profile); const amount = Number(values.transferAmount); if (!userId) { const text = message("user_not_found"); setAutoVerifyStatus(text, "error"); showToast(text); renderAll(); return; } if (!values.orderNo) { const text = message("please_enter_order_no"); setAutoVerifyStatus(text, "error"); showToast(text); renderAll(); return; } if (!Number.isFinite(amount) || amount <= 0) { const text = message("please_enter_valid_amount"); setAutoVerifyStatus(text, "error"); showToast(text); renderAll(); return; } if (!values.billNo) { const text = message("please_enter_bill_no"); setAutoVerifyStatus(text, "error"); showToast(text); renderAll(); return; } state.autoVerifyLocked = true; setAutoVerifyStatus(message("verifying")); renderAll(); try { const result = await requestGoJSON(endpointAutoVerify(), { method: "POST", body: { billNo: values.billNo, orderNo: values.orderNo, targetUserId: String(userId), transferAmount: values.transferAmount, transferType: values.transferType } }) || {}; const gold = formatCoins(result.goldAmount || 0); const successText = `${message("auto_verify_success")}: ${gold} ${message("coins")}`; state.autoVerifyForm = { orderNo: "", transferAmount: "", transferType: "BINANCE_PAY", billNo: "" }; resetHistoryCache(); setAutoVerifyStatus(successText); showToast(successText); if (String(userId) === String(pickProfileId(state.selfProfile))) { await fetchCoinBalance(); } if (state.historyOpen) fetchHistoryRecords(); } catch (error) { console.error("Auto recharge verification failed:", error); const text = error.response?.errorMsg || error.message || message("failed_to_load"); setAutoVerifyStatus(text, "error"); showToast(text); } finally { state.autoVerifyLocked = false; renderAll(); } } async function rechargeCoins(event) { if (event) event.preventDefault(); if (state.rechargeLocked) return; const input = document.querySelector("#coinAmountInput"); const amountValue = sanitizeCoinAmount(input?.value || state.rechargeAmount); state.rechargeAmount = amountValue; if (input) input.value = amountValue; const quantity = Number(amountValue); const userId = pickProfileId(state.profile); if (!userId) { const text = message("user_not_found"); setRechargeStatus(text, "error"); showToast(text); renderAll(); return; } if (!Number.isFinite(quantity) || quantity <= 0) { const text = message("please_enter_valid_amount"); setRechargeStatus(text, "error"); showToast(text); renderAll(); return; } state.rechargeLocked = true; setRechargeStatus(message("recharging")); renderAll(); try { const result = await requestJSON(endpointFreightShip(), { method: "POST", body: { acceptUserId: String(userId), quantity } }); if (result !== null && result !== undefined) { state.coins = pickCoinAmount(result); renderBalance(); } else { await fetchCoinBalance(); } state.rechargeAmount = ""; resetHistoryCache(); setRechargeStatus(message("recharge_success")); showToast(message("recharge_success")); if (state.historyOpen) fetchHistoryRecords(); } catch (error) { console.error("Recharge coins failed:", error); const text = error.response?.errorMsg || error.message || message("failed_to_load"); setRechargeStatus(text, "error"); showToast(text); } finally { state.rechargeLocked = false; renderAll(); } } function payEnvironment() { const value = String(window.AIRWALLEX_ENV || params.get("payEnv") || window.ENV || "").toLowerCase(); if (value === "production" || value === "prod") return "prod"; if (value === "staging") return "staging"; if (value === "dev") return "dev"; if (value === "local") return "local"; return "demo"; } function airwallexHost(env) { const hosts = { prod: "checkout.airwallex.com", demo: "checkout-demo.airwallex.com", staging: "checkout-staging.airwallex.com", dev: "checkout-dev.airwallex.com", local: "localhost:3000" }; return `https://${hosts[env] || hosts.prod}`; } function loadAirwallex(env) { if (window.Airwallex) { window.Airwallex.init({ env }); return Promise.resolve(window.Airwallex); } const src = `${airwallexHost(env)}/assets/elements.bundle.min.js?version=1.160.0`; const existing = document.querySelector(`[data-airwallex-script="${env}"]`); const script = existing || document.createElement("script"); if (!existing) { script.src = src; script.crossOrigin = "anonymous"; script.dataset.airwallexScript = env; document.head.appendChild(script); } return new Promise((resolve, reject) => { script.addEventListener("load", () => { if (!window.Airwallex) { reject(new Error("Failed to load Airwallex")); return; } window.Airwallex.init({ env }); resolve(window.Airwallex); }, { once: true }); script.addEventListener("error", () => reject(new Error("Failed to load Airwallex")), { once: true }); }); } function parseExpand(value) { if (!value) return {}; if (typeof value === "object") return value; try { return JSON.parse(value); } catch (error) { return {}; } } function paymentResultURL(status) { const type = params.get("type") || ""; const query = new URLSearchParams({ redirect: "recharge", appType: type, appId: RECHARGE_APPLICATION_ID, appStatus: status }); return `${window.location.origin}/#/pay-result?${query.toString()}`; } async function redirectAirwallex(result) { const env = payEnvironment(); const expand = parseExpand(result.expand); if (!expand.id || !expand.client_secret) throw new Error(message("payment_not_available")); await loadAirwallex(env); window.Airwallex.redirectToCheckout({ env, mode: "payment", currency: result.currency, intent_id: expand.id, client_secret: expand.client_secret, methods: result.factoryChannelCode ? [result.factoryChannelCode] : undefined, recurringOptions: { card: { next_triggered_by: "customer", merchant_trigger_reason: "unscheduled", currency: result.currency } }, logoUrl: window.sysOriginLog || "", successUrl: paymentResultURL("0"), failUrl: paymentResultURL("1"), theme: { fonts: [ { src: "https://checkout.airwallex.com/fonts/CircularXXWeb/CircularXXWeb-Regular.woff2", family: "AxLLCircular", weight: 400 } ] } }); } async function handlePaymentResponse(result) { if (!result) throw new Error(message("payment_not_available")); if (result.factoryCode === "AIRWALLEX") { setPaymentStatus(message("redirecting_payment")); await redirectAirwallex(result); return; } if (result.requestUrl) { setPaymentStatus(message("redirecting_payment")); window.location.href = decodeURIComponent(result.requestUrl); return; } setPaymentStatus(message("order_created")); showToast(message("order_created")); } async function placeOrder(channel, goods) { if (state.ordering || state.loadingConfig) return; const country = selectedCountry(); const code = channelCode(channel); const userId = pickProfileId(state.payProfile || state.profile); if (!country?.id || !goods?.id || !userId || !code) { showToast(message("payment_not_available")); return; } state.ordering = true; state.activeOrderKey = packageKey(channel, goods); setPaymentStatus(message("creating_order")); renderAll(); try { const result = await requestJSON("/order/web/pay/recharge", { method: "POST", body: { applicationId: RECHARGE_APPLICATION_ID, payCountryId: String(country.id), goodsId: String(goods.id), userId: String(userId), channelCode: code } }); await handlePaymentResponse(result); } catch (error) { console.error("Recharge order failed:", error); const text = error.response?.errorMsg || error.message || message("payment_not_available"); setPaymentStatus(text, "error"); showToast(text); } finally { state.ordering = false; state.activeOrderKey = ""; renderAll(); } } function changeTarget() { state.profile = null; state.payProfile = null; state.autoVerifyOpen = false; state.autoVerifyForm = { orderNo: "", transferAmount: "", transferType: "BINANCE_PAY", billNo: "" }; state.autoVerifyStatusText = ""; state.autoVerifyStatusType = ""; state.rechargeAmount = ""; state.rechargeStatusText = ""; state.rechargeStatusType = ""; state.countryList = []; state.selectedCountryId = ""; state.regionId = ""; state.commodity = []; state.channels = []; state.paymentStatusText = ""; state.paymentStatusType = ""; renderAll(); window.setTimeout(() => document.querySelector("#accountInput")?.focus(), 60); } async function openHistoryModal() { state.historyOpen = true; renderHistoryModal(); if (!activeHistoryList().length && !state.historyLoading) { await fetchHistoryRecords(); } } function closeHistoryModal() { state.historyOpen = false; renderHistoryModal(); } async function switchHistoryTab(type) { const nextType = Number(type); if (state.historyTab === nextType || state.historyLoading) return; state.historyTab = nextType; state.historyStatusText = ""; state.historyStatusType = ""; renderHistoryModal(); if (!activeHistoryList().length) await fetchHistoryRecords(); } async function searchHistory(event) { if (event) event.preventDefault(); state.historySearch = String(document.querySelector("#historySearchInput")?.value || "").trim(); resetHistoryCache(); renderHistoryModal(); await fetchHistoryRecords(); } async function clearHistorySearch() { if (!state.historySearch && !document.querySelector("#historySearchInput")?.value) return; state.historySearch = ""; resetHistoryCache(); renderHistoryModal(); await fetchHistoryRecords(); } function openWhatsappModal() { state.whatsappOpen = true; state.whatsappDraft = state.whatsapp || ""; state.whatsappStatusText = ""; state.whatsappStatusType = ""; renderWhatsappModal(); window.setTimeout(() => document.querySelector("#whatsappInput")?.focus(), 60); } function closeWhatsappModal() { if (state.whatsappSaving) return; state.whatsappOpen = false; state.whatsappStatusText = ""; state.whatsappStatusType = ""; renderWhatsappModal(); } async function saveWhatsapp(event) { if (event) event.preventDefault(); if (state.whatsappSaving) return; const input = document.querySelector("#whatsappInput"); const whatsapp = String(input?.value || state.whatsappDraft || "").trim(); state.whatsappDraft = whatsapp; if (!whatsapp) { setWhatsappStatus(message("whatsapp_required"), "error"); renderWhatsappModal(); return; } if (whatsapp.length > 64) { setWhatsappStatus(message("whatsapp_too_long"), "error"); renderWhatsappModal(); return; } state.whatsappSaving = true; setWhatsappStatus(message("saving")); renderWhatsappModal(); try { const profile = await requestGoJSON(endpointSellerWhatsapp(), { method: "POST", body: { whatsapp } }) || {}; state.sellerProfile = profile; state.whatsapp = pickWhatsapp(profile) || whatsapp; state.whatsappOpen = false; state.whatsappStatusText = ""; state.whatsappStatusType = ""; showToast(message("whatsapp_saved")); } catch (error) { console.error("Save WhatsApp failed:", error); const text = error.response?.errorMsg || error.message || message("failed_to_load"); state.whatsappStatusText = text; state.whatsappStatusType = "error"; showToast(text); } finally { state.whatsappSaving = false; renderAll(); } } function closePage() { try { 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"); return; } if (window.history.length > 1) window.history.back(); } catch (error) { console.error("close recharge center page failed:", error); if (window.history.length > 1) window.history.back(); } } function setLanguageMenuOpen(open) { const button = document.querySelector(".language-button"); const menu = document.querySelector(".language-menu"); if (!button || !menu) return; menu.hidden = !open; button.setAttribute("aria-expanded", String(open)); } function renderLanguageControls() { const button = document.querySelector(".language-button"); if (button) { button.textContent = languageLabels[currentLang]; button.setAttribute("aria-label", message("language_button_aria", "Change language")); } document.querySelectorAll(".language-menu [data-lang]").forEach((node) => { const active = node.dataset.lang === currentLang; node.classList.toggle("is-active", active); node.setAttribute("aria-pressed", String(active)); }); } function applyLanguage(lang) { currentLang = normalizeLanguage(lang); currentMessages = messages[currentLang] || messages.en; document.documentElement.lang = currentLang; document.documentElement.dir = currentLang === "ar" ? "rtl" : "ltr"; document.title = message("document_title", "Recharge Center"); renderMessages(currentMessages); renderLanguageControls(); renderAll(); } function bindEvents() { document.querySelector(".back-button")?.addEventListener("click", closePage); document.querySelector(".language-button")?.addEventListener("click", (event) => { event.stopPropagation(); const menu = document.querySelector(".language-menu"); setLanguageMenuOpen(Boolean(menu?.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.querySelector("#accountInput")?.addEventListener("input", (event) => { state.query = String(event.target.value || ""); if (!state.query.trim()) setSearchStatus(""); renderSearch(); }); document.querySelector("#coinAmountInput")?.addEventListener("input", (event) => { const value = sanitizeCoinAmount(event.target.value); state.rechargeAmount = value; if (event.target.value !== value) event.target.value = value; setRechargeStatus(""); renderCoinRecharge(); }); document.querySelector("#autoVerifyToggle")?.addEventListener("click", () => { state.autoVerifyOpen = !state.autoVerifyOpen; renderAutoVerify(); if (state.autoVerifyOpen) window.setTimeout(() => document.querySelector("#autoOrderInput")?.focus(), 60); }); document.querySelector("#autoOrderInput")?.addEventListener("input", (event) => { state.autoVerifyForm.orderNo = String(event.target.value || ""); if (state.autoVerifyStatusText) setAutoVerifyStatus(""); renderAutoVerify(); }); document.querySelector("#autoAmountInput")?.addEventListener("input", (event) => { const value = sanitizeMoneyAmount(event.target.value); state.autoVerifyForm.transferAmount = value; if (event.target.value !== value) event.target.value = value; if (state.autoVerifyStatusText) setAutoVerifyStatus(""); renderAutoVerify(); }); document.querySelector("#autoTransferType")?.addEventListener("change", (event) => { state.autoVerifyForm.transferType = String(event.target.value || "BINANCE_PAY"); if (state.autoVerifyStatusText) setAutoVerifyStatus(""); renderAutoVerify(); }); document.querySelector("#autoBillInput")?.addEventListener("input", (event) => { state.autoVerifyForm.billNo = String(event.target.value || ""); if (state.autoVerifyStatusText) setAutoVerifyStatus(""); renderAutoVerify(); }); document.querySelector("#searchForm")?.addEventListener("submit", searchTarget); document.querySelector("#coinRechargeForm")?.addEventListener("submit", rechargeCoins); document.querySelector("#autoVerifyForm")?.addEventListener("submit", verifyAutoRecharge); document.querySelector("[data-change-target]")?.addEventListener("click", changeTarget); document.querySelector("[data-help-open]")?.addEventListener("click", () => setModalOpen("#helpModal", true)); document.querySelector("[data-history-open]")?.addEventListener("click", () => openHistoryModal()); document.querySelectorAll("[data-whatsapp-open]").forEach((node) => { node.addEventListener("click", openWhatsappModal); }); document.querySelector("#whatsappInput")?.addEventListener("input", (event) => { state.whatsappDraft = String(event.target.value || ""); if (state.whatsappStatusText) setWhatsappStatus(""); renderWhatsappModal(); }); document.querySelector("#whatsappForm")?.addEventListener("submit", saveWhatsapp); document.querySelector("#historySearchInput")?.addEventListener("input", (event) => { if (!String(event.target.value || "").trim()) state.historySearch = ""; }); document.querySelector("#historySearchForm")?.addEventListener("submit", searchHistory); document.querySelector("#historyClearButton")?.addEventListener("click", () => clearHistorySearch()); document.querySelector("#historyLoadMore")?.addEventListener("click", () => fetchHistoryRecords({ append: true })); document.querySelectorAll("[data-history-tab]").forEach((button) => { button.addEventListener("click", () => switchHistoryTab(button.dataset.historyTab)); }); document.querySelectorAll("[data-help-close]").forEach((node) => { node.addEventListener("click", () => setModalOpen("#helpModal", false)); }); document.querySelectorAll("[data-history-close]").forEach((node) => { node.addEventListener("click", closeHistoryModal); }); document.querySelectorAll("[data-whatsapp-close]").forEach((node) => { node.addEventListener("click", closeWhatsappModal); }); document.addEventListener("keydown", (event) => { if (event.key === "Escape") { setModalOpen("#helpModal", false); closeHistoryModal(); closeWhatsappModal(); } }); } async function initializePage() { setLoading(true); bindEvents(); applyLanguage(currentLang); renderAll(); if (!readRawParam("token")) { setSearchStatus(message("missing_token"), "error"); renderAll(); setLoading(false); return; } const initialResults = await Promise.allSettled([ fetchSelfProfile(), fetchCoinBalance(), fetchSellerProfile() ]); initialResults.forEach((result) => { if (result.status === "rejected") { console.warn("Recharge center initial request failed:", result.reason); } }); renderAll(); setLoading(false); if (state.query) { await searchTarget(); } } initializePage(); })();