(function () { const API_BASE_URL = "https://jvapi.haiyihy.com"; const EXCHANGE_RATE = 80000; const WITHDRAW_MIN_AMOUNT = 50; const supportedLanguages = ["en", "ar", "tr", "id"]; const languageLabels = { en: "EN", ar: "AR", tr: "TR", id: "ID" }; const translations = { en: { page_label: "Withdraw and exchange", back: "Back", title: "Withdraw/Exchange", language_button_aria: "Change language", available_income: "salary", bd_salary: "BD salary", bd_leader_salary: "BD Leader salary", withdraw: "Withdraw", exchange: "Exchange", transfer: "Transfer", withdraw_amount: "Withdraw amount", exchange_amount: "Exchange amount", transfer_amount: "Transfer amount", transfer_to: "Transfer to", search: "Search", searching: "Searching...", minimum: "Minimum 50 USD", all: "All", exchange_rate: "Exchange rate", payment_method: "USDT TRC20 address", payment_method_subtitle: "Withdrawals are sent to your TRC20 USDT address.", no_payment_info: "No USDT address", no_payment_info_copy: "Fill in a TRC20 USDT address before withdrawing.", trc20_address: "TRC20 address", save_address: "Save address", saving: "Saving...", usdt_only: "USDT only", withdraw_now: "Withdraw now", exchange_now: "Exchange now", transfer_now: "Transfer now", submitting: "Submitting...", payee: "Payee", uid_prefix: "UID:", missing_token: "Missing token", load_balance_failed: "Failed to load balance", load_payment_failed: "Failed to load payment info", missing_payment_info: "Please add a TRC20 USDT address", invalid_address: "Please enter a valid TRC20 USDT address", invalid_amount: "Please enter a valid amount", amount_too_low: "Minimum withdrawal amount is 50 USD", amount_exceeds_balance: "Amount exceeds salary", payee_not_found: "User not found", missing_payee: "Please search and select a user", address_saved: "Address saved", withdraw_success: "Withdrawal submitted", withdraw_failed: "Withdrawal failed", exchange_success: "Exchange success", exchange_failed: "Exchange failed", transfer_success: "Transfer success", transfer_failed: "Transfer failed", salary_history: "Salary history", close: "Close", loading: "Loading...", no_salary_history: "No salary history", income: "Income", expense: "Expense", balance: "Balance", receive_time: "Receive time", transfer_time: "Transfer time", period: "Period", days: "Days", target: "Target", work_report: "Work report", load_salary_history_failed: "Failed to load salary history" }, ar: { page_label: "السحب والتبديل", back: "رجوع", title: "سحب/تبديل", language_button_aria: "تغيير اللغة", available_income: "الراتب", bd_salary: "راتب BD", bd_leader_salary: "راتب قائد BD", withdraw: "سحب", exchange: "تبديل", transfer: "تحويل", withdraw_amount: "مبلغ السحب", exchange_amount: "مبلغ التبديل", transfer_amount: "مبلغ التحويل", transfer_to: "تحويل إلى", search: "بحث", searching: "جار البحث...", minimum: "الحد الأدنى 50 USD", all: "الكل", exchange_rate: "سعر التبديل", payment_method: "عنوان USDT TRC20", payment_method_subtitle: "يتم إرسال السحب إلى عنوان USDT TRC20.", no_payment_info: "لا يوجد عنوان USDT", no_payment_info_copy: "أدخل عنوان USDT TRC20 قبل السحب.", trc20_address: "عنوان TRC20", save_address: "حفظ العنوان", saving: "جار الحفظ...", usdt_only: "USDT فقط", withdraw_now: "اسحب الآن", exchange_now: "بدل الآن", transfer_now: "حوّل الآن", submitting: "جار الإرسال...", payee: "المستفيد", uid_prefix: "UID:", missing_token: "رمز الدخول مفقود", load_balance_failed: "فشل تحميل الرصيد", load_payment_failed: "فشل تحميل معلومات الدفع", missing_payment_info: "يرجى إضافة عنوان USDT TRC20", invalid_address: "يرجى إدخال عنوان USDT TRC20 صالح", invalid_amount: "يرجى إدخال مبلغ صالح", amount_too_low: "الحد الأدنى للسحب هو 50 USD", amount_exceeds_balance: "المبلغ يتجاوز الراتب", payee_not_found: "لم يتم العثور على المستخدم", missing_payee: "يرجى البحث واختيار مستخدم", address_saved: "تم حفظ العنوان", withdraw_success: "تم إرسال طلب السحب", withdraw_failed: "فشل السحب", exchange_success: "تم التبديل", exchange_failed: "فشل التبديل", transfer_success: "تم التحويل", transfer_failed: "فشل التحويل", salary_history: "سجل الراتب", close: "إغلاق", loading: "جار التحميل...", no_salary_history: "لا يوجد سجل راتب", income: "دخل", expense: "مصروف", balance: "الرصيد", period: "الفترة", days: "الأيام", target: "الهدف", work_report: "تقرير العمل", load_salary_history_failed: "فشل تحميل سجل الراتب" }, tr: { page_label: "Çekme ve değişim", back: "Geri", title: "Çek/Değiştir", language_button_aria: "Dili değiştir", available_income: "Maaş", bd_salary: "BD maaşı", bd_leader_salary: "BD Leader maaşı", withdraw: "Withdraw", exchange: "Exchange", transfer: "Transfer", withdraw_amount: "Withdraw amount", exchange_amount: "Exchange amount", transfer_amount: "Transfer amount", transfer_to: "Transfer to", search: "Search", searching: "Searching...", minimum: "Minimum 50 USD", all: "All", exchange_rate: "Exchange rate", payment_method: "USDT TRC20 address", payment_method_subtitle: "Withdrawals are sent to your TRC20 USDT address.", no_payment_info: "No USDT address", no_payment_info_copy: "Fill in a TRC20 USDT address before withdrawing.", trc20_address: "TRC20 address", save_address: "Save address", saving: "Saving...", usdt_only: "USDT only", withdraw_now: "Withdraw now", exchange_now: "Exchange now", transfer_now: "Transfer now", submitting: "Submitting...", payee: "Payee", uid_prefix: "UID:", missing_token: "Missing token", load_balance_failed: "Failed to load balance", load_payment_failed: "Failed to load payment info", missing_payment_info: "Please add a TRC20 USDT address", invalid_address: "Please enter a valid TRC20 USDT address", invalid_amount: "Please enter a valid amount", amount_too_low: "Minimum withdrawal amount is 50 USD", amount_exceeds_balance: "Amount exceeds salary", payee_not_found: "User not found", missing_payee: "Please search and select a user", address_saved: "Address saved", withdraw_success: "Withdrawal submitted", withdraw_failed: "Withdrawal failed", exchange_success: "Exchange success", exchange_failed: "Exchange failed", transfer_success: "Transfer success", transfer_failed: "Transfer failed", salary_history: "Salary history", close: "Close", loading: "Loading...", no_salary_history: "No salary history", income: "Income", expense: "Expense", balance: "Balance", period: "Period", days: "Days", target: "Target", work_report: "Work report", load_salary_history_failed: "Failed to load salary history" }, id: { page_label: "Tarik dan tukar", back: "Kembali", title: "Tarik/Tukar", language_button_aria: "Ubah bahasa", available_income: "Gaji", bd_salary: "Gaji BD", bd_leader_salary: "Gaji BD Leader", withdraw: "Tarik", exchange: "Tukar", transfer: "Transfer", withdraw_amount: "Jumlah penarikan", exchange_amount: "Jumlah penukaran", transfer_amount: "Jumlah transfer", transfer_to: "Transfer ke", search: "Cari", searching: "Mencari...", minimum: "Minimum 50 USD", all: "Semua", exchange_rate: "Kurs penukaran", payment_method: "Alamat USDT TRC20", payment_method_subtitle: "Penarikan dikirim ke alamat USDT TRC20 Anda.", no_payment_info: "Tidak ada alamat USDT", no_payment_info_copy: "Isi alamat USDT TRC20 sebelum menarik.", trc20_address: "Alamat TRC20", save_address: "Simpan alamat", saving: "Menyimpan...", usdt_only: "Hanya USDT", withdraw_now: "Tarik sekarang", exchange_now: "Tukar sekarang", transfer_now: "Transfer sekarang", submitting: "Mengirim...", payee: "Penerima", uid_prefix: "UID:", missing_token: "Token tidak ditemukan", load_balance_failed: "Gagal memuat saldo", load_payment_failed: "Gagal memuat informasi pembayaran", missing_payment_info: "Tambahkan alamat USDT TRC20", invalid_address: "Masukkan alamat USDT TRC20 yang valid", invalid_amount: "Masukkan jumlah yang valid", amount_too_low: "Minimum penarikan adalah 50 USD", amount_exceeds_balance: "Jumlah melebihi gaji", payee_not_found: "Pengguna tidak ditemukan", missing_payee: "Cari dan pilih pengguna", address_saved: "Alamat disimpan", withdraw_success: "Penarikan terkirim", withdraw_failed: "Penarikan gagal", exchange_success: "Penukaran berhasil", exchange_failed: "Penukaran gagal", transfer_success: "Transfer berhasil", transfer_failed: "Transfer gagal", salary_history: "Riwayat gaji", close: "Tutup", loading: "Memuat...", no_salary_history: "Tidak ada riwayat gaji", income: "Masuk", expense: "Keluar", balance: "Saldo", period: "Periode", days: "Hari", target: "Target", work_report: "Laporan kerja", load_salary_history_failed: "Gagal memuat riwayat gaji" } }; const params = readURLParams(); let currentLang = normalizeLanguage(params.get("lang")); let currentMode = normalizeMode(params.get("mode")) || "withdraw"; const pageIdentity = normalizeIdentity(params.get("identity")); let currentSalaryType = normalizeSalaryType(params.get("salaryType")) || defaultSalaryTypeForIdentity(pageIdentity); let currentMessages = translations[currentLang] || translations.en; let availableBalance = 0; let salaryBalances = {}; let selectedPaymentInfo = null; let selectedPayee = null; let currentProfile = null; let isSubmitting = false; let isSavingAddress = false; let isSearchingPayee = false; let isSalaryHistoryLoading = false; let salaryHistoryItems = []; let salaryHistoryTotal = 0; let salaryHistoryLoadedType = ""; const inputs = { withdraw: document.querySelector("#withdrawAmount"), exchange: document.querySelector("#exchangeAmount"), transfer: document.querySelector("#transferAmount") }; const balanceAmount = document.querySelector("#balanceAmount"); const emptyState = document.querySelector(".empty-address-state"); const addressForm = document.querySelector("#addressForm"); const trc20AddressInput = document.querySelector("#trc20Address"); const saveAddressButton = document.querySelector("#saveAddressButton"); const boundCard = document.querySelector(".bound-address-card"); const paymentSection = document.querySelector("[data-payment-section]"); const submitButton = document.querySelector(".submit-button"); const toast = document.querySelector(".toast"); const payeeResult = document.querySelector("#payeeResult"); const transferAccountInput = document.querySelector("#transferAccount"); const searchPayeeButton = document.querySelector("#searchPayeeButton"); const exchangeRateText = document.querySelector("#exchangeRateText"); const salarySelector = document.querySelector("#salarySelector"); const salaryHistoryButton = document.querySelector("#salaryHistoryButton"); const salaryHistoryModal = document.querySelector("#salaryHistoryModal"); const salaryHistoryList = document.querySelector("#salaryHistoryList"); const salaryHistoryTotalNode = document.querySelector("#salaryHistoryTotal"); 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 splitHeaderPairs(value) { const result = {}; String(value || "").split(";").forEach((item) => { const index = item.indexOf("="); if (index > -1) result[item.slice(0, index).trim()] = item.slice(index + 1).trim(); }); return result; } function firstRawParam(names) { for (const name of names) { const value = readRawParam(name); if (value) return value; } return ""; } function readRequestSysOrigin() { const packed = splitHeaderPairs(firstRawParam(["req-sys-origin", "Req-Sys-Origin", "reqSysOrigin", "ReqSysOrigin"])); const origin = String(firstRawParam(["sysOrigin", "origin", "Sys-Origin"]) || packed.origin || "LIKEI") .trim() .toUpperCase(); const originChild = String( firstRawParam(["sysOriginChild", "originChild", "child", "Sys-Origin-Child"]) || packed.originChild || packed.child || origin ).trim().toUpperCase(); return { origin, originChild }; } function normalizeLanguage(lang) { const value = String(lang || "en").toLowerCase(); return supportedLanguages.includes(value) ? value : "en"; } function normalizeMode(mode) { return ["withdraw", "exchange", "transfer"].includes(mode) ? mode : ""; } function normalizeIdentity(identity) { const value = String(identity || "").trim().toUpperCase(); return ["HOST", "BD", "BD_LEADER", "ADMIN"].includes(value) ? value : ""; } function normalizeSalaryType(type) { const value = String(type || "").trim().toUpperCase(); return ["HOST_SALARY", "BD_SALARY", "BD_LEADER_SALARY", "ADMIN_SALARY"].includes(value) ? value : ""; } function defaultSalaryTypeForIdentity(identity) { if (identity === "BD_LEADER") return "BD_LEADER_SALARY"; if (identity === "BD") return "BD_SALARY"; if (identity === "ADMIN") return "ADMIN_SALARY"; return ""; } function isSalaryAccountMode() { return Boolean(currentSalaryType && currentSalaryType !== "HOST_SALARY"); } function activeSalaryType() { return currentSalaryType || "HOST_SALARY"; } function responseBody(data) { return data?.body ?? data?.data ?? null; } function apiBaseURL() { const override = window.HYAPP_API_BASE_URL || params.get("apiBase"); if (override) return String(override).replace(/\/+$/, ""); return API_BASE_URL; } function normalizeAuthorization(token) { const value = String(token || "").trim(); if (!value) return ""; return /^bearer\s+/i.test(value) ? value : `Bearer ${value}`; } function authHeadersFromToken(token, hasBody) { const sysOrigin = readRequestSysOrigin(); const headers = { 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=${sysOrigin.origin};originChild=${sysOrigin.originChild}` }; if (hasBody) headers["content-type"] = "application/json"; return headers; } async function requestJSON(path, options = {}) { const token = readRawParam("token"); if (!token) throw new Error(message("missing_token", "Missing token")); const response = await fetch(`${apiBaseURL()}${path}`, { method: options.method || "GET", cache: "no-store", headers: authHeadersFromToken(token, options.body !== undefined), body: options.body === undefined ? undefined : JSON.stringify(options.body) }); 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 message(key, fallback) { return currentMessages[key] || translations.en[key] || fallback || key; } function formatMoney(value) { const number = Number(value); return Number.isFinite(number) ? new Intl.NumberFormat("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(number) : "0.00"; } function formatMoneyInput(value) { const number = Number(value); return Number.isFinite(number) ? number.toFixed(2) : "0.00"; } function moneyNumber(value) { const number = Number(value); return Number.isFinite(number) ? number : 0; } function currentUserId() { return currentProfile?.id || currentProfile?.userId || firstRawParam(["userId", "UserId", "uid", "UID", "id"]); } function activeInput() { return inputs[currentMode] || null; } function activeAmount() { return moneyNumber(activeInput()?.value); } function formatExchangeRate() { return `1$ = ${EXCHANGE_RATE} golds`; } function salaryBalanceValue(type) { const balance = salaryBalances[type] || {}; return moneyNumber(balance.availableBalance ?? balance.balance ?? balance.currentSalary ?? 0); } function renderSalarySelector() { if (!salarySelector) return; const showSelector = pageIdentity === "BD_LEADER"; salarySelector.hidden = !showSelector; if (!showSelector) return; salarySelector.querySelectorAll("[data-salary-type]").forEach((button) => { const type = normalizeSalaryType(button.dataset.salaryType); const active = type === currentSalaryType; const label = message(type === "BD_SALARY" ? "bd_salary" : "bd_leader_salary"); button.classList.toggle("active", active); button.setAttribute("aria-pressed", String(active)); button.textContent = `${label} $${formatMoney(salaryBalanceValue(type))}`; }); } function setActiveSalaryType(type) { const nextType = normalizeSalaryType(type); if (!nextType || nextType === currentSalaryType) return; currentSalaryType = nextType; renderBalance(salaryBalanceValue(nextType)); if (salaryHistoryModal && !salaryHistoryModal.hidden && salaryHistoryLoadedType !== activeSalaryType()) { openSalaryHistory(); } } function isValidTrc20Address(value) { return /^T[1-9A-HJ-NP-Za-km-z]{33}$/.test(String(value || "").trim()); } function maskAddress(value) { const text = String(value || "").trim(); if (text.length <= 12) return text || "-"; return `${text.slice(0, 6)}...${text.slice(-6)}`; } function applyLanguage(lang) { currentLang = normalizeLanguage(lang); currentMessages = translations[currentLang] || translations.en; document.documentElement.lang = currentLang; document.documentElement.dir = currentLang === "ar" ? "rtl" : "ltr"; document.querySelectorAll("[data-i18n]").forEach((node) => { const value = currentMessages[node.dataset.i18n]; if (value) node.textContent = value; }); document.querySelectorAll("[data-i18n-aria]").forEach((node) => { const value = currentMessages[node.dataset.i18nAria]; if (value) node.setAttribute("aria-label", value); }); const languageButton = document.querySelector(".language-button"); if (languageButton) { languageButton.textContent = languageLabels[currentLang]; languageButton.setAttribute("aria-label", message("language_button_aria", "Change language")); } document.querySelectorAll(".language-menu [data-lang]").forEach((button) => { const isActive = button.dataset.lang === currentLang; button.classList.toggle("is-active", isActive); button.setAttribute("aria-pressed", String(isActive)); }); if (exchangeRateText) exchangeRateText.textContent = formatExchangeRate(); renderSalarySelector(); renderPayee(); renderSalaryHistory(); updateSubmitState(); } function setMode(mode) { const nextMode = normalizeMode(mode) || "withdraw"; currentMode = nextMode; document.querySelectorAll(".mode-tab").forEach((tab) => { const active = tab.dataset.mode === nextMode; tab.classList.toggle("active", active); tab.setAttribute("aria-selected", String(active)); }); document.querySelectorAll(".mode-panel").forEach((panel) => { panel.classList.toggle("active", panel.dataset.panel === nextMode); }); if (paymentSection) paymentSection.hidden = nextMode !== "withdraw"; updateSubmitState(); } function updateSubmitState() { if (!submitButton) return; if (isSubmitting) { submitButton.textContent = message("submitting", "Submitting..."); submitButton.disabled = true; return; } const submitLabels = { withdraw: message("withdraw_now", "Withdraw now"), exchange: message("exchange_now", "Exchange now"), transfer: message("transfer_now", "Transfer now") }; submitButton.textContent = submitLabels[currentMode]; const amount = activeAmount(); const exceedsBalance = amount > availableBalance; const missingWithdrawInfo = currentMode === "withdraw" && !selectedPaymentInfo?.id; const missingPayee = currentMode === "transfer" && !selectedPayee?.id; submitButton.disabled = !amount || amount <= 0 || exceedsBalance || missingWithdrawInfo || missingPayee || (currentMode === "withdraw" && amount < WITHDRAW_MIN_AMOUNT); } function showToast(keyOrText, fallback) { if (!toast) return; toast.textContent = message(keyOrText, fallback || keyOrText); toast.hidden = false; window.clearTimeout(showToast.timer); showToast.timer = window.setTimeout(() => { toast.hidden = true; }, 2200); } function renderBalance(value) { availableBalance = moneyNumber(value); if (balanceAmount) balanceAmount.textContent = formatMoney(availableBalance); renderSalarySelector(); Object.values(inputs).forEach((input) => { if (input) input.max = formatMoneyInput(availableBalance); }); updateSubmitState(); } function renderPaymentInfo(paymentInfo) { selectedPaymentInfo = paymentInfo || null; if (!selectedPaymentInfo) { if (emptyState) emptyState.hidden = false; if (addressForm) addressForm.hidden = false; if (boundCard) boundCard.hidden = true; updateSubmitState(); return; } const badge = boundCard?.querySelector(".network-badge"); const network = boundCard?.querySelector(".bound-network"); const address = boundCard?.querySelector(".bound-address"); const meta = boundCard?.querySelector(".bound-meta"); if (badge) badge.textContent = "TRC20"; if (network) network.textContent = "USDT TRC20"; if (address) address.textContent = maskAddress(selectedPaymentInfo.cardNo); if (meta) meta.textContent = message("usdt_only", "USDT only"); if (emptyState) emptyState.hidden = true; if (addressForm) addressForm.hidden = true; if (boundCard) boundCard.hidden = false; updateSubmitState(); } function setSalaryHistoryModalOpen(open) { if (!salaryHistoryModal) return; salaryHistoryModal.hidden = !open; document.body.classList.toggle("salary-history-open", open); } function formatHistoryDateParts(date) { const pad = (item) => String(item).padStart(2, "0"); return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`; } function formatHistoryDate(value) { if (value === null || value === undefined || value === "") return ""; const raw = String(value).trim(); if (/^\d{10,13}$/.test(raw)) { const number = Number(raw); const date = new Date(raw.length === 10 ? number * 1000 : number); if (!Number.isNaN(date.getTime())) return formatHistoryDateParts(date); } const text = raw.replace("T", " "); return text.length > 16 ? text.slice(0, 16) : text; } function rawHistoryTime(item) { const transferCandidates = [item?.transferTime, item?.transferredAt, item?.transferAt, item?.outTime]; const receiveCandidates = [item?.receiveTime, item?.receivedAt, item?.receiveAt, item?.incomeTime]; const commonCandidates = [item?.createTime, item?.createdAt, item?.time, item?.timestamp, item?.updateTime, item?.updatedAt]; const candidates = Number(item?.type) === 1 ? transferCandidates.concat(commonCandidates) : receiveCandidates.concat(commonCandidates); return candidates.find((candidate) => candidate !== null && candidate !== undefined && candidate !== "") || ""; } function historyTimeLabel(item) { return Number(item?.type) === 1 ? message("transfer_time", "Transfer time") : message("receive_time", "Receive time"); } function formatBillPeriod(value) { const text = String(value || ""); if (text.length < 6) return text; return `${text.slice(0, 4)}.${text.slice(4, 6)}`; } function salaryTypeLabel(type) { const value = normalizeSalaryType(type); if (value === "BD_SALARY") return message("bd_salary", "BD salary"); if (value === "BD_LEADER_SALARY") return message("bd_leader_salary", "BD Leader salary"); return message("available_income", "salary"); } function salaryHistoryCode(item) { const candidates = [ item?.salaryEvent, item?.event, item?.status, item?.title, item?.eventDesc, item?.eventDescribe, item?.salaryTypeName ]; for (const candidate of candidates) { const value = String(candidate || "").trim().toUpperCase(); if (value) return value; } return ""; } function isAgencySalaryHistoryItem(item) { const code = salaryHistoryCode(item); const salaryType = normalizeSalaryType(item?.salaryType); if ( code.includes("SETTLEMENT_WAGES_AGENT") || code.includes("SETTLEMENT_WAGES_MEMBER") || code.includes("BD_SALARY_SETTLEMENT") || code.includes("BD_LEADER_SALARY_SETTLEMENT") || code.includes("ADMIN_SALARY_SETTLEMENT") ) { return true; } return code === "ISSUE" && salaryType && salaryType !== "HOST_SALARY"; } function isHostSalaryHistoryItem(item) { if (isAgencySalaryHistoryItem(item)) return false; const code = salaryHistoryCode(item); const salaryType = normalizeSalaryType(item?.salaryType); if (code === "SYSTEM_SETTLEMENT_WAGES" || code === "SYSTEM_AUTOMATIC_SETTLEMENT_WAGES" || code === "SEND_SALARY") { return true; } if (code === "ISSUE" && salaryType === "HOST_SALARY") return true; return code.includes("SETTLEMENT_WAGES") || code.includes("SETTLEMENT WAGES"); } function parseHistoryObject(value) { if (!value) return null; if (typeof value === "object") return value; if (typeof value !== "string") return null; const text = value.trim(); if (!text || !text.startsWith("{")) return null; try { return JSON.parse(text); } catch (error) { return null; } } function cleanHistoryName(value) { const text = String(value || "").trim(); if (!text || text === "null" || text === "undefined" || /^\d+$/.test(text)) return ""; return text; } function cleanHistoryAvatar(value) { const text = String(value || "").trim(); if (!text || text === "null" || text === "undefined" || /^\d+$/.test(text)) return ""; return text; } function historyNameFromObject(source) { const object = parseHistoryObject(source); if (!object) return ""; const keys = [ "providerName", "agencyName", "agentName", "fromUserName", "sourceName", "senderName", "userNickname", "userNickName", "nickName", "nickname", "name", "account" ]; for (const key of keys) { const name = cleanHistoryName(object[key]); if (name) return name; } const nestedKeys = ["provider", "agency", "agent", "fromUser", "sourceUser", "sender", "user", "acceptUser", "acceptUserAccount", "profile"]; for (const key of nestedKeys) { const name = historyNameFromObject(object[key]); if (name) return name; } return ""; } function historyAvatarFromObject(source) { const object = parseHistoryObject(source); if (!object) return ""; const keys = [ "userAvatar", "avatar", "avatarUrl", "avatarURL", "headPic", "headImg", "portrait", "photo", "profileImage", "image" ]; for (const key of keys) { const avatar = cleanHistoryAvatar(object[key]); if (avatar) return avatar; } const nestedKeys = ["provider", "agency", "agent", "fromUser", "sourceUser", "sender", "user", "acceptUser", "acceptUserAccount", "profile"]; for (const key of nestedKeys) { const avatar = historyAvatarFromObject(object[key]); if (avatar) return avatar; } return ""; } function agencySalaryProviderName(item) { return historyNameFromObject(item) || historyNameFromObject(item?.details) || historyNameFromObject(item?.detail) || historyNameFromObject(item?.raw) || "Agency"; } function agencySalaryProviderAvatar(item) { return historyAvatarFromObject(item) || historyAvatarFromObject(item?.details) || historyAvatarFromObject(item?.detail) || historyAvatarFromObject(item?.raw) || ""; } function agencySalaryDescription(item) { if (!isAgencySalaryHistoryItem(item)) return ""; return `${agencySalaryProviderName(item)} provided you $${formatMoney(item.amount)} salary`; } function createAgencySalaryDescription(item) { const text = agencySalaryDescription(item); if (!text) return null; const name = agencySalaryProviderName(item); const avatarURL = agencySalaryProviderAvatar(item); const description = document.createElement("div"); description.className = "salary-history-item-desc"; const avatar = document.createElement("span"); avatar.className = "salary-history-desc-avatar"; if (avatarURL) { const image = document.createElement("img"); image.src = avatarURL; image.alt = ""; avatar.appendChild(image); } else { avatar.textContent = (name.slice(0, 1) || "A").toUpperCase(); } const copy = document.createElement("span"); copy.className = "salary-history-item-desc-text"; copy.textContent = text; description.append(avatar, copy); return description; } function shouldShowHistoryStatus(item) { return Boolean(item.status && !isAgencySalaryHistoryItem(item) && !isHostSalaryHistoryItem(item)); } function historyItemTitle(item) { if (isAgencySalaryHistoryItem(item)) return "Agency salary"; if (isHostSalaryHistoryItem(item)) return "Host salary"; return item.title || item.eventDesc || item.eventDescribe || item.salaryTypeName || item.salaryEvent || item.event || message("work_report", "Work report"); } function createSalaryHistoryItem(item) { const row = document.createElement("article"); row.className = "salary-history-item"; const main = document.createElement("div"); main.className = "salary-history-item-main"; const title = document.createElement("div"); title.className = "salary-history-item-title"; title.textContent = historyItemTitle(item); const description = createAgencySalaryDescription(item); const meta = document.createElement("div"); meta.className = "salary-history-item-meta"; const metaItems = []; if (item.date) metaItems.push(`${historyTimeLabel(item)}: ${item.date}`); if (item.period) metaItems.push(`${message("period", "Period")}: ${item.period}`); if (item.target) metaItems.push(`${message("target", "Target")}: ${item.target}`); if (item.days) metaItems.push(`${message("days", "Days")}: ${item.days}`); if (item.balance !== undefined && item.balance !== null && item.balance !== "") { metaItems.push(`${message("balance", "Balance")}: $${formatMoney(item.balance)}`); } meta.textContent = metaItems.join(" "); main.append(title); if (description) main.appendChild(description); main.appendChild(meta); const isOut = Number(item.type) === 1; const amount = document.createElement("div"); amount.className = `salary-history-amount${isOut ? " is-out" : ""}`; amount.textContent = `${isOut ? "-" : "+"}$${formatMoney(item.amount)}`; row.append(main, amount); if (shouldShowHistoryStatus(item)) { const status = document.createElement("div"); status.className = "salary-history-status"; status.textContent = item.status; row.appendChild(status); } return row; } function renderSalaryHistory() { if (salaryHistoryTotalNode) salaryHistoryTotalNode.textContent = `$${formatMoney(salaryHistoryTotal)}`; if (!salaryHistoryList) return; if (isSalaryHistoryLoading) { const loading = document.createElement("div"); loading.className = "salary-history-empty"; loading.textContent = message("loading", "Loading..."); salaryHistoryList.replaceChildren(loading); return; } if (!salaryHistoryItems.length) { const empty = document.createElement("div"); empty.className = "salary-history-empty"; empty.textContent = message("no_salary_history", "No salary history"); salaryHistoryList.replaceChildren(empty); return; } salaryHistoryList.replaceChildren(...salaryHistoryItems.map(createSalaryHistoryItem)); } function renderPayee() { if (!payeeResult) return; if (!selectedPayee) { payeeResult.hidden = true; payeeResult.innerHTML = ""; updateSubmitState(); return; } const name = selectedPayee.userNickname || selectedPayee.name || "-"; const account = selectedPayee.ownSpecialId?.account || selectedPayee.account || selectedPayee.id || "-"; const avatar = selectedPayee.userAvatar || selectedPayee.avatar || ""; payeeResult.hidden = false; payeeResult.innerHTML = `
${avatar ? `` : escapeHTML(name.slice(0, 1) || "U")}
${escapeHTML(name)}
${escapeHTML(message("uid_prefix", "UID:"))} ${escapeHTML(account)}
`; updateSubmitState(); } function escapeHTML(value) { return String(value || "").replace(/[&<>"']/g, (char) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[char]); } async function fetchBalance() { try { if (pageIdentity === "BD_LEADER") { const salaryTypes = ["BD_SALARY", "BD_LEADER_SALARY"]; const results = await Promise.allSettled( salaryTypes.map((type) => requestJSON(`/wallet/salary-account/balance?salaryType=${encodeURIComponent(type)}`)) ); salaryBalances = {}; results.forEach((result, index) => { const type = salaryTypes[index]; salaryBalances[type] = result.status === "fulfilled" ? responseBody(result.value) || {} : {}; if (result.status === "rejected") console.warn(`Failed to load ${type} balance:`, result.reason); }); if (results.every((result) => result.status === "rejected")) throw results[0].reason; if (!currentSalaryType) currentSalaryType = "BD_LEADER_SALARY"; renderBalance(salaryBalanceValue(currentSalaryType)); return; } if (isSalaryAccountMode()) { const data = await requestJSON(`/wallet/salary-account/balance?salaryType=${encodeURIComponent(currentSalaryType)}`); const body = responseBody(data) || {}; salaryBalances = { [currentSalaryType]: body }; renderBalance(body.availableBalance ?? body.balance ?? body.currentSalary ?? 0); return; } const data = await requestJSON("/wallet/bank/balance"); const body = responseBody(data) ?? 0; renderBalance(typeof body === "number" ? body : body.currentSalary ?? body.availableBalance ?? body.balance ?? 0); } catch (error) { console.error("Failed to load balance:", error); renderBalance(0); showToast("load_balance_failed", error.message); } } async function fetchUserProfile() { try { const data = await requestJSON("/team/member/profile"); currentProfile = data.body ?? data.data ?? null; } catch (error) { console.error("Failed to load user profile:", error); currentProfile = null; } } async function fetchSalaryAccountHistory() { if (!currentProfile) await fetchUserProfile(); const query = new URLSearchParams({ salaryType: activeSalaryType(), current: "1", size: "20" }); const userId = currentUserId(); if (userId) query.set("userId", String(userId)); const data = await requestJSON(`/wallet/salary-account/running-water?${query.toString()}`); const body = responseBody(data) || data || {}; const records = Array.isArray(body.records) ? body.records : Array.isArray(body.list) ? body.list : []; return { total: availableBalance, items: records.map((item) => ({ id: item.id, title: item.eventDesc || item.salaryTypeName || salaryTypeLabel(item.salaryType), eventDesc: item.eventDesc, eventDescribe: item.eventDescribe, salaryEvent: item.salaryEvent, event: item.event || item.salaryEvent, salaryType: item.salaryType || activeSalaryType(), salaryTypeName: item.salaryTypeName, details: item.details || item.detail, detail: item.detail, raw: item, amount: item.amount, balance: item.availableBalance ?? item.balance, type: item.type, status: item.salaryEvent || "", period: item.billBelong ? formatBillPeriod(item.billBelong) : "", date: formatHistoryDate(rawHistoryTime(item)) })) }; } async function fetchBankWaterHistory() { const data = await requestJSON("/wallet/bank/balance/water/details"); const records = Array.isArray(responseBody(data)) ? responseBody(data) : Array.isArray(data) ? data : []; return { total: availableBalance, items: records.map((item) => ({ id: item.id, title: item.eventDescribe || item.event || message("salary_history", "Salary history"), event: item.event, eventDescribe: item.eventDescribe, details: item.details || item.detail, detail: item.detail, raw: item, amount: item.amount, balance: item.balance, type: item.type, status: item.event || "", date: formatHistoryDate(rawHistoryTime(item)) })) }; } async function fetchSalaryHistory() { if (isSalaryAccountMode()) return fetchSalaryAccountHistory(); return fetchBankWaterHistory(); } async function openSalaryHistory() { salaryHistoryLoadedType = activeSalaryType(); salaryHistoryTotal = availableBalance; salaryHistoryItems = []; isSalaryHistoryLoading = true; renderSalaryHistory(); setSalaryHistoryModalOpen(true); try { const result = await fetchSalaryHistory(); salaryHistoryTotal = moneyNumber(result.total ?? availableBalance); salaryHistoryItems = Array.isArray(result.items) ? result.items : []; } catch (error) { console.error("Failed to load salary history:", error); salaryHistoryItems = []; showToast(error.message || message("load_salary_history_failed", "Failed to load salary history")); } finally { isSalaryHistoryLoading = false; renderSalaryHistory(); } } async function fetchPaymentInfo() { try { const data = await requestJSON("/wallet/bank-card/list"); const cards = Array.isArray(data.body) ? data.body : Array.isArray(data.data) ? data.data : []; const usdtCards = cards.filter((item) => item.cardType === "USDT"); renderPaymentInfo(usdtCards.find((item) => item.use) || usdtCards[0] || null); } catch (error) { console.error("Failed to load payment info:", error); renderPaymentInfo(null); showToast("load_payment_failed", error.message); } } function normalizeAmountInput(event) { const input = event?.target || activeInput(); if (!input) return; const raw = String(input.value || "").replace(/[^\d.]/g, ""); const parts = raw.split("."); let nextValue = parts.length > 2 ? `${parts[0]}.${parts.slice(1).join("")}` : raw; const amount = Number(nextValue); if (Number.isFinite(amount) && amount > availableBalance) nextValue = formatMoneyInput(availableBalance); input.value = nextValue; updateSubmitState(); } function validateAmount(requireMinimum) { const amount = activeAmount(); if (!amount || amount <= 0) return "invalid_amount"; if (requireMinimum && amount < WITHDRAW_MIN_AMOUNT) return "amount_too_low"; if (amount > availableBalance) return "amount_exceeds_balance"; return ""; } async function saveAddress() { const address = String(trc20AddressInput?.value || "").trim(); if (!isValidTrc20Address(address)) { showToast("invalid_address"); return; } if (!currentProfile) await fetchUserProfile(); isSavingAddress = true; if (saveAddressButton) { saveAddressButton.disabled = true; saveAddressButton.textContent = message("saving", "Saving..."); } try { const data = await requestJSON("/wallet/bank-card/add", { method: "POST", body: { userId: currentProfile?.id || currentProfile?.userId || "", cardType: "USDT", cardName: "USDT", cardNo: address, payee: "USDT-TRC20" } }); const created = data.body || data.data || null; if (created?.id) { renderPaymentInfo({ ...created, cardType: "USDT", cardNo: address }); await requestJSON("/wallet/bank-card/use", { method: "POST", body: { id: created.id } }).catch((error) => console.warn("Failed to set USDT address in use:", error)); } await fetchPaymentInfo(); showToast("address_saved"); } catch (error) { console.error("Failed to save TRC20 address:", error); showToast(error.message || message("load_payment_failed", "Failed to load payment info")); } finally { isSavingAddress = false; if (saveAddressButton) { saveAddressButton.disabled = false; saveAddressButton.textContent = message("save_address", "Save address"); } } } async function searchPayee() { const account = String(transferAccountInput?.value || "").trim(); if (!account) { showToast("missing_payee"); return; } selectedPayee = null; renderPayee(); isSearchingPayee = true; if (searchPayeeButton) { searchPayeeButton.disabled = true; searchPayeeButton.textContent = message("searching", "Searching..."); } try { const path = `/wallet/bank/search/user-profile?account=${encodeURIComponent(account)}&type=AGENT&searchType=TRANSFER`; const data = await requestJSON(path); const body = data.body ?? data.data ?? null; if (!body?.id) { showToast("payee_not_found"); return; } selectedPayee = body; renderPayee(); } catch (error) { console.error("Failed to search payee:", error); showToast(error.message || message("payee_not_found", "User not found")); } finally { isSearchingPayee = false; if (searchPayeeButton) { searchPayeeButton.disabled = false; searchPayeeButton.textContent = message("search", "Search"); } } } function validateWithdraw() { if (!selectedPaymentInfo?.id) return "missing_payment_info"; return validateAmount(true); } function validateTransfer() { if (!selectedPayee?.id) return "missing_payee"; return validateAmount(false); } async function submitWithdraw() { const errorKey = validateWithdraw(); if (errorKey) { showToast(errorKey); return; } await requestJSON("/wallet/bank/withdraw", { method: "POST", body: { amount: activeAmount(), acceptBankCardId: selectedPaymentInfo.id, salaryType: activeSalaryType() } }); showToast("withdraw_success"); } async function submitExchange() { const errorKey = validateAmount(false); if (errorKey) { showToast(errorKey); return; } await requestJSON(isSalaryAccountMode() ? "/wallet/salary-account/exchange/gold" : "/wallet/bank/exchange/gold", { method: "POST", body: isSalaryAccountMode() ? { amount: activeAmount(), salaryType: activeSalaryType() } : { amount: activeAmount() } }); showToast("exchange_success"); } async function submitTransfer() { const errorKey = validateTransfer(); if (errorKey) { showToast(errorKey); return; } await requestJSON(isSalaryAccountMode() ? "/wallet/salary-account/transferGold" : "/wallet/bank/transferGold", { method: "POST", body: isSalaryAccountMode() ? { amount: activeAmount(), acceptUserId: selectedPayee.id, salaryType: activeSalaryType() } : { amount: activeAmount(), acceptUserId: selectedPayee.id, acceptMethod: "BANK_TRANSFER" } }); showToast("transfer_success"); } async function submitCurrentMode() { if (isSubmitting) return; isSubmitting = true; updateSubmitState(); try { if (currentMode === "withdraw") await submitWithdraw(); if (currentMode === "exchange") await submitExchange(); if (currentMode === "transfer") await submitTransfer(); const input = activeInput(); if (input) input.value = ""; await fetchBalance(); } catch (error) { console.error(`Failed to submit ${currentMode}:`, error); const fallback = { withdraw: "withdraw_failed", exchange: "exchange_failed", transfer: "transfer_failed" }[currentMode]; showToast(error.message || message(fallback, "Submit failed")); } finally { isSubmitting = false; updateSubmitState(); } } function syncBackFallback() { document.querySelector(".back-button")?.addEventListener("click", () => { if (window.history.length > 1) { window.history.back(); return; } const nextParams = new URLSearchParams(params); const token = readRawParam("token"); if (token) nextParams.set("token", token); if (currentLang) nextParams.set("lang", currentLang); const query = nextParams.toString(); const fallbackPage = pageIdentity === "BD_LEADER" ? "../bd-leader-center/index.html" : "../host-center/index.html"; window.location.href = `${fallbackPage}${query ? `?${query}` : ""}`; }); } function setLanguageMenuOpen(open) { const languageButton = document.querySelector(".language-button"); const languageMenu = document.querySelector(".language-menu"); if (!languageButton || !languageMenu) return; languageMenu.hidden = !open; languageButton.setAttribute("aria-expanded", String(open)); } document.querySelector(".language-button")?.addEventListener("click", (event) => { event.stopPropagation(); const languageMenu = document.querySelector(".language-menu"); setLanguageMenuOpen(Boolean(languageMenu?.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.querySelectorAll(".mode-tab").forEach((tab) => { tab.addEventListener("click", () => setMode(tab.dataset.mode)); }); document.querySelectorAll("[data-salary-type]").forEach((button) => { button.addEventListener("click", () => setActiveSalaryType(button.dataset.salaryType)); }); document.querySelector(".text-action")?.addEventListener("click", () => { if (inputs.withdraw) inputs.withdraw.value = formatMoneyInput(availableBalance); updateSubmitState(); }); Object.values(inputs).forEach((input) => input?.addEventListener("input", normalizeAmountInput)); transferAccountInput?.addEventListener("input", () => { selectedPayee = null; renderPayee(); }); transferAccountInput?.addEventListener("keydown", (event) => { if (event.key === "Enter") searchPayee(); }); searchPayeeButton?.addEventListener("click", searchPayee); saveAddressButton?.addEventListener("click", saveAddress); submitButton?.addEventListener("click", submitCurrentMode); salaryHistoryButton?.addEventListener("click", openSalaryHistory); document.querySelectorAll("[data-close-salary-history]").forEach((node) => { node.addEventListener("click", () => setSalaryHistoryModalOpen(false)); }); syncBackFallback(); applyLanguage(currentLang); setMode(currentMode); fetchUserProfile(); fetchBalance(); fetchPaymentInfo(); })();