chatapp-h5/h5/transfer/transfer.js
2026-04-24 21:13:53 +08:00

439 lines
13 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const API_BASE_URL = "https://jvapi.haiyihy.com";
const DEFAULT_AVATAR = "/assets/defaultAvatar-CdxWBK1k-1776148661448.png";
const PAYEE_KEY = "transfer.payee";
const icons = {
back: "../host-center/icons/back.svg",
arrow: "../host-center/icons/arrow.svg",
dollar: "./icons/dollar.svg"
};
let commonHeaders = {
authorization: "Bearer undefined",
"req-app-intel": "version=1.0.0;build=1;model=SM-G9550;sysVersion=9;channel=Google",
"req-client": "Android",
"req-imei": "8fa54d728ab449e04f9292329ed44bda",
"req-lang": "id-ID",
"req-sys-origin": "origin=ATYOU;originChild=ATYOU",
"req-version": "V2",
"req-zone": "Asia/Shanghai"
};
let preservePayeeOnLeave = false;
const i18n = {
en: {
transfer: "Transfer",
information: "Information",
details: "Details",
available_salaries: "Available salaries",
loading: "Loading",
transfer_to_others: "Transfer to others",
search: "Search",
change: "Change",
user_id_prefix: "ID:",
failed_to_load_balance: "Failed to load balance",
please_select_amount: "Please select amount",
please_select_payee: "Please select payee",
transfer_dollar_success: "Transfer ${price} to {name} successfully"
},
ar: {
transfer: "تحويل",
information: "المعلومات",
details: "التفاصيل",
available_salaries: "الرواتب المتاحة",
loading: "تحميل",
transfer_to_others: "تحويل للآخرين",
search: "بحث",
change: "تغيير",
user_id_prefix: "ID:",
failed_to_load_balance: "فشل تحميل الرصيد",
please_select_amount: "يرجى اختيار المبلغ",
please_select_payee: "يرجى اختيار المستلم",
transfer_dollar_success: "تم تحويل ${price} إلى {name} بنجاح"
},
tr: {
transfer: "Transfer",
information: "Bilgi",
details: "Detaylar",
available_salaries: "Kullanılabilir maaşlar",
loading: "Yükleniyor",
transfer_to_others: "Başkalarına transfer",
search: "Ara",
change: "Değiştir",
user_id_prefix: "ID:",
failed_to_load_balance: "Bakiye yüklenemedi",
please_select_amount: "Lütfen tutar seçin",
please_select_payee: "Lütfen alıcı seçin",
transfer_dollar_success: "${price}, {name} kullanıcısına başarıyla aktarıldı"
},
bn: {
transfer: "ট্রান্সফার",
information: "তথ্য",
details: "বিস্তারিত",
available_salaries: "উপলভ্য বেতন",
loading: "লোড হচ্ছে",
transfer_to_others: "অন্যদের ট্রান্সফার",
search: "সার্চ",
change: "পরিবর্তন",
user_id_prefix: "ID:",
failed_to_load_balance: "ব্যালেন্স লোড করতে ব্যর্থ",
please_select_amount: "পরিমাণ নির্বাচন করুন",
please_select_payee: "প্রাপক নির্বাচন করুন",
transfer_dollar_success: "${price} {name} কে সফলভাবে ট্রান্সফার হয়েছে"
}
};
const state = {
lang: resolveLanguage(),
balance: "0.00",
loading: false,
selectedId: null,
payee: {
id: null,
account: "",
name: "",
avatar: "",
type: "",
isHost: false,
isAgency: false,
hasSalary: false
},
amounts: [
{ id: 1, amount: 10500, price: 1 },
{ id: 2, amount: 52500, price: 5 },
{ id: 3, amount: 105000, price: 10 },
{ id: 4, amount: 525000, price: 50 },
{ id: 5, amount: 1050000, price: 100 },
{ id: 6, amount: 2100000, price: 200 }
]
};
const $ = (selector) => document.querySelector(selector);
const $$ = (selector) => Array.from(document.querySelectorAll(selector));
const t = (key, params = {}) => {
let text = (i18n[state.lang] && i18n[state.lang][key]) || i18n.en[key] || key;
Object.entries(params).forEach(([name, value]) => {
text = text.replaceAll(`{${name}}`, value);
});
return text;
};
function resolveLanguage() {
const params = new URLSearchParams(window.location.search);
const lang = params.get("lang") || navigator.language || "en";
if (lang.startsWith("ar")) return "ar";
if (lang.startsWith("tr")) return "tr";
if (lang.startsWith("bn")) return "bn";
return "en";
}
function splitHeaderPairs(value) {
const result = {};
if (!value) return result;
value.split(";").forEach((item) => {
const index = item.indexOf("=");
if (index > -1) result[item.slice(0, index)] = item.slice(index + 1);
});
return result;
}
function parseAccessOrigin(raw) {
const data = JSON.parse(raw);
const appIntel = splitHeaderPairs(data["Req-App-Intel"]);
const sysOrigin = splitHeaderPairs(data["Req-Sys-Origin"]);
return {
reqLang: data["Req-Lang"],
authorization: data.Authorization,
buildVersion: appIntel.build,
appVersion: appIntel.version,
appChannel: appIntel.channel,
reqImei: appIntel["Req-Imei"],
sysOrigin: sysOrigin.origin,
sysOriginChild: sysOrigin.child
};
}
function setHeadersFromApp(headers) {
const next = {};
if (headers.authorization) next.authorization = headers.authorization;
if (headers.reqLang) next["req-lang"] = headers.reqLang;
if (headers.appVersion || headers.buildVersion || headers.appChannel) {
const intel = [];
if (headers.appVersion) intel.push(`version=${headers.appVersion}`);
if (headers.buildVersion) intel.push(`build=${headers.buildVersion}`);
if (headers.appChannel) intel.push(`channel=${headers.appChannel}`);
next["req-app-intel"] = intel.join(";");
}
if (headers.reqImei) next["req-imei"] = headers.reqImei;
if (headers.sysOrigin) {
const origin = [`origin=${headers.sysOrigin}`];
if (headers.sysOriginChild) origin.push(`child=${headers.sysOriginChild}`);
next["req-sys-origin"] = origin.join(";");
}
commonHeaders = { ...commonHeaders, ...next };
}
function connectToApp() {
return new Promise((resolve) => {
window.renderData = resolve;
window.getIosAccessOriginParam = resolve;
if (!(window.app || window.webkit || window.FlutterPageControl)) {
setHeadersFromApp({
authorization: "",
reqLang: state.lang,
appVersion: "1.0.0",
buildVersion: "1",
appChannel: "Web",
reqImei: "H5-STATIC",
sysOrigin: "LIKEI",
sysOriginChild: "LIKEI"
});
resolve({ success: true });
return;
}
let attempt = 0;
const poll = () => {
attempt += 1;
if (window.app && typeof window.app.getAccessOrigin === "function") {
const raw = window.app.getAccessOrigin();
if (raw) setHeadersFromApp(parseAccessOrigin(raw));
resolve({ success: true });
return;
}
if (attempt < 50) {
window.setTimeout(poll, 100);
return;
}
if (window.FlutterApp && typeof window.FlutterApp.postMessage === "function") {
window.FlutterApp.postMessage("requestAccessOrigin");
}
resolve({ success: true });
};
poll();
}).then((result) => {
if (typeof result === "string") setHeadersFromApp(parseAccessOrigin(result));
return result;
});
}
async function request(method, path, body) {
const response = await fetch(`${API_BASE_URL}${path}`, {
method,
headers: {
...commonHeaders,
"content-type": "application/json"
},
body: body === undefined ? undefined : JSON.stringify(body)
});
const data = await response.json().catch(() => ({}));
if (!response.ok || data.status === false) {
const error = new Error(data.errorMsg || data.message || `HTTP ${response.status}`);
error.response = data;
throw error;
}
return data;
}
const apiGet = (path) => request("GET", path);
const apiPost = (path, body) => request("POST", path, body);
function formatMoney(value) {
const number = Number(value || 0);
return Number.isFinite(number) ? number.toFixed(2) : "0.00";
}
function getStoredPayee() {
try {
const payee = JSON.parse(sessionStorage.getItem(PAYEE_KEY) || "null");
if (payee) Object.assign(state.payee, payee);
} catch (error) {
console.error("Failed to parse transfer payee:", error);
}
}
function clearPayee() {
Object.assign(state.payee, {
id: null,
account: "",
name: "",
avatar: "",
type: "",
isHost: false,
isAgency: false,
hasSalary: false
});
sessionStorage.removeItem(PAYEE_KEY);
}
async function fetchBalance() {
state.loading = true;
renderBalance();
try {
const result = await apiGet("/wallet/bank/balance");
state.balance = result.status && typeof result.body === "number" ? result.body.toFixed(2) : "0.00";
} catch (error) {
console.error("Failed to fetch bank balance:", error);
state.balance = "0.00";
showToast(t("failed_to_load_balance"));
} finally {
state.loading = false;
renderBalance();
}
}
async function submitTransfer() {
if (!state.selectedId) {
showToast(t("please_select_amount"));
return;
}
if (!state.payee.id) {
showToast(t("please_select_payee"));
return;
}
const selected = state.amounts.find((item) => item.id === state.selectedId);
const payload = {
amount: selected.price,
acceptUserId: state.payee.id,
acceptMethod: "BANK_TRANSFER"
};
try {
const result = await apiPost("/wallet/bank/transferGold", payload);
if (result.status) {
showToast(t("transfer_dollar_success", { price: selected.price, name: state.payee.name }));
state.selectedId = null;
await fetchBalance();
renderAmounts();
}
} catch (error) {
console.error("Transfer failed:", error);
showToast(error.response?.errorMsg || error.message);
}
}
function normalizeRoute(target) {
const url = new URL(target, window.location.origin);
const currentLang = new URLSearchParams(window.location.search).get("lang");
if (currentLang && !url.searchParams.has("lang")) url.searchParams.set("lang", currentLang);
return `${url.pathname}${url.search}${url.hash}`;
}
function navigateTo(target) {
window.location.href = normalizeRoute(target);
}
function closePage() {
if (window.history.length > 1) {
window.history.back();
return;
}
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");
}
}
function showToast(message) {
const toast = $("#toast");
toast.textContent = message || "Request failed";
toast.hidden = false;
window.clearTimeout(showToast.timer);
showToast.timer = window.setTimeout(() => {
toast.hidden = true;
}, 2500);
}
function renderI18n() {
document.documentElement.lang = state.lang;
document.documentElement.dir = state.lang === "ar" ? "rtl" : "ltr";
$(".lang-button").textContent = state.lang.toUpperCase();
$$("[data-i18n]").forEach((node) => {
node.textContent = t(node.dataset.i18n);
});
}
function renderIcons() {
$("#backIcon").src = icons.back;
$("#detailsArrow").src = icons.arrow;
}
function renderBalance() {
$("#balanceLoading").hidden = !state.loading;
$("#balanceValue").hidden = state.loading;
$("#balanceValue").textContent = state.balance;
}
function renderPayee() {
const hasPayee = Boolean(state.payee.id);
$("#payeeSelected").hidden = !hasPayee;
$("#payeeEmpty").hidden = hasPayee;
if (!hasPayee) return;
$("#payeeName").textContent = state.payee.name;
$("#payeeAccount").textContent = state.payee.goodID || state.payee.account || "-";
const avatar = $("#payeeAvatar");
avatar.src = state.payee.avatar || "";
avatar.alt = state.payee.name || "";
}
function renderAmounts() {
const root = $("#amountGrid");
root.innerHTML = "";
state.amounts.forEach((item) => {
const button = document.createElement("button");
button.type = "button";
button.className = `coin-item${state.selectedId === item.id ? " selected" : ""}`;
button.innerHTML = `<img src="${icons.dollar}" alt="" /><div class="coin-price">$${item.price}</div>`;
button.addEventListener("click", () => {
state.selectedId = state.selectedId === item.id ? null : item.id;
renderAmounts();
});
root.appendChild(button);
});
$("#transferButton").disabled = !state.selectedId || !state.payee.id;
}
function render() {
renderI18n();
renderIcons();
renderBalance();
renderPayee();
renderAmounts();
}
function bindEvents() {
$("[data-action='back']").addEventListener("click", closePage);
$(".lang-button").addEventListener("click", () => navigateTo("/language"));
$$("[data-route]").forEach((node) => {
node.addEventListener("click", () => navigateTo(node.dataset.route));
});
$$("[data-action='search-payee']").forEach((node) => {
node.addEventListener("click", () => {
clearPayee();
preservePayeeOnLeave = true;
navigateTo("/search-payee?from=/transfer");
});
});
$("#transferButton").addEventListener("click", submitTransfer);
window.addEventListener("pagehide", () => {
if (!preservePayeeOnLeave) sessionStorage.removeItem(PAYEE_KEY);
});
$("#payeeAvatar").addEventListener("error", (event) => {
event.target.onerror = null;
event.target.src = DEFAULT_AVATAR;
});
}
async function init() {
getStoredPayee();
bindEvents();
render();
await connectToApp();
await fetchBalance();
}
init();