const query = readURLParams(); const storedAppId = window.localStorage.getItem("mifa-pay-application-id") || ""; const API_BASE_URL = "https://jvapi.haiyihy.com"; const LOCAL_API_BASE_URL = "http://127.0.0.1:1100"; const CHANNEL_LOGOS = [ ["qris", "./logos/qris.png"], ["dana", "./logos/dana.png"], ["linkaja", "./logos/linkaja.png"], ["ovo", "./logos/ovo.png"], ["shopeepay", "./logos/shopeepay.png"], ["bca", "./logos/bca.png"], ["bni", "./logos/bni.png"], ["permata", "./logos/permata.png"], ["mandiri", "./logos/mandiri.png"], ["bri", "./logos/bri.png"], ["upi", "./logos/upi.png"], ["paytm", "./logos/paytm.png"], ["phonepe", "./logos/phonepe.png"], ["googlepay", "./logos/googlepay.png"], ["bhim", "./logos/bhim.png"], ["mobikwik", "./logos/mobikwik.png"], ["jazzcash", "./logos/jazzcash.png"], ["easypaisa", "./logos/easypaisa.png"], ["vietqr", "./logos/vietqr.png"], ["vnbanktransfer", "./logos/vn-banktransfer.png"], ]; 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 getPathApplicationId() { const segments = window.location.pathname.split("/").filter(Boolean); const pageIndex = segments.lastIndexOf("mifa-pay"); if (pageIndex === -1) { return ""; } const candidate = segments[pageIndex + 1] || ""; return /^\d+$/.test(candidate) ? candidate : ""; } const defaults = { applicationId: getPathApplicationId() || query.get("applicationId") || query.get("appId") || storedAppId || "2048200000000000701", productType: query.get("type") || "GOLD", }; const state = { applicationId: defaults.applicationId, productType: defaults.productType, application: null, userProfile: null, countries: [], regionId: "", selectedCountryId: "", commodityCard: null, loading: true, unsupportedRegion: false, selectedGoodsId: "", selectedGoods: null, selectedChannelCode: "", pendingChannelCode: "", }; const els = { applicationId: document.getElementById("applicationId"), productList: document.getElementById("productList"), channelList: document.getElementById("channelList"), unsupportedMessage: document.getElementById("unsupportedMessage"), goRecharge: document.getElementById("goRecharge"), pageStatus: document.getElementById("pageStatus"), }; function setStatus(text = "") { if (!text) { els.pageStatus.textContent = ""; els.pageStatus.className = "status"; return; } els.pageStatus.textContent = text; els.pageStatus.className = "status show"; } function persistSettings() { window.localStorage.setItem("mifa-pay-application-id", state.applicationId); } function readRawParam(name) { const queries = [window.location.search.replace(/^\?/, "")]; if (window.location.hash.includes("?")) { queries.push(window.location.hash.split("?")[1]); } for (const item of queries) { for (const part of item.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 apiBaseURL() { const useProdApi = query.get("prod") === "true"; const isLocalPage = window.location.protocol === "http:" && ["localhost", "127.0.0.1", "0.0.0.0", "::1", "[::1]"].includes( window.location.hostname ); if (isLocalPage && !useProdApi) { return LOCAL_API_BASE_URL; } if (useProdApi) { return API_BASE_URL; } const override = window.MIFAPAY_API_BASE_URL || query.get("apiBase"); return String(override || API_BASE_URL).replace(/\/+$/, ""); } function normalizeAuthorization(value) { const token = String(value || "").trim(); if (!token) { return ""; } return /^bearer\s+/i.test(token) ? token : `Bearer ${token}`; } function currentToken() { return ( readRawParam("token") || readRawParam("Authorization") || readRawParam("authorization") || readRawParam("accessToken") ); } function currentLanguage() { return query.get("lang") || document.documentElement.lang || navigator.language || "en"; } function currentSysOrigin() { return String(state.application?.appCode || query.get("sysOrigin") || query.get("origin") || "LIKEI") .trim() .toUpperCase(); } function authHeadersFromToken(token) { const sysOrigin = currentSysOrigin(); return { Accept: "application/json, text/plain, */*", Authorization: normalizeAuthorization(token), "req-lang": currentLanguage(), "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=${sysOrigin};originChild=${sysOrigin}`, }; } function requestHeaders(hasBody = false) { const token = currentToken(); if (!token) { throw new Error("Missing token"); } const headers = authHeadersFromToken(token); if (hasBody) { headers["Content-Type"] = "application/json"; } return headers; } function buildRequestURL(path) { const base = `${apiBaseURL()}/`; const target = String(path || "").replace(/^\/+/, ""); return new URL(target, base).toString(); } async function requestJSON(path, options = {}) { const hasBody = options.body !== undefined; const request = { method: options.method || "GET", cache: "no-store", headers: requestHeaders(hasBody), }; if (hasBody) { request.body = JSON.stringify(options.body); } const response = await fetch(buildRequestURL(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; } function apiGet(path) { return requestJSON(path); } function apiPost(path, body) { return requestJSON(path, { method: "POST", body, }); } function extractErrorMessage(error) { return ( error?.response?.errorMsg || error?.response?.body?.errorMsg || error?.errorMsg || error?.message || "Unknown error" ); } function escapeHTML(value) { return String(value ?? "").replace(/[&<>"']/g, (char) => { const entities = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'", }; return entities[char]; }); } function escapeAttribute(value) { return escapeHTML(value).replace(/`/g, "`"); } function readStoredUserProfile() { const raw = window.localStorage.getItem("userProfile"); if (!raw) { return null; } try { const parsed = JSON.parse(raw); return parsed && typeof parsed === "object" ? parsed : null; } catch (error) { return null; } } function resolveUserProfile(response = null) { if (response?.body && typeof response.body === "object") { return response.body; } return readStoredUserProfile(); } function currentUserId() { return String(state.userProfile?.id || "").trim(); } function currentCountry() { return ( state.countries.find((item) => String(item.id) === String(state.selectedCountryId)) || null ); } function currentPayCountryId() { const selected = currentCountry(); return String(selected?.payCountryId || selected?.id || state.selectedCountryId || ""); } function currentRegionId() { return state.regionId; } function currentUserAccount() { return String( state.userProfile?.account || state.userProfile?.actualAccount || state.userProfile?.ownSpecialId?.account || state.userProfile?.specialId?.account || "" ).trim(); } function getCountryLabel(item) { return ( item?.label || item?.countryName || item?.country?.aliasName || item?.country?.countryName || String(item?.id || "") ); } function getGoodsList() { return state.commodityCard?.commodity || []; } function getAllChannelList() { return state.commodityCard?.channels || []; } function toNumber(value, fallback = 0) { const number = Number(value); return Number.isFinite(number) ? number : fallback; } function channelSupportsGoods(channel, goods) { if (!goods) { return true; } const details = channel?.details || {}; const amountUsd = toNumber(goods?.amountUsd || goods?.amount); const min = toNumber(details.factoryMinLimit, 0); const max = toNumber(details.factoryMaxLimit, Number.MAX_SAFE_INTEGER); const daily = toNumber(details.factoryDailyLimit, Number.MAX_SAFE_INTEGER); return amountUsd >= min && amountUsd <= max && amountUsd <= daily; } function getChannelList(goods = state.selectedGoods) { return getAllChannelList().filter((item) => channelSupportsGoods(item, goods)); } function goodsHasChannel(goods) { return getChannelList(goods).length > 0; } function hasAvailablePayment() { return getGoodsList().some((goods) => goodsHasChannel(goods)); } function setUnsupportedRegion(value) { state.unsupportedRegion = Boolean(value); renderUnsupportedState(); } function renderUnsupportedState() { const show = !state.loading && state.unsupportedRegion; els.unsupportedMessage.hidden = !show; els.unsupportedMessage.className = show ? "unsupported-state show" : "unsupported-state"; els.productList.hidden = show; els.channelList.hidden = show; if (show) { els.goRecharge.hidden = true; els.productList.innerHTML = ""; els.channelList.innerHTML = ""; } } function getChannelCode(item) { return item?.details?.channelCode || item?.channel?.channelCode || ""; } function getChannelName(item) { return String( item?.channel?.channelName || item?.details?.factoryChannel || item?.details?.channelCode || item?.channel?.channelCode || "Pay" ).trim(); } function normalizeLogoKey(value) { return String(value || "") .trim() .toLowerCase() .replace(/[^a-z0-9]/g, ""); } function getChannelLogoCandidates(item) { const values = [ item?.channel?.channelCode, item?.details?.channelCode, item?.details?.factoryChannel, item?.channel?.channelName, getChannelName(item), ]; const keys = new Set(); values.filter(Boolean).forEach((value) => { const text = String(value); const normalized = normalizeLogoKey(text); if (normalized) { keys.add(normalized); } text .split(/[_\s/-]+/) .map(normalizeLogoKey) .filter(Boolean) .forEach((key) => keys.add(key)); }); return Array.from(keys); } function getLocalChannelIconURL(item) { const candidates = getChannelLogoCandidates(item); const exact = CHANNEL_LOGOS.find(([key]) => candidates.includes(key)); if (exact) { return exact[1]; } const combined = candidates.join(" "); const partial = CHANNEL_LOGOS.find(([key]) => combined.includes(key)); return partial?.[1] || ""; } function getChannelIconURL(item) { return String( item?.channel?.channelIcon || item?.details?.channelIcon || getLocalChannelIconURL(item) ).trim(); } function getChannelAbbr(item) { const candidates = [getChannelCode(item), getChannelName(item)]; for (const value of candidates) { const segments = String(value || "") .split(/[_\s/-]+/) .map((segment) => segment.replace(/[^A-Za-z0-9]/g, "")) .filter(Boolean); if (segments.length) { return segments[segments.length - 1].slice(0, 5).toUpperCase(); } } return "PAY"; } function channelIconHTML(item) { const name = getChannelName(item); const icon = getChannelIconURL(item); if (icon) { return ``; } return ``; } function getAmountText(item) { const content = item?.content || item?.obtainGoldsQuantity || "-"; return `${content} ${item?.type || ""}`.trim(); } function currentCurrency(item = null) { return String( item?.currency || state.commodityCard?.payCountry?.currency || currentCountry()?.currency || "USD" ) .trim() .toUpperCase(); } function formatMoney(value, currency = "") { if (value === null || value === undefined || value === "") { return "-"; } const number = Number(value); if (!Number.isFinite(number)) { return String(value); } const zeroDecimalCurrencies = new Set(["IDR", "JPY", "KRW", "VND"]); const maximumFractionDigits = zeroDecimalCurrencies.has(currency) ? 0 : 2; return number.toLocaleString("en-US", { minimumFractionDigits: 0, maximumFractionDigits, }); } function getPriceText(item) { const currency = currentCurrency(item); return `${currency} ${formatMoney(item?.amount ?? item?.amountUsd, currency)}`; } function getUsdPriceText(item) { return `USD ${formatMoney(item?.amountUsd ?? item?.amount ?? "-", "USD")}`; } function getCountryCodes(item) { const country = item?.country || {}; return [ item?.countryCode, country?.alphaTwo, country?.alphaThree, ...(Array.isArray(country?.countryCodeAliases) ? country.countryCodeAliases : []), ] .filter(Boolean) .map((value) => String(value).trim().toUpperCase()) .filter(Boolean); } function getCountryId(item) { return String(item?.countryId || item?.country?.id || "").trim(); } function mergeCompatibleCountries(regionCountries, openCountries) { const compatibleCountryMap = new Map(); openCountries.forEach((item) => { const countryId = getCountryId(item); if (countryId && !compatibleCountryMap.has(countryId)) { compatibleCountryMap.set(countryId, item); } }); return regionCountries.map((item) => { if (item?.payCountryId) { return item; } const compatibleCountry = compatibleCountryMap.get(getCountryId(item)); if (!compatibleCountry) { return item; } return { ...item, payCountryId: compatibleCountry.payCountryId || compatibleCountry.id, currency: item.currency || compatibleCountry.currency, }; }); } function ensureSelectedGoods() { const goods = getGoodsList(); if (!goods.length) { state.selectedGoodsId = ""; state.selectedGoods = null; return; } const matched = goods.find((item) => String(item.id) === state.selectedGoodsId) || goods.find((item) => item.type === state.productType) || goods[0]; const payableGoods = goodsHasChannel(matched) ? matched : goods.find((item) => goodsHasChannel(item)) || matched; state.selectedGoods = payableGoods; state.selectedGoodsId = String(payableGoods.id); } function renderProductLoading() { els.productList.innerHTML = `
`; } function renderChannelLoading() { els.goRecharge.hidden = true; els.channelList.innerHTML = Array.from({ length: 8 }) .map( () => ` ` ) .join(""); } function renderProducts() { if (state.loading) { renderProductLoading(); return; } if (state.unsupportedRegion) { els.productList.innerHTML = ""; return; } const goods = getGoodsList(); ensureSelectedGoods(); if (!goods.length) { els.productList.innerHTML = '