2026-04-30 13:29:00 +08:00

1773 lines
60 KiB
JavaScript

(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",
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",
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: "إضافة واتساب",
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: "تأكيد الشحن",
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",
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",
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",
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",
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: "",
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"
};
}
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(new URL(path, `${baseURL}/`).toString(), 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 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 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 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;
}
if (button) {
button.hidden = Boolean(whatsapp);
button.textContent = 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 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 input = document.querySelector("#whatsappInput");
const button = document.querySelector("#saveWhatsappButton");
if (modal) modal.hidden = !state.whatsappOpen;
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();
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.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.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 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.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("#searchForm")?.addEventListener("submit", searchTarget);
document.querySelector("#coinRechargeForm")?.addEventListener("submit", rechargeCoins);
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.querySelector("[data-whatsapp-open]")?.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();
})();