2026-04-30 15:20:34 +08:00

545 lines
14 KiB
JavaScript

const query = readURLParams();
const storedAppId = window.localStorage.getItem("mifa-pay-application-id") || "";
const API_BASE_URL = "https://jvapi.haiyihy.com";
const LOGO_SRC = "./mifapay-logo.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,
selectedGoodsId: "",
selectedGoods: null,
pendingChannelCode: "",
};
const els = {
applicationId: document.getElementById("applicationId"),
productList: document.getElementById("productList"),
channelList: document.getElementById("channelList"),
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 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 authHeadersFromToken(token) {
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=ATYOU;originChild=ATYOU",
};
}
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 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 currentCountryId() {
return 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 getChannelList() {
return state.commodityCard?.channels || [];
}
function getChannelCode(item) {
return item?.details?.channelCode || item?.channel?.channelCode || "";
}
function getAmountText(item) {
const content = item?.content || item?.obtainGoldsQuantity || "-";
return `${content} ${item?.type || ""}`.trim();
}
function getPriceText(item) {
const currency = item?.currency || state.commodityCard?.payCountry?.currency || "USD";
return `${item?.amount || item?.amountUsd || "-"} ${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];
state.selectedGoods = matched;
state.selectedGoodsId = String(matched.id);
}
function renderProducts() {
const goods = getGoodsList();
ensureSelectedGoods();
if (!goods.length) {
els.productList.innerHTML = '<div class="empty">No amount available.</div>';
return;
}
els.productList.innerHTML = goods
.map((item) => {
const selected = String(item.id) === state.selectedGoodsId;
return `
<button
type="button"
class="amount-item${selected ? " selected" : ""}"
data-goods-id="${item.id}"
>
<span class="check"></span>
<span class="amount-copy">
<span class="amount-value">${item.content || item.obtainGoldsQuantity || "-"}</span>
<span class="amount-meta">${item.type || "Recharge"}</span>
</span>
<span class="price-copy">
<strong>${item.amount || item.amountUsd || "-"}</strong>
<span>USD ${item.amountUsd || "-"}</span>
</span>
</button>
`;
})
.join("");
els.productList.querySelectorAll("[data-goods-id]").forEach((button) => {
button.addEventListener("click", () => {
const goodsId = button.getAttribute("data-goods-id");
state.selectedGoodsId = goodsId;
state.selectedGoods =
getGoodsList().find((item) => String(item.id) === goodsId) || null;
renderProducts();
});
});
}
function renderChannels() {
const channels = getChannelList();
if (!channels.length) {
els.channelList.innerHTML = '<div class="empty">No payment method available.</div>';
return;
}
els.channelList.innerHTML = channels
.map((item) => {
const code = getChannelCode(item);
const pending = state.pendingChannelCode === code;
return `
<button
type="button"
class="channel-button"
data-channel-code="${code}"
${pending ? "disabled" : ""}
>
<span class="channel-logo">
<img src="${LOGO_SRC}" alt="MiFaPay" />
</span>
<span class="channel-name">${pending ? "Processing..." : "Pay with MiFaPay"}</span>
</button>
`;
})
.join("");
els.channelList.querySelectorAll("[data-channel-code]").forEach((button) => {
button.addEventListener("click", async () => {
const channelCode = button.getAttribute("data-channel-code");
try {
await createOrder(channelCode);
} catch (error) {
state.pendingChannelCode = "";
renderChannels();
setStatus(extractErrorMessage(error));
}
});
});
}
function selectCountryByProfile() {
const userCountryCode = state.userProfile?.countryCode;
if (!userCountryCode) {
return false;
}
const matched = state.countries.find((item) =>
getCountryLabel(item).toUpperCase().includes(userCountryCode.toUpperCase())
);
if (!matched) {
return false;
}
state.selectedCountryId = String(matched.id);
return true;
}
async function connectSession() {
const response = await apiGet("/team/member/profile");
const userProfile = resolveUserProfile(response);
if (!userProfile?.id) {
throw new Error("Account not available");
}
state.userProfile = userProfile;
}
async function refreshMeta() {
if (!state.applicationId) {
throw new Error("Missing applicationId");
}
persistSettings();
els.applicationId.value = state.applicationId;
const applicationResp = await apiGet(
`/order/web/pay/application/${encodeURIComponent(state.applicationId)}`
);
state.application = applicationResp?.body || {};
const profileQuery = new URLSearchParams({
sysOrigin: state.application?.appCode || state.userProfile?.originSys || "",
type: state.productType,
});
const account = currentUserAccount();
if (account) {
profileQuery.set("account", account);
}
if (currentUserId()) {
profileQuery.set("userId", currentUserId());
}
const profileResp = await apiGet(
`/order/web/pay/user-profile?${profileQuery.toString()}`
);
const payProfile = profileResp?.body || {};
if (payProfile.userProfile) {
state.userProfile = {
...state.userProfile,
...payProfile.userProfile,
};
}
state.regionId = payProfile.regionId || "";
state.countries = Array.isArray(payProfile.countryList) ? payProfile.countryList : [];
if (!state.countries.length) {
state.selectedCountryId = "";
state.commodityCard = null;
renderProducts();
renderChannels();
throw new Error("No supported country");
}
if (
!state.selectedCountryId ||
!state.countries.some((item) => String(item.id) === String(state.selectedCountryId))
) {
if (!selectCountryByProfile()) {
state.selectedCountryId = String(state.countries[0].id);
}
}
const commodityResp = await apiGet(
`/order/web/pay/commodity?applicationId=${encodeURIComponent(
state.applicationId
)}&payCountryId=${encodeURIComponent(currentCountryId())}&type=${encodeURIComponent(
state.productType
)}&regionId=${encodeURIComponent(currentRegionId())}`
);
state.commodityCard = commodityResp?.body || {};
renderProducts();
renderChannels();
setStatus("");
}
function safeDecodeUrl(url) {
if (!url) {
return "";
}
try {
return decodeURIComponent(url);
} catch (error) {
return url;
}
}
async function createOrder(channelCode) {
if (!state.selectedGoods) {
throw new Error("Select an amount first");
}
if (!currentUserId()) {
throw new Error("Account not available");
}
state.pendingChannelCode = channelCode;
setStatus("");
renderChannels();
const payload = {
applicationId: state.applicationId,
goodsId: state.selectedGoodsId,
payCountryId: currentCountryId(),
userId: currentUserId(),
channelCode,
newVersion: true,
};
const response = await apiPost("/order/web/pay/recharge", payload);
const requestUrl = safeDecodeUrl(response?.body?.requestUrl || "");
if (!requestUrl) {
throw new Error("Missing payment url");
}
window.location.href = requestUrl;
}
async function reloadPageData() {
await connectSession();
await refreshMeta();
}
async function init() {
renderProducts();
renderChannels();
try {
await reloadPageData();
} catch (error) {
setStatus(extractErrorMessage(error));
}
}
init();