diff --git a/h5/mifa-pay/app.js b/h5/mifa-pay/app.js index deb1395..8b4f6d6 100644 --- a/h5/mifa-pay/app.js +++ b/h5/mifa-pay/app.js @@ -2,7 +2,29 @@ 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 LOGO_SRC = "./mifapay-logo.png"; + +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); @@ -46,8 +68,11 @@ const state = { regionId: "", selectedCountryId: "", commodityCard: null, + loading: true, + unsupportedRegion: false, selectedGoodsId: "", selectedGoods: null, + selectedChannelCode: "", pendingChannelCode: "", }; @@ -55,6 +80,8 @@ 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"), }; @@ -146,7 +173,14 @@ 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), @@ -155,7 +189,7 @@ function authHeadersFromToken(token) { "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", + "req-sys-origin": `origin=${sysOrigin};originChild=${sysOrigin}`, }; } @@ -224,6 +258,23 @@ function extractErrorMessage(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) { @@ -250,8 +301,16 @@ function currentUserId() { return String(state.userProfile?.id || "").trim(); } -function currentCountryId() { - return state.selectedCountryId; +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() { @@ -282,22 +341,230 @@ function getGoodsList() { return state.commodityCard?.commodity || []; } -function getChannelList() { +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 `${escapeAttribute(
+      name
+    )}`; + } + return `${escapeHTML(getChannelAbbr(item))}`; +} + 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 = item?.currency || state.commodityCard?.payCountry?.currency || "USD"; - return `${item?.amount || item?.amountUsd || "-"} ${currency}`; + 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() { @@ -312,11 +579,53 @@ function ensureSelectedGoods() { goods.find((item) => String(item.id) === state.selectedGoodsId) || goods.find((item) => item.type === state.productType) || goods[0]; - state.selectedGoods = matched; - state.selectedGoodsId = String(matched.id); + 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(); @@ -327,21 +636,28 @@ function renderProducts() { els.productList.innerHTML = goods .map((item) => { - const selected = String(item.id) === state.selectedGoodsId; + const payable = goodsHasChannel(item); + const selected = payable && String(item.id) === state.selectedGoodsId; + const amount = item?.content || item?.obtainGoldsQuantity || "-"; + const type = item?.type || "Recharge"; return ` `; @@ -355,12 +671,50 @@ function renderProducts() { state.selectedGoods = getGoodsList().find((item) => String(item.id) === goodsId) || null; renderProducts(); + renderChannels(); }); }); } +function ensureSelectedChannel(channels) { + if (!channels.length) { + state.selectedChannelCode = ""; + return; + } + + const selectedExists = channels.some( + (item) => String(getChannelCode(item)) === String(state.selectedChannelCode) + ); + if (!selectedExists) { + state.selectedChannelCode = getChannelCode(channels[0]); + } +} + +function renderGoRecharge(channels = getChannelList()) { + const hasSelection = channels.some( + (item) => String(getChannelCode(item)) === String(state.selectedChannelCode) + ); + els.goRecharge.hidden = !channels.length; + els.goRecharge.disabled = !hasSelection || Boolean(state.pendingChannelCode); + els.goRecharge.textContent = state.pendingChannelCode ? "Processing..." : "Go Recharge"; +} + function renderChannels() { + if (state.loading) { + renderChannelLoading(); + return; + } + + if (state.unsupportedRegion) { + els.channelList.innerHTML = ""; + els.goRecharge.hidden = true; + return; + } + const channels = getChannelList(); + ensureSelectedChannel(channels); + renderGoRecharge(channels); + if (!channels.length) { els.channelList.innerHTML = '
No payment method available.
'; return; @@ -369,46 +723,47 @@ function renderChannels() { els.channelList.innerHTML = channels .map((item) => { const code = getChannelCode(item); - const pending = state.pendingChannelCode === code; + const selected = String(code) === String(state.selectedChannelCode); + const name = getChannelName(item); return ` `; }) .join(""); els.channelList.querySelectorAll("[data-channel-code]").forEach((button) => { - button.addEventListener("click", async () => { + button.addEventListener("click", () => { const channelCode = button.getAttribute("data-channel-code"); - try { - await createOrder(channelCode); - } catch (error) { - state.pendingChannelCode = ""; - renderChannels(); - setStatus(extractErrorMessage(error)); - } + state.selectedChannelCode = channelCode; + setStatus(""); + renderChannels(); }); }); } function selectCountryByProfile() { - const userCountryCode = state.userProfile?.countryCode; - if (!userCountryCode) { + const userCountryCode = String(state.userProfile?.countryCode || "").trim().toUpperCase(); + const userCountryId = String(state.userProfile?.countryId || "").trim(); + if (!userCountryCode && !userCountryId) { return false; } - const matched = state.countries.find((item) => - getCountryLabel(item).toUpperCase().includes(userCountryCode.toUpperCase()) - ); + const matched = state.countries.find((item) => { + const itemCountryId = getCountryId(item); + if (userCountryId && itemCountryId === userCountryId) { + return true; + } + return userCountryCode && getCountryCodes(item).includes(userCountryCode); + }); if (!matched) { return false; @@ -469,9 +824,18 @@ async function refreshMeta() { if (!state.countries.length) { state.selectedCountryId = ""; state.commodityCard = null; + state.loading = false; + setUnsupportedRegion(true); renderProducts(); renderChannels(); - throw new Error("No supported country"); + setStatus(""); + return; + } + + if (state.countries.some((item) => !item?.payCountryId)) { + const countryResp = await apiGet("/order/web/pay/country"); + const openCountries = Array.isArray(countryResp?.body) ? countryResp.body : []; + state.countries = mergeCompatibleCountries(state.countries, openCountries); } if ( @@ -486,12 +850,14 @@ async function refreshMeta() { const commodityResp = await apiGet( `/order/web/pay/commodity?applicationId=${encodeURIComponent( state.applicationId - )}&payCountryId=${encodeURIComponent(currentCountryId())}&type=${encodeURIComponent( + )}&payCountryId=${encodeURIComponent(currentPayCountryId())}&type=${encodeURIComponent( state.productType )}®ionId=${encodeURIComponent(currentRegionId())}` ); state.commodityCard = commodityResp?.body || {}; + state.loading = false; + setUnsupportedRegion(!hasAvailablePayment()); renderProducts(); renderChannels(); setStatus(""); @@ -514,10 +880,15 @@ async function createOrder(channelCode) { throw new Error("Select an amount first"); } + if (!channelCode) { + throw new Error("Select a payment method first"); + } + if (!currentUserId()) { throw new Error("Account not available"); } + state.selectedChannelCode = channelCode; state.pendingChannelCode = channelCode; setStatus(""); renderChannels(); @@ -525,7 +896,7 @@ async function createOrder(channelCode) { const payload = { applicationId: state.applicationId, goodsId: state.selectedGoodsId, - payCountryId: currentCountryId(), + payCountryId: currentPayCountryId(), userId: currentUserId(), channelCode, newVersion: true, @@ -546,12 +917,27 @@ async function reloadPageData() { } async function init() { + state.loading = true; + setUnsupportedRegion(false); renderProducts(); renderChannels(); + els.goRecharge.addEventListener("click", async () => { + try { + await createOrder(state.selectedChannelCode); + } catch (error) { + state.pendingChannelCode = ""; + renderChannels(); + setStatus(extractErrorMessage(error)); + } + }); try { await reloadPageData(); } catch (error) { + state.loading = false; + setUnsupportedRegion(false); + renderProducts(); + renderChannels(); setStatus(extractErrorMessage(error)); } } diff --git a/h5/mifa-pay/bg.png b/h5/mifa-pay/bg.png new file mode 100644 index 0000000..7513f52 Binary files /dev/null and b/h5/mifa-pay/bg.png differ diff --git a/h5/mifa-pay/index.html b/h5/mifa-pay/index.html index 86c94ae..d95b00d 100644 --- a/h5/mifa-pay/index.html +++ b/h5/mifa-pay/index.html @@ -12,10 +12,13 @@ --bg: #08131f; --surface: rgba(15, 28, 42, 0.92); --surface-strong: #102030; - --line: rgba(214, 170, 82, 0.18); - --line-strong: rgba(214, 170, 82, 0.42); + --line: rgba(72, 224, 208, 0.2); + --line-strong: rgba(72, 224, 208, 0.78); --text: #f6f1e6; - --muted: #bcae8d; + --muted: #bed4d1; + --accent: #48e0d0; + --accent-strong: #1baaa3; + --accent-soft: rgba(72, 224, 208, 0.16); --gold: #d6aa52; --gold-strong: #c49335; --danger: #ff9f9f; @@ -38,8 +41,9 @@ sans-serif; color: var(--text); background: - radial-gradient(circle at top, rgba(214, 170, 82, 0.1), transparent 24%), - linear-gradient(180deg, #091321 0%, var(--bg) 100%); + linear-gradient(180deg, rgba(8, 19, 31, 0.44), rgba(8, 19, 31, 0.72)), + url("./bg.png") center top / cover no-repeat, + var(--bg); } button { @@ -59,9 +63,12 @@ font-size: 30px; line-height: 1; font-weight: 800; - color: var(--gold); + color: #f8fbff; text-align: center; letter-spacing: 0.03em; + text-shadow: + 0 2px 10px rgba(8, 19, 31, 0.88), + 0 0 18px rgba(72, 224, 208, 0.3); } .list { @@ -69,39 +76,48 @@ gap: 12px; } - .amount-item, - .channel-button { + .amount-item { position: relative; width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 12px; - padding: 16px 18px; + padding: 16px 54px 16px 18px; border-radius: 22px; border: 1px solid var(--line); background: - linear-gradient(180deg, rgba(214, 170, 82, 0.04), transparent 60%), + linear-gradient(180deg, rgba(72, 224, 208, 0.07), transparent 62%), var(--surface); box-shadow: var(--shadow); color: var(--text); text-align: left; transition: transform 0.18s ease, + background 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease; } - .amount-item:active, - .channel-button:active { + .amount-item:active { transform: scale(0.988); } .amount-item.selected { border-color: var(--line-strong); + background: + linear-gradient(135deg, rgba(72, 224, 208, 0.22), rgba(13, 30, 44, 0.96)), + var(--surface-strong); + transform: scale(1.012); box-shadow: - 0 0 0 1px rgba(214, 170, 82, 0.12), - 0 16px 36px rgba(0, 0, 0, 0.24); + 0 0 0 1px rgba(72, 224, 208, 0.22), + 0 0 22px rgba(72, 224, 208, 0.28), + 0 16px 36px rgba(0, 0, 0, 0.28); + } + + .amount-item:disabled { + cursor: not-allowed; + opacity: 0.55; } .amount-copy { @@ -111,12 +127,37 @@ } .amount-value { + display: inline-flex; + align-items: center; + gap: 8px; font-size: 24px; line-height: 1; font-weight: 800; color: var(--text); } + .coin-icon { + position: relative; + width: 24px; + height: 24px; + flex: 0 0 auto; + border-radius: 50%; + background: + radial-gradient(circle at 35% 28%, #fff5b8 0 18%, transparent 19%), + linear-gradient(135deg, #ffd96a, #d79a26); + box-shadow: + inset 0 0 0 2px rgba(142, 91, 13, 0.46), + 0 0 12px rgba(255, 217, 106, 0.36); + } + + .coin-icon::after { + content: ""; + position: absolute; + inset: 6px; + border-radius: 50%; + border: 2px solid rgba(122, 78, 10, 0.48); + } + .amount-meta { font-size: 11px; line-height: 1; @@ -130,12 +171,14 @@ display: grid; gap: 6px; flex-shrink: 0; + max-width: 46%; } .price-copy strong { - font-size: 22px; + font-size: 20px; line-height: 1; - color: var(--gold); + color: var(--accent); + white-space: nowrap; } .price-copy span { @@ -145,17 +188,20 @@ .check { position: absolute; - top: 14px; - right: 14px; + top: 50%; + right: 18px; width: 20px; height: 20px; border-radius: 50%; - border: 1px solid rgba(214, 170, 82, 0.3); + border: 1px solid rgba(72, 224, 208, 0.36); + transform: translateY(-50%); + pointer-events: none; } .amount-item.selected .check { border-color: transparent; - background: linear-gradient(135deg, #efcf82, var(--gold-strong)); + background: linear-gradient(135deg, #8cf5ec, var(--accent-strong)); + box-shadow: 0 0 14px rgba(72, 224, 208, 0.45); } .amount-item.selected .check::after { @@ -174,32 +220,107 @@ margin-top: 18px; } - .channel-button { - justify-content: flex-start; - padding-right: 18px; + .channel-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 10px; } - .channel-logo { - width: 78px; - height: 44px; + .channel-button { + position: relative; + width: 100%; + min-height: 84px; + display: grid; + align-content: center; + justify-items: center; + gap: 8px; + padding: 10px 6px; + border-radius: 16px; + border: 1px solid var(--line); + background: + linear-gradient(180deg, rgba(72, 224, 208, 0.06), transparent 62%), + var(--surface); + box-shadow: var(--shadow); + color: var(--text); + text-align: center; + transition: + transform 0.18s ease, + background 0.18s ease, + border-color 0.18s ease, + box-shadow 0.18s ease; + } + + .channel-button:active { + transform: scale(0.98); + } + + .channel-button.selected { + border-color: var(--line-strong); + background: + linear-gradient(135deg, rgba(72, 224, 208, 0.28), rgba(13, 30, 44, 0.97)), + var(--surface-strong); + transform: translateY(-2px) scale(1.045); + box-shadow: + 0 0 0 1px rgba(72, 224, 208, 0.26), + 0 0 22px rgba(72, 224, 208, 0.34), + 0 18px 40px rgba(0, 0, 0, 0.32); + } + + .channel-button.selected::after { + content: ""; + position: absolute; + top: 8px; + right: 8px; + width: 9px; + height: 9px; + border-radius: 50%; + background: var(--accent); + box-shadow: 0 0 12px rgba(72, 224, 208, 0.68); + } + + .channel-icon { + width: 54px; + height: 38px; display: grid; place-items: center; flex-shrink: 0; - border-radius: 14px; + padding: 4px; + border-radius: 10px; background: #ffffff; overflow: hidden; } - .channel-logo img { + .channel-icon img { display: block; - width: 68px; - height: auto; + max-width: 48px; + max-height: 30px; + object-fit: contain; + } + + .channel-fallback { + width: 42px; + height: 42px; + padding: 0; + border-radius: 12px; + background: linear-gradient(135deg, #8cf5ec, var(--accent-strong)); + color: #0c1520; + font-size: 12px; + line-height: 1; + font-weight: 800; } .channel-name { - font-size: 16px; + width: 100%; + min-height: 24px; + display: -webkit-box; + overflow: hidden; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + font-size: 10px; + line-height: 1.2; font-weight: 700; color: var(--text); + overflow-wrap: anywhere; } .channel-button:disabled { @@ -207,6 +328,32 @@ opacity: 0.55; } + .recharge-button { + width: 100%; + margin-top: 14px; + padding: 14px 18px; + border-radius: 18px; + background: linear-gradient(135deg, #8cf5ec, var(--accent-strong)); + color: #0c1520; + font-size: 16px; + font-weight: 800; + box-shadow: + 0 0 18px rgba(72, 224, 208, 0.28), + 0 16px 32px rgba(0, 0, 0, 0.28); + transition: + transform 0.18s ease, + box-shadow 0.18s ease; + } + + .recharge-button:active { + transform: scale(0.985); + } + + .recharge-button:disabled { + cursor: not-allowed; + opacity: 0.55; + } + .status { display: none; margin-top: 14px; @@ -225,6 +372,7 @@ } .empty { + grid-column: 1 / -1; padding: 16px; border-radius: 20px; border: 1px dashed var(--line); @@ -232,18 +380,136 @@ text-align: center; font-size: 13px; } + + .unsupported-state { + display: none; + min-height: calc(100vh - 130px); + place-items: center; + padding: 36px 8px; + } + + .unsupported-state.show { + display: grid; + } + + .unsupported-panel { + width: 100%; + max-width: 360px; + display: grid; + justify-items: center; + gap: 14px; + padding: 28px 22px; + border-radius: 24px; + border: 1px solid rgba(72, 224, 208, 0.46); + background: + radial-gradient(circle at top, rgba(72, 224, 208, 0.18), transparent 58%), + rgba(8, 19, 31, 0.82); + box-shadow: + 0 0 0 1px rgba(248, 251, 255, 0.06), + 0 0 28px rgba(72, 224, 208, 0.24), + 0 18px 44px rgba(0, 0, 0, 0.34); + text-align: center; + } + + .unsupported-icon { + width: 44px; + height: 44px; + display: grid; + place-items: center; + border-radius: 50%; + background: linear-gradient(135deg, #8cf5ec, var(--accent-strong)); + color: #0c1520; + font-size: 24px; + font-weight: 900; + box-shadow: 0 0 18px rgba(72, 224, 208, 0.42); + } + + .unsupported-title { + margin: 0; + max-width: 290px; + color: #f8fbff; + font-size: 20px; + line-height: 1.35; + font-weight: 800; + text-shadow: 0 2px 10px rgba(8, 19, 31, 0.72); + } + + .skeleton { + position: relative; + overflow: hidden; + } + + .skeleton::after { + content: ""; + position: absolute; + inset: 0; + transform: translateX(-100%); + background: linear-gradient( + 90deg, + transparent, + rgba(248, 251, 255, 0.12), + transparent + ); + animation: shimmer 1.25s infinite; + } + + .skeleton-line, + .skeleton-icon { + display: block; + border-radius: 999px; + background: rgba(248, 251, 255, 0.12); + } + + .skeleton-line.strong { + width: 92px; + height: 22px; + } + + .skeleton-line.short { + width: 64px; + height: 10px; + } + + .skeleton-line.price { + width: 104px; + height: 18px; + margin-left: auto; + } + + .skeleton-icon { + width: 54px; + height: 38px; + border-radius: 10px; + } + + @keyframes shimmer { + 100% { + transform: translateX(100%); + } + }

Recharge

-
+
+ +
- + diff --git a/h5/mifa-pay/logos/bca.png b/h5/mifa-pay/logos/bca.png new file mode 100644 index 0000000..7ded3db Binary files /dev/null and b/h5/mifa-pay/logos/bca.png differ diff --git a/h5/mifa-pay/logos/bhim.png b/h5/mifa-pay/logos/bhim.png new file mode 100644 index 0000000..858d64d Binary files /dev/null and b/h5/mifa-pay/logos/bhim.png differ diff --git a/h5/mifa-pay/logos/bni.png b/h5/mifa-pay/logos/bni.png new file mode 100644 index 0000000..8892628 Binary files /dev/null and b/h5/mifa-pay/logos/bni.png differ diff --git a/h5/mifa-pay/logos/bri.png b/h5/mifa-pay/logos/bri.png new file mode 100644 index 0000000..b04d296 Binary files /dev/null and b/h5/mifa-pay/logos/bri.png differ diff --git a/h5/mifa-pay/logos/dana.png b/h5/mifa-pay/logos/dana.png new file mode 100644 index 0000000..4044353 Binary files /dev/null and b/h5/mifa-pay/logos/dana.png differ diff --git a/h5/mifa-pay/logos/easypaisa.png b/h5/mifa-pay/logos/easypaisa.png new file mode 100644 index 0000000..e60e897 Binary files /dev/null and b/h5/mifa-pay/logos/easypaisa.png differ diff --git a/h5/mifa-pay/logos/googlepay.png b/h5/mifa-pay/logos/googlepay.png new file mode 100644 index 0000000..f2e8af4 Binary files /dev/null and b/h5/mifa-pay/logos/googlepay.png differ diff --git a/h5/mifa-pay/logos/jazzcash.png b/h5/mifa-pay/logos/jazzcash.png new file mode 100644 index 0000000..7a3ed46 Binary files /dev/null and b/h5/mifa-pay/logos/jazzcash.png differ diff --git a/h5/mifa-pay/logos/linkaja.png b/h5/mifa-pay/logos/linkaja.png new file mode 100644 index 0000000..5b763c4 Binary files /dev/null and b/h5/mifa-pay/logos/linkaja.png differ diff --git a/h5/mifa-pay/logos/mandiri.png b/h5/mifa-pay/logos/mandiri.png new file mode 100644 index 0000000..6c8f612 Binary files /dev/null and b/h5/mifa-pay/logos/mandiri.png differ diff --git a/h5/mifa-pay/logos/mobikwik.png b/h5/mifa-pay/logos/mobikwik.png new file mode 100644 index 0000000..495b604 Binary files /dev/null and b/h5/mifa-pay/logos/mobikwik.png differ diff --git a/h5/mifa-pay/logos/ovo.png b/h5/mifa-pay/logos/ovo.png new file mode 100644 index 0000000..eeec87b Binary files /dev/null and b/h5/mifa-pay/logos/ovo.png differ diff --git a/h5/mifa-pay/logos/paytm.png b/h5/mifa-pay/logos/paytm.png new file mode 100644 index 0000000..984beaa Binary files /dev/null and b/h5/mifa-pay/logos/paytm.png differ diff --git a/h5/mifa-pay/logos/permata.png b/h5/mifa-pay/logos/permata.png new file mode 100644 index 0000000..9211def Binary files /dev/null and b/h5/mifa-pay/logos/permata.png differ diff --git a/h5/mifa-pay/logos/phonepe.png b/h5/mifa-pay/logos/phonepe.png new file mode 100644 index 0000000..4c15b7a Binary files /dev/null and b/h5/mifa-pay/logos/phonepe.png differ diff --git a/h5/mifa-pay/logos/qris.png b/h5/mifa-pay/logos/qris.png new file mode 100644 index 0000000..05f761e Binary files /dev/null and b/h5/mifa-pay/logos/qris.png differ diff --git a/h5/mifa-pay/logos/shopeepay.png b/h5/mifa-pay/logos/shopeepay.png new file mode 100644 index 0000000..229e82a Binary files /dev/null and b/h5/mifa-pay/logos/shopeepay.png differ diff --git a/h5/mifa-pay/logos/upi.png b/h5/mifa-pay/logos/upi.png new file mode 100644 index 0000000..d57a0e6 Binary files /dev/null and b/h5/mifa-pay/logos/upi.png differ diff --git a/h5/mifa-pay/logos/vietqr.png b/h5/mifa-pay/logos/vietqr.png new file mode 100644 index 0000000..9ef33b7 Binary files /dev/null and b/h5/mifa-pay/logos/vietqr.png differ diff --git a/h5/mifa-pay/logos/vn-banktransfer.png b/h5/mifa-pay/logos/vn-banktransfer.png new file mode 100644 index 0000000..b841eed Binary files /dev/null and b/h5/mifa-pay/logos/vn-banktransfer.png differ