2026-04-29 23:08:47 +08:00

1058 lines
35 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",
search_title: "Recharge user",
search_subtitle: "Search by user ID",
search_placeholder: "Enter user ID",
search: "Search",
searching: "Searching...",
target_user: "Target user",
uid_prefix: "UID:",
change: "Change",
select_country: "Select a country",
payment_methods: "Payment methods",
select_package: "Select a package",
loading_data: "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",
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, choose a package, then select a payment method.",
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: "Recharge Center",
page_label: "Recharge Center",
back: "Back",
title: "Recharge Center",
language_button_aria: "Change language",
help: "Help",
close: "Close",
search_title: "Recharge user",
search_subtitle: "Search by user ID",
search_placeholder: "Enter user ID",
search: "Search",
searching: "Searching...",
target_user: "Target user",
uid_prefix: "UID:",
change: "Change",
select_country: "Select a country",
payment_methods: "Payment methods",
select_package: "Select a package",
loading_data: "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",
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, choose a package, then select a payment method.",
faq_question_2: "How to check recharge result?",
faq_answer_2: "After payment, return to the app and check the user's wallet balance."
},
tr: {
document_title: "Recharge Center",
page_label: "Recharge Center",
back: "Geri",
title: "Recharge Center",
language_button_aria: "Dili degistir",
help: "Help",
close: "Kapat",
search_title: "Recharge user",
search_subtitle: "Kullanici ID ile ara",
search_placeholder: "Kullanici ID gir",
search: "Ara",
searching: "Araniyor...",
target_user: "Target user",
uid_prefix: "UID:",
change: "Degistir",
select_country: "Ulke sec",
payment_methods: "Payment methods",
select_package: "Paket sec",
loading_data: "Yukleniyor...",
no_country: "Desteklenen ulke yok",
no_packages: "Recharge package yok",
user_not_found: "Kullanici bulunamadi",
missing_token: "Token eksik",
failed_to_load: "Veri yuklenemedi",
payment_not_available: "Payment not available",
creating_order: "Order olusturuluyor...",
redirecting_payment: "Payment sayfasina yonlendiriliyor...",
order_created: "Order created",
faq_title: "FAQ",
faq_question_1: "How to recharge?",
faq_answer_1: "Enter the user's ID, choose a package, then select a payment method.",
faq_question_2: "How to check recharge result?",
faq_answer_2: "After payment, return to the app and check the user's wallet balance."
},
id: {
document_title: "Recharge Center",
page_label: "Recharge Center",
back: "Kembali",
title: "Recharge Center",
language_button_aria: "Ubah bahasa",
help: "Help",
close: "Tutup",
search_title: "Recharge user",
search_subtitle: "Cari dengan user ID",
search_placeholder: "Masukkan user ID",
search: "Cari",
searching: "Mencari...",
target_user: "Target user",
uid_prefix: "UID:",
change: "Ganti",
select_country: "Pilih negara",
payment_methods: "Payment methods",
select_package: "Pilih paket",
loading_data: "Memuat...",
no_country: "Negara tidak tersedia",
no_packages: "Paket recharge tidak tersedia",
user_not_found: "User tidak ditemukan",
missing_token: "Token tidak ditemukan",
failed_to_load: "Gagal memuat data",
payment_not_available: "Payment not available",
creating_order: "Membuat order...",
redirecting_payment: "Membuka payment...",
order_created: "Order created",
faq_title: "FAQ",
faq_question_1: "How to recharge?",
faq_answer_1: "Enter the user's ID, choose a package, then select a payment method.",
faq_question_2: "How to check recharge result?",
faq_answer_2: "After payment, return to the app and check the user's wallet balance."
}
};
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: "",
searchStatusText: "",
searchStatusType: "",
paymentStatusText: "",
paymentStatusType: "",
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 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 requestJSON(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, `${apiBaseURL()}/`).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;
}
function endpointOpenSearch(account) {
return `/user/user-profile/open-search?account=${encodeURIComponent(account)}`;
}
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")).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 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 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 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 renderAvatar(profile) {
const image = document.querySelector("#targetAvatar");
const fallback = document.querySelector("#targetAvatarFallback");
if (!image || !fallback) return;
const name = pickProfileName(profile);
const avatar = profile?.userAvatar || profile?.avatar || "";
fallback.textContent = String(name || "U").slice(0, 1).toUpperCase() || "U";
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 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();
renderAvatar(state.profile);
}
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";
coin.textContent = "$";
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() {
renderSearch();
renderTarget();
renderCountries();
renderPayment();
}
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.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;
setSearchStatus("");
renderAll();
await loadRechargeOptions();
} 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();
}
}
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.countryList = [];
state.selectedCountryId = "";
state.regionId = "";
state.commodity = [];
state.channels = [];
state.paymentStatusText = "";
state.paymentStatusType = "";
renderAll();
window.setTimeout(() => document.querySelector("#accountInput")?.focus(), 60);
}
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("#searchForm")?.addEventListener("submit", searchTarget);
document.querySelector("[data-change-target]")?.addEventListener("click", changeTarget);
document.querySelector("[data-help-open]")?.addEventListener("click", () => setModalOpen("#helpModal", true));
document.querySelectorAll("[data-help-close]").forEach((node) => {
node.addEventListener("click", () => setModalOpen("#helpModal", false));
});
document.addEventListener("keydown", (event) => {
if (event.key === "Escape") setModalOpen("#helpModal", false);
});
}
async function initializePage() {
setLoading(true);
bindEvents();
applyLanguage(currentLang);
renderAll();
setLoading(false);
if (!readRawParam("token")) {
setSearchStatus(message("missing_token"), "error");
renderAll();
return;
}
if (state.query) {
await searchTarget();
}
}
initializePage();
})();