(function () { var SELLER_ASSET_TYPE = 'COIN_SELLER_COIN'; var query = new URLSearchParams(window.location.search); var mock = { profile: { name: 'CoinSeller Mina', uid: '163009', region: 'Saudi Arabia', avatar: avatarData('C', '#dbc8ff', '#7d57c7'), }, balance: { coins: 1285000, }, users: [ { uid: '2001', userId: '9002001', name: 'Alice', avatar: avatarData('A', '#dbc8ff', '#7d57c7'), }, { uid: '2002', userId: '9002002', name: 'Bob', avatar: avatarData('B', '#f4ebff', '#7d57c7'), }, { uid: '2003', userId: '9002003', name: 'Nora', avatar: avatarData('N', '#efe4ff', '#7d57c7'), }, ], quickAmounts: [10000, 50000, 100000], records: [ { id: 'tx-001', type: 'income', name: 'Stock transfer', uid: 'System', avatar: avatarData('S', '#e7f7f3', '#31a88f'), coins: 300000, amount: 36, currency: 'USD', time: '2026-06-04 10:30', }, { id: 'tx-002', type: 'expenditure', name: 'Alice', uid: '2001', avatar: avatarData('A', '#dbc8ff', '#7d57c7'), coins: 110000, amount: 12.5, currency: 'USD', time: '2026-06-04 10:10', }, { id: 'tx-003', type: 'expenditure', name: 'Bob', uid: '2002', avatar: avatarData('B', '#f4ebff', '#7d57c7'), coins: 88000, amount: 9.25, currency: 'USD', time: '2026-06-04 09:20', }, { id: 'tx-004', type: 'income', name: 'Manager recharge', uid: 'System', avatar: avatarData('M', '#e7f7f3', '#31a88f'), coins: 180000, amount: 22, currency: 'USD', time: '2026-06-03 18:42', }, ], subSellerPermission: { visible: true, canInvite: true, canTransfer: true, }, subSellers: [ { uid: '7001001', userId: '107001001', name: 'Sub Seller Sara', avatar: avatarData('S', '#efe4ff', '#7d57c7'), status: 'active', balance: 320000, }, { uid: '7001002', userId: '107001002', name: 'Sub Seller Omar', avatar: avatarData('O', '#e7f7f3', '#31a88f'), status: 'active', balance: 180000, }, ], }; var data = { profile: emptyProfile(), balance: { coins: 0 }, users: [], quickAmounts: mock.quickAmounts.slice(), records: [], subSellerPermission: emptySubSellerPermission(), subSellers: [], }; var state = { target: null, coinAmount: '', historyTab: 'income', historyQuery: '', historyLimit: 3, isMock: query.get('mock') === '1', loading: query.get('mock') !== '1', searching: false, submitting: false, subSellerLoading: false, invitingSubSeller: false, subSellerTransferTarget: null, subSellerTransferAmount: '', subSellerTransferSubmitting: false, }; function $(id) { return document.getElementById(id); } function t(key, fallback) { return window.HyAppI18n && window.HyAppI18n.t ? window.HyAppI18n.t(key, fallback) : fallback || key; } function avatarData(text, background, foreground) { var value = String(text || 'C') .trim() .charAt(0) || 'C'; var letter = value.replace(/[<>&"']/g, ''); var svg = '' + '' + '' + letter + '' + ''; return 'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent(svg); } function emptyProfile() { return { name: '', uid: '', region: '', avatar: '', }; } function emptySubSellerPermission() { return { visible: false, canInvite: false, canTransfer: false, }; } function initial(text) { return String(text || '') .trim() .charAt(0) .toUpperCase(); } function count(value) { return Number(value || 0).toLocaleString('en-US', { maximumFractionDigits: 0, }); } function toNumber(value) { var number = Number(value); return Number.isFinite(number) ? number : 0; } function positiveNumber(value) { var number = toNumber(value); return number > 0 ? number : 0; } function booleanValue(value) { if (value === true || value === 1) return true; if (value === false || value === 0) return false; var text = String(value || '') .trim() .toLowerCase(); return text === 'true' || text === '1' || text === 'yes'; } function firstValue(source, keys) { if (!source) return ''; for (var i = 0; i < keys.length; i += 1) { var value = source[keys[i]]; if (value !== undefined && value !== null && value !== '') { return value; } } return ''; } function unwrapData(payload) { if (!payload || typeof payload !== 'object') return payload; return Object.prototype.hasOwnProperty.call(payload, 'data') ? payload.data : payload; } function replaceChildren(node, children) { node.textContent = ''; children.forEach(function (child) { node.appendChild(child); }); } function toast(message) { if (!message) return; if (window.HyAppToast && window.HyAppToast.show) { window.HyAppToast.show(message); return; } window.alert(message); } function setStatus(node, message, type) { node.hidden = !message; node.textContent = message || ''; node.className = 'form-status' + (type ? ' ' + type : ''); } function setSkeleton(element, loading, text) { if (!element) return; element.classList.toggle('skeleton-line', !!loading); if (loading) { element.textContent = ''; return; } element.textContent = text || ''; } function hasAPI() { return ( window.HyAppAPI && window.HyAppAPI.user && window.HyAppAPI.wallet ); } function hasSubSellerAPI() { return ( hasAPI() && window.HyAppAPI.wallet.getCoinSellerSubSellerPermission && window.HyAppAPI.wallet.listCoinSellerSubSellers ); } function setImage(image, fallback, src, name) { if (!image || !fallback) return; fallback.textContent = initial(name) || 'C'; if (!src) { image.hidden = true; image.removeAttribute('src'); fallback.hidden = false; return; } image.onload = function () { image.hidden = false; fallback.hidden = true; }; image.onerror = function () { image.hidden = true; fallback.hidden = false; }; image.src = src; } function renderAvatar(container, item) { container.textContent = ''; if (item.avatar) { var image = document.createElement('img'); image.src = item.avatar; image.alt = ''; image.addEventListener('error', function () { container.textContent = initial(item.name || item.uid) || 'U'; }); container.appendChild(image); return; } container.textContent = initial(item.name || item.uid) || 'U'; } function normalizeProfile(payload) { var source = unwrapData(payload) || {}; return { name: firstValue(source, [ 'username', 'nickname', 'name', 'display_user_id', 'displayUserID', 'user_id', 'userId', ]) || '-', uid: firstValue(source, [ 'display_user_id', 'displayUserID', 'displayUserId', 'user_id', 'userId', ]) || '-', region: firstValue(source, [ 'country_display_name', 'countryDisplayName', 'country_name', 'countryName', 'region_name', 'regionName', 'country', 'region', ]) || '', avatar: firstValue(source, [ 'avatar', 'avatar_url', 'avatarUrl', 'profile_picture_url', 'profilePictureUrl', ]) || '', }; } function normalizeBalance(payload) { var source = unwrapData(payload) || {}; var balances = Array.isArray(source.balances) ? source.balances : Array.isArray(source) ? source : []; var matched = balances.find(function (item) { return item && item.asset_type === SELLER_ASSET_TYPE; }) || balances.find(function (item) { return item && item.assetType === SELLER_ASSET_TYPE; }) || balances[0] || {}; return { coins: toNumber( firstValue(matched, [ 'available_amount', 'availableAmount', 'available', 'amount', ]) ), }; } function timeLabelFromMS(value) { var number = Number(value || 0); if (!Number.isFinite(number) || number <= 0) return ''; var date = new Date(number); if (Number.isNaN(date.getTime())) return ''; return ( date.getFullYear() + '-' + String(date.getMonth() + 1).padStart(2, '0') + '-' + String(date.getDate()).padStart(2, '0') + ' ' + String(date.getHours()).padStart(2, '0') + ':' + String(date.getMinutes()).padStart(2, '0') ); } function normalizeTransferAmount(item) { // 转账美金来自服务端账务 metadata;不同入账来源汇率可能不同,前端不能用金币数反推,缺字段时保持不展示。 var minor = positiveNumber( firstValue(item, [ 'transfer_usd_minor', 'transferUsdMinor', 'salary_usd_minor', 'salaryUsdMinor', 'recharge_usd_minor', 'rechargeUsdMinor', 'usd_minor_amount', 'usdMinorAmount', ]) ); if (minor > 0) return minor / 100; var micro = positiveNumber( firstValue(item, [ 'paid_amount_micro', 'paidAmountMicro', 'amount_usdt_micro', 'amountUsdtMicro', ]) ); if (micro > 0) return micro / 1000000; return positiveNumber( firstValue(item, [ 'amount_usd', 'amountUsd', 'transfer_amount_usd', 'transferAmountUsd', ]) ); } function normalizeTransferCurrency(item, amount) { return amount > 0 ? String( firstValue(item, [ 'transfer_currency_code', 'transferCurrencyCode', 'recharge_currency_code', 'rechargeCurrencyCode', 'paid_currency_code', 'paidCurrencyCode', 'currency_code', 'currencyCode', ]) || 'USD' ).toUpperCase() : ''; } function formatTransferAmount(amount, currency) { if (!amount || amount <= 0) return ''; var code = String(currency || 'USD').toUpperCase(); var value = Number(amount).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2, }); return code + ' ' + value; } function normalizeResolvedUser(payload, keyword) { var source = unwrapData(payload) || {}; var identity = source.identity || source.user_identity || source; var profile = source.profile || source.user || source; var displayID = firstValue(identity, [ 'display_user_id', 'displayUserID', 'displayUserId', 'display_id', 'displayId', ]) || firstValue(profile, [ 'display_user_id', 'displayUserID', 'displayUserId', 'display_id', 'displayId', ]) || String(keyword || '').trim(); var userID = firstValue(identity, ['user_id', 'userId', 'id']) || firstValue(profile, ['user_id', 'userId', 'id']) || ''; return { uid: displayID, userId: userID, name: firstValue(profile, ['username', 'nickname', 'name']) || firstValue(identity, ['username', 'nickname', 'name']) || t('coinseller_center.user_name_template', 'User {id}').replace( '{id}', displayID ), avatar: firstValue(profile, [ 'avatar', 'avatar_url', 'avatarUrl', 'profile_picture_url', 'profilePictureUrl', ]) || firstValue(identity, [ 'avatar', 'avatar_url', 'avatarUrl', 'profile_picture_url', 'profilePictureUrl', ]) || '', }; } function normalizeTransactionRecord(item, index) { var delta = toNumber( firstValue(item, ['available_delta', 'availableDelta']) ); var type = delta >= 0 ? 'income' : 'expenditure'; var counterpartyUserID = String( firstValue(item, ['counterparty_user_id', 'counterpartyUserId']) || '' ); var counterpartyDisplayID = String( firstValue(item, [ 'counterparty_display_user_id', 'counterpartyDisplayUserId', 'counterpartyDisplayUserID', ]) || '' ); var counterpartyName = firstValue(item, [ 'counterparty_username', 'counterpartyUsername', 'counterparty_name', 'counterpartyName', ]); var counterpartyAvatar = firstValue(item, [ 'counterparty_avatar', 'counterpartyAvatar', ]); var counterpartyUID = counterpartyDisplayID || counterpartyUserID; // 入账流水也可能来自某个用户的工资转币商,标题必须优先展示对方资料;只有后台进货这类没有对方用户的流水才展示库存入账。 var name = counterpartyName || (counterpartyUID ? t( 'coinseller_center.user_name_template', 'User {id}' ).replace('{id}', counterpartyUID) : type === 'income' ? t('coinseller_center.stock_transfer', 'Stock transfer') : t('coinseller_center.transfer_out', 'Transfer out')); var uid = counterpartyUID || (type === 'income' ? t('coinseller_center.system', 'System') : '-'); var transferAmount = normalizeTransferAmount(item); return { id: firstValue(item, ['transaction_id', 'transactionId']) || String(firstValue(item, ['entry_id', 'entryId']) || index), type: type, name: name, uid: uid, avatar: counterpartyAvatar || avatarData( name, type === 'income' ? '#e7f7f3' : '#dbc8ff', type === 'income' ? '#31a88f' : '#7d57c7' ), coins: Math.abs(delta), amount: transferAmount, currency: normalizeTransferCurrency(item, transferAmount), time: timeLabelFromMS( firstValue(item, ['created_at_ms', 'createdAtMs']) ), }; } function normalizeTransactions(payload) { var source = unwrapData(payload) || {}; var items = Array.isArray(source.items) ? source.items : Array.isArray(source.transactions) ? source.transactions : Array.isArray(source) ? source : []; return items.map(function (item, index) { return normalizeTransactionRecord(item || {}, index); }); } function normalizeSubSellerPermission(payload) { var source = unwrapData(payload) || {}; var canInvite = booleanValue( firstValue(source, [ 'can_invite_sub_seller', 'canInviteSubSeller', 'can_open_sub_seller', 'canOpenSubSeller', 'can_invite', 'canInvite', ]) ); var canTransfer = booleanValue( firstValue(source, [ 'can_transfer_sub_seller', 'canTransferSubSeller', 'can_transfer', 'canTransfer', ]) ); var canList = booleanValue( firstValue(source, [ 'can_list_sub_sellers', 'canListSubSellers', 'can_list', 'canList', ]) ); var explicitVisible = firstValue(source, [ 'visible', 'enabled', 'allowed', 'has_permission', 'hasPermission', ]); return { visible: explicitVisible === '' || explicitVisible === undefined ? canInvite || canTransfer || canList : booleanValue(explicitVisible), canInvite: canInvite, canTransfer: canTransfer, }; } function normalizeSubSeller(item, index) { var source = item || {}; var displayID = firstValue(source, [ 'display_user_id', 'displayUserID', 'displayUserId', 'display_id', 'displayId', ]) || ''; var userID = firstValue(source, ['user_id', 'userId', 'id']) || ''; var name = firstValue(source, ['username', 'nickname', 'name']) || t('coinseller_center.user_name_template', 'User {id}').replace( '{id}', displayID || userID || index + 1 ); return { uid: displayID || userID, userId: userID, name: name, avatar: firstValue(source, [ 'avatar', 'avatar_url', 'avatarUrl', 'profile_picture_url', 'profilePictureUrl', ]) || '', status: firstValue(source, ['status']) || '', balance: toNumber( firstValue(source, [ 'coin_seller_balance', 'coinSellerBalance', 'available_amount', 'availableAmount', 'balance', 'amount', ]) ), region: firstValue(source, [ 'country_display_name', 'countryDisplayName', 'region_name', 'regionName', 'country_name', 'countryName', ]) || '', }; } function normalizeSubSellers(payload) { var source = unwrapData(payload) || {}; var items = Array.isArray(source.items) ? source.items : Array.isArray(source.sub_sellers) ? source.sub_sellers : Array.isArray(source.subSellers) ? source.subSellers : Array.isArray(source.children) ? source.children : Array.isArray(source) ? source : []; return items .map(function (item, index) { return normalizeSubSeller(item, index); }) .filter(function (item) { var status = String(item.status || '').toLowerCase(); // 列表接口的服务端契约只返回 active 直属关系;这里再过滤一次,避免异常数据把 pending 申请展示成可转账子币商。 return !status || status === 'active'; }); } function applyMockData() { data.profile = Object.assign({}, mock.profile); data.balance = Object.assign({}, mock.balance); data.users = mock.users.slice(); data.quickAmounts = mock.quickAmounts.slice(); data.records = mock.records.slice(); data.subSellerPermission = Object.assign({}, mock.subSellerPermission); data.subSellers = mock.subSellers.slice(); } function applyEmptyData() { data.profile = emptyProfile(); data.balance = { coins: 0 }; data.users = []; data.quickAmounts = mock.quickAmounts.slice(); data.records = []; data.subSellerPermission = emptySubSellerPermission(); data.subSellers = []; } function loadSubSellerCapability() { if (!hasSubSellerAPI()) { data.subSellerPermission = emptySubSellerPermission(); data.subSellers = []; return Promise.resolve(); } state.subSellerLoading = true; renderSubSellers(); return window.HyAppAPI.wallet .getCoinSellerSubSellerPermission() .then(function (response) { data.subSellerPermission = normalizeSubSellerPermission(response); // 子币商列表属于权限增强区;只有服务端明确给到能力时才拉取,避免普通币商看到空功能入口。 if (!data.subSellerPermission.visible) { data.subSellers = []; return null; } return window.HyAppAPI.wallet .listCoinSellerSubSellers({ page: 1, page_size: 50, }) .then(function (listResponse) { data.subSellers = normalizeSubSellers(listResponse); }); }) .catch(function (error) { if (window.console && window.console.warn) { window.console.warn( '[coinseller-center] sub seller api failed', error ); } data.subSellerPermission = emptySubSellerPermission(); data.subSellers = []; }) .finally(function () { state.subSellerLoading = false; renderSubSellers(); }); } function loadRealData() { if (!hasAPI()) { toast( t( 'coinseller_center.load_failed', 'Load failed. Try again later.' ) ); return Promise.resolve(); } state.loading = true; render(); return Promise.all([ window.HyAppAPI.user.me(), window.HyAppAPI.wallet.balances(SELLER_ASSET_TYPE), window.HyAppAPI.wallet.transactions ? window.HyAppAPI.wallet .transactions({ asset_type: SELLER_ASSET_TYPE, page: 1, page_size: 20, }) .catch(function (error) { if (window.console && window.console.warn) { window.console.warn( '[coinseller-center] records api failed', error ); } return null; }) : Promise.resolve(null), loadSubSellerCapability(), ]) .then(function (results) { data.profile = normalizeProfile(results[0]); data.balance = normalizeBalance(results[1]); data.records = normalizeTransactions(results[2]); }) .catch(function (error) { if (window.console && window.console.warn) { window.console.warn( '[coinseller-center] api failed', error ); } toast( t( 'coinseller_center.load_failed', 'Load failed. Try again later.' ) ); }) .finally(function () { state.loading = false; render(); }); } function renderProfile() { var image = $('profileAvatar'); var fallback = $('profileAvatarFallback'); var profile = data.profile; var loading = !!state.loading; setSkeleton($('profileName'), loading, profile.name || ''); setSkeleton( $('profileUID'), loading, profile.uid ? t('coinseller_center.uid_prefix', 'UID: {id}').replace( '{id}', profile.uid ) : '' ); fallback.classList.toggle('avatar-skeleton', loading); if (loading) { image.hidden = true; image.removeAttribute('src'); fallback.hidden = false; fallback.textContent = ''; return; } setImage(image, fallback, profile.avatar, profile.name || profile.uid); } function renderBalance() { setSkeleton($('coinBalance'), state.loading, count(data.balance.coins)); var coinIcon = document.querySelector('.balance-value .coin-icon'); if (coinIcon) { coinIcon.classList.toggle('skeleton-coin', !!state.loading); } } function createSubSellerRow(item) { var row = document.createElement('article'); var avatar = document.createElement('div'); var main = document.createElement('div'); var title = document.createElement('div'); var meta = document.createElement('div'); var balance = document.createElement('div'); var action = document.createElement('button'); row.className = 'sub-seller-row'; avatar.className = 'record-avatar'; main.className = 'sub-seller-main'; title.className = 'record-title'; meta.className = 'record-meta'; balance.className = 'sub-seller-balance'; action.className = 'sub-seller-transfer'; action.type = 'button'; action.disabled = !data.subSellerPermission.canTransfer || state.subSellerTransferSubmitting; action.textContent = t('coinseller_center.transfer', 'Transfer'); renderAvatar(avatar, item); title.textContent = item.name || '-'; meta.textContent = t( 'coinseller_center.uid_prefix', 'UID: {id}' ).replace('{id}', item.uid || '-'); balance.textContent = t('coinseller_center.sub_seller_wallet', 'Wallet') + ': ' + count(item.balance) + ' ' + t('coinseller_center.coins_lower', 'coins'); action.addEventListener('click', function () { openSubSellerTransfer(item); }); main.appendChild(title); main.appendChild(meta); if (item.balance > 0) main.appendChild(balance); row.appendChild(avatar); row.appendChild(main); if (data.subSellerPermission.canTransfer) row.appendChild(action); return row; } function renderSubSellers() { var section = $('subSellerSection'); if (!section) return; var permission = data.subSellerPermission || emptySubSellerPermission(); section.hidden = !permission.visible; $('inviteSubSellerButton').hidden = !permission.canInvite; if (!permission.visible) return; if (state.subSellerLoading) { setStatus( $('subSellerStatus'), t('coinseller_center.loading', 'Loading...'), '' ); } else { setStatus( $('subSellerStatus'), data.subSellers.length ? '' : t( 'coinseller_center.no_sub_sellers', 'No sub coin sellers yet.' ), data.subSellers.length ? '' : 'warning' ); } replaceChildren( $('subSellerList'), data.subSellers.map(function (item) { return createSubSellerRow(item); }) ); } function renderQuickAmounts() { replaceChildren( $('quickAmounts'), data.quickAmounts.map(function (amount) { var button = document.createElement('button'); button.className = 'quick-amount' + (String(amount) === state.coinAmount ? ' is-active' : ''); button.type = 'button'; button.textContent = count(amount); button.addEventListener('click', function () { state.coinAmount = String(amount); $('coinAmountInput').value = state.coinAmount; setStatus($('rechargeStatus'), '', ''); renderRechargeState(); }); return button; }) ); } function renderTarget() { var card = $('targetCard'); if (!state.target) { card.hidden = true; $('rechargeTargetName').textContent = '-'; return; } card.hidden = false; renderAvatar($('targetAvatar'), state.target); $('targetName').textContent = state.target.name; $('targetUID').textContent = t( 'coinseller_center.uid_prefix', 'UID: {id}' ).replace('{id}', state.target.uid); $('rechargeTargetName').textContent = t('coinseller_center.target_user', 'Target user') + ': ' + state.target.name; } function parsedCoinAmount() { var input = $('coinAmountInput'); if (input) { var sanitized = String(input.value || '').replace(/[^\d]/g, ''); if (input.value !== sanitized) input.value = sanitized; state.coinAmount = sanitized; } return Number(state.coinAmount || 0); } function renderRechargeState() { var amount = parsedCoinAmount(); var disabled = state.loading || state.submitting || state.searching || !state.target || amount <= 0; $('rechargeButton').disabled = disabled; $('searchButton').disabled = state.searching; renderQuickAmounts(); } function recordMatchesQuery(record) { if (!state.historyQuery) return true; var keyword = state.historyQuery.toLowerCase(); return ( String(record.name || '') .toLowerCase() .indexOf(keyword) >= 0 || String(record.uid || '') .toLowerCase() .indexOf(keyword) >= 0 ); } function visibleHistoryRecords() { return data.records .filter(function (record) { return ( record.type === state.historyTab && recordMatchesQuery(record) ); }) .slice(0, state.historyLimit); } function createRecordRow(record) { var row = document.createElement('article'); var avatar = document.createElement('div'); var main = document.createElement('div'); var title = document.createElement('div'); var metaGroup = document.createElement('div'); var meta = document.createElement('div'); var transfer = document.createElement('span'); var side = document.createElement('div'); var amount = document.createElement('strong'); var time = document.createElement('span'); row.className = 'record-row'; avatar.className = 'record-avatar'; main.className = 'record-main'; title.className = 'record-title'; metaGroup.className = 'record-meta-group'; meta.className = 'record-meta'; transfer.className = 'record-transfer'; side.className = 'record-side' + (record.type === 'income' ? ' is-income' : ''); renderAvatar(avatar, record); title.textContent = record.name || '-'; meta.textContent = t( 'coinseller_center.uid_prefix', 'UID: {id}' ).replace('{id}', record.uid || '-'); transfer.textContent = formatTransferAmount( record.amount, record.currency ); amount.textContent = (record.type === 'income' ? '+' : '-') + count(record.coins) + ' ' + t('coinseller_center.coins_lower', 'coins'); time.textContent = record.time || ''; main.appendChild(title); metaGroup.appendChild(meta); if (transfer.textContent) metaGroup.appendChild(transfer); main.appendChild(metaGroup); side.appendChild(amount); side.appendChild(time); row.appendChild(avatar); row.appendChild(main); row.appendChild(side); return row; } function renderRecent() { replaceChildren( $('recentList'), data.records.slice(0, 3).map(function (record) { return createRecordRow(record); }) ); } function renderHistory() { document.querySelectorAll('[data-history-tab]').forEach(function (tab) { var active = tab.getAttribute('data-history-tab') === state.historyTab; tab.classList.toggle('is-active', active); tab.setAttribute('aria-selected', String(active)); }); var all = data.records.filter(function (record) { return ( record.type === state.historyTab && recordMatchesQuery(record) ); }); var records = visibleHistoryRecords(); $('loadMoreButton').hidden = records.length >= all.length; setStatus( $('historyStatus'), all.length ? '' : t( 'coinseller_center.no_transaction_records', 'No transaction records' ), all.length ? '' : 'warning' ); replaceChildren( $('historyList'), records.map(function (record) { return createRecordRow(record); }) ); } function findMockUser(keyword) { var normalized = String(keyword || '') .trim() .toLowerCase(); return data.users.find(function (user) { return ( String(user.uid || '') .toLowerCase() .indexOf(normalized) >= 0 || String(user.name || '') .toLowerCase() .indexOf(normalized) >= 0 ); }); } function findUser(keyword) { if (state.isMock) { return Promise.resolve(findMockUser(keyword) || null); } if (!hasAPI() || !window.HyAppAPI.user.resolveDisplayUserID) { return Promise.reject(new Error('api_not_ready')); } return window.HyAppAPI.user .resolveDisplayUserID(keyword) .then(function (payload) { return normalizeResolvedUser(payload, keyword); }); } function handleSearch(value) { state.searching = true; setStatus( $('searchStatus'), t('coinseller_center.loading', 'Loading...'), '' ); renderRechargeState(); findUser(value) .then(function (user) { if (!user) { state.target = null; renderTarget(); renderRechargeState(); setStatus( $('searchStatus'), t( 'coinseller_center.user_not_found', 'User not found.' ), 'warning' ); return; } state.target = user; renderTarget(); renderRechargeState(); setStatus($('searchStatus'), '', ''); }) .catch(function (error) { if (window.console && window.console.warn) { window.console.warn( '[coinseller-center] user search failed', error ); } state.target = null; renderTarget(); renderRechargeState(); setStatus( $('searchStatus'), t('coinseller_center.user_not_found', 'User not found.'), 'warning' ); }) .finally(function () { state.searching = false; renderRechargeState(); }); } function commandID() { return [ 'coin-seller-transfer', Date.now(), Math.random().toString(36).slice(2, 8), ].join('-'); } function subSellerCommandID(action) { return [ 'coin-seller-sub-seller', action || 'action', Date.now(), Math.random().toString(36).slice(2, 8), ].join('-'); } function buildTransferPayload(amount) { return { command_id: commandID(), target_display_user_id: String(state.target.uid || '').trim(), amount: amount, reason: 'player recharge', }; } function appendTransferRecord(amount, response) { var payload = unwrapData(response) || {}; var coins = toNumber( firstValue(payload, ['amount', 'coin_amount', 'coinAmount']) ); var transferAmount = normalizeTransferAmount(payload); data.records.unshift({ id: firstValue(payload, ['transaction_id', 'transactionId']) || 'tx-' + Date.now(), type: 'expenditure', name: state.target.name, uid: state.target.uid, avatar: state.target.avatar, coins: coins || amount, amount: transferAmount, currency: normalizeTransferCurrency(payload, transferAmount), time: currentTimeLabel(), }); } function appendSubSellerTransferRecord(amount, response) { var target = state.subSellerTransferTarget; if (!target) return; var payload = unwrapData(response) || {}; var coins = toNumber( firstValue(payload, ['amount', 'coin_amount', 'coinAmount']) ); data.records.unshift({ id: firstValue(payload, ['transaction_id', 'transactionId']) || 'sub-seller-tx-' + Date.now(), type: 'expenditure', name: target.name, uid: target.uid, avatar: target.avatar, coins: coins || amount, amount: 0, currency: '', time: currentTimeLabel(), }); } function refreshSubSellerFromResponse(target, payload, amount) { if (!target) return; var balanceAfter = firstValue(payload, [ 'sub_seller_balance_after', 'subSellerBalanceAfter', 'child_balance_after', 'childBalanceAfter', 'target_balance_after', 'targetBalanceAfter', ]); data.subSellers = data.subSellers.map(function (item) { if (String(item.uid) !== String(target.uid)) return item; return Object.assign({}, item, { balance: balanceAfter !== '' && balanceAfter !== undefined ? toNumber(balanceAfter) : item.balance + amount, }); }); } function inviteSubSeller() { var input = $('inviteSubSellerInput'); var displayID = String(input.value || '').trim(); if (!data.subSellerPermission.canInvite) { setStatus( $('inviteSubSellerStatus'), t('coinseller_center.invite_failed', 'Invite failed.'), 'warning' ); return; } if (!displayID) { setStatus( $('inviteSubSellerStatus'), t('coinseller_center.enter_user_id', 'Enter user ID'), 'warning' ); return; } if (state.isMock) { input.value = ''; closeModal('inviteSubSellerModal'); toast( t( 'coinseller_center.invite_success', 'Official will review your invitation within 24 hours.' ) ); return; } if (!hasAPI() || !window.HyAppAPI.wallet.inviteCoinSellerSubSeller) { setStatus( $('inviteSubSellerStatus'), t('coinseller_center.invite_failed', 'Invite failed.'), 'warning' ); return; } state.invitingSubSeller = true; $('submitInviteSubSellerButton').disabled = true; setStatus( $('inviteSubSellerStatus'), t('coinseller_center.loading', 'Loading...'), '' ); // 邀请子币商现在只创建待审核申请;command_id 保证移动端重试不会重复生成多个 pending 申请。 window.HyAppAPI.wallet .inviteCoinSellerSubSeller({ command_id: subSellerCommandID('invite'), target_display_user_id: displayID, }) .then(function (response) { input.value = ''; setStatus( $('inviteSubSellerStatus'), t( 'coinseller_center.invite_success', 'Official will review your invitation within 24 hours.' ), 'success' ); // 申请提交成功不代表父子关系已经生效,列表必须继续以服务端 active 关系为准。 toast( t( 'coinseller_center.invite_success', 'Official will review your invitation within 24 hours.' ) ); closeModal('inviteSubSellerModal'); }) .catch(function (error) { if (window.console && window.console.warn) { window.console.warn( '[coinseller-center] invite sub seller failed', error ); } setStatus( $('inviteSubSellerStatus'), error && error.message ? error.message : t( 'coinseller_center.invite_failed', 'Invite failed.' ), 'warning' ); }) .finally(function () { state.invitingSubSeller = false; $('submitInviteSubSellerButton').disabled = false; }); } function openSubSellerTransfer(item) { state.subSellerTransferTarget = item; state.subSellerTransferAmount = ''; $('subSellerTransferAmountInput').value = ''; $('subSellerTransferTargetName').textContent = t( 'coinseller_center.transfer_to_sub_seller', 'Transfer to sub seller' ) + ': ' + item.name; setStatus($('subSellerTransferStatus'), '', ''); renderSubSellerTransferState(); openModal('subSellerTransferModal'); window.setTimeout(function () { $('subSellerTransferAmountInput').focus(); }, 80); } function parsedSubSellerTransferAmount() { var input = $('subSellerTransferAmountInput'); if (input) { var sanitized = String(input.value || '').replace(/[^\d]/g, ''); if (input.value !== sanitized) input.value = sanitized; state.subSellerTransferAmount = sanitized; } return Number(state.subSellerTransferAmount || 0); } function renderSubSellerTransferState() { var amount = parsedSubSellerTransferAmount(); $('submitSubSellerTransferButton').disabled = state.loading || state.subSellerTransferSubmitting || !state.subSellerTransferTarget || amount <= 0; } function transferToSubSeller() { var amount = parsedSubSellerTransferAmount(); var target = state.subSellerTransferTarget; if (!target) { setStatus( $('subSellerTransferStatus'), t( 'coinseller_center.select_sub_seller_first', 'Select a sub coin seller first.' ), 'warning' ); return; } if (amount <= 0) { setStatus( $('subSellerTransferStatus'), t( 'coinseller_center.valid_amount_required', 'Please enter a valid amount.' ), 'warning' ); return; } if (amount > data.balance.coins) { setStatus( $('subSellerTransferStatus'), t( 'coinseller_center.amount_exceeds_stock', 'Amount exceeds stock.' ), 'warning' ); return; } if (state.isMock) { data.balance.coins -= amount; refreshSubSellerFromResponse(target, {}, amount); appendSubSellerTransferRecord(amount, {}); finishSubSellerTransfer(); return; } if ( !hasAPI() || !window.HyAppAPI.wallet.transferCoinSellerToSubSeller ) { setStatus($('subSellerTransferStatus'), '', ''); toast(t('coinseller_center.transfer_failed', 'Transfer failed.')); return; } state.subSellerTransferSubmitting = true; setStatus( $('subSellerTransferStatus'), t('coinseller_center.loading', 'Loading...'), '' ); renderSubSellerTransferState(); // 子币商转账目标是对方的币商专用钱包,不是玩家普通金币;payload 单独命名,避免误走老的玩家充值接口。 window.HyAppAPI.wallet .transferCoinSellerToSubSeller({ command_id: subSellerCommandID('transfer'), target_display_user_id: String(target.uid || '').trim(), amount: amount, reason: 'sub coin seller stock transfer', }) .then(function (response) { var payload = unwrapData(response) || {}; var balanceAfter = firstValue(payload, [ 'parent_balance_after', 'parentBalanceAfter', 'seller_balance_after', 'sellerBalanceAfter', ]); data.balance.coins = balanceAfter !== '' && balanceAfter !== undefined ? toNumber(balanceAfter) : data.balance.coins - amount; refreshSubSellerFromResponse(target, payload, amount); appendSubSellerTransferRecord(amount, response); finishSubSellerTransfer(); }) .catch(function (error) { if (window.console && window.console.warn) { window.console.warn( '[coinseller-center] sub seller transfer failed', error ); } setStatus($('subSellerTransferStatus'), '', ''); toast( error && error.message ? error.message : t( 'coinseller_center.transfer_failed', 'Transfer failed.' ) ); }) .finally(function () { state.subSellerTransferSubmitting = false; renderSubSellerTransferState(); renderSubSellers(); }); } function rechargeNow() { var amount = parsedCoinAmount(); if (!state.target) { setStatus( $('rechargeStatus'), t( 'coinseller_center.select_user_first', 'Select a user first.' ), 'warning' ); return; } if (amount <= 0) { setStatus( $('rechargeStatus'), t( 'coinseller_center.valid_amount_required', 'Please enter a valid amount.' ), 'warning' ); return; } if (amount > data.balance.coins) { setStatus( $('rechargeStatus'), t( 'coinseller_center.amount_exceeds_stock', 'Amount exceeds stock.' ), 'warning' ); return; } if (state.isMock) { data.balance.coins -= amount; appendTransferRecord(amount, {}); finishRecharge(); return; } if (!hasAPI() || !window.HyAppAPI.wallet.transferCoinFromSeller) { setStatus($('rechargeStatus'), '', ''); toast(t('coinseller_center.transfer_failed', 'Transfer failed.')); return; } state.submitting = true; setStatus( $('rechargeStatus'), t('coinseller_center.loading', 'Loading...'), '' ); renderRechargeState(); window.HyAppAPI.wallet .transferCoinFromSeller(buildTransferPayload(amount)) .then(function (response) { var payload = unwrapData(response) || {}; var balanceAfter = toNumber( firstValue(payload, [ 'seller_balance_after', 'sellerBalanceAfter', ]) ); if (balanceAfter > 0 || payload.seller_balance_after === 0) { data.balance.coins = balanceAfter; } else { data.balance.coins -= amount; } appendTransferRecord(amount, response); finishRecharge(); }) .catch(function (error) { if (window.console && window.console.warn) { window.console.warn( '[coinseller-center] transfer failed', error ); } setStatus($('rechargeStatus'), '', ''); toast( error && error.message ? error.message : t( 'coinseller_center.transfer_failed', 'Transfer failed.' ) ); }) .finally(function () { state.submitting = false; renderRechargeState(); }); } function finishRecharge() { state.coinAmount = ''; $('coinAmountInput').value = ''; setStatus( $('rechargeStatus'), t('coinseller_center.recharge_success', 'Recharge success.'), 'success' ); renderBalance(); renderRecent(); renderRechargeState(); if (!$('historyModal').hidden) renderHistory(); closeModal('rechargeModal'); toast(t('coinseller_center.recharge_success', 'Recharge success.')); } function finishSubSellerTransfer() { state.subSellerTransferAmount = ''; $('subSellerTransferAmountInput').value = ''; setStatus( $('subSellerTransferStatus'), t('coinseller_center.transfer_success', 'Transfer success.'), 'success' ); renderBalance(); renderSubSellers(); renderRecent(); renderSubSellerTransferState(); if (!$('historyModal').hidden) renderHistory(); closeModal('subSellerTransferModal'); toast(t('coinseller_center.transfer_success', 'Transfer success.')); } function currentTimeLabel() { var now = new Date(); return ( now.getFullYear() + '-' + String(now.getMonth() + 1).padStart(2, '0') + '-' + String(now.getDate()).padStart(2, '0') + ' ' + String(now.getHours()).padStart(2, '0') + ':' + String(now.getMinutes()).padStart(2, '0') ); } function openModal(id) { var modal = $(id); if (!modal) return; modal.hidden = false; modal.setAttribute('aria-hidden', 'false'); document.body.classList.add('modal-open'); } function closeModal(id) { var modal = $(id); if (!modal) return; modal.hidden = true; modal.setAttribute('aria-hidden', 'true'); var hasOpen = Array.prototype.some.call( document.querySelectorAll('.sheet-modal'), function (node) { return !node.hidden; } ); document.body.classList.toggle('modal-open', hasOpen); } function bindEvents() { $('backButton').addEventListener('click', function () { if (window.HyAppBridge) window.HyAppBridge.back(); }); $('historyButton').addEventListener('click', function () { state.historyLimit = 3; renderHistory(); openModal('historyModal'); }); $('viewAllHistoryButton').addEventListener('click', function () { state.historyLimit = 3; renderHistory(); openModal('historyModal'); }); $('helpButton').addEventListener('click', function () { openModal('helpModal'); }); $('inviteSubSellerButton').addEventListener('click', function () { $('inviteSubSellerInput').value = ''; setStatus($('inviteSubSellerStatus'), '', ''); openModal('inviteSubSellerModal'); window.setTimeout(function () { $('inviteSubSellerInput').focus(); }, 80); }); document .querySelectorAll('[data-close-modal]') .forEach(function (node) { node.addEventListener('click', function () { closeModal(node.getAttribute('data-close-modal')); }); }); $('searchForm').addEventListener('submit', function (event) { event.preventDefault(); var value = $('userSearchInput').value.trim(); if (!value) { setStatus( $('searchStatus'), t('coinseller_center.enter_user_id', 'Enter user ID'), 'warning' ); return; } handleSearch(value); }); $('openRechargeButton').addEventListener('click', function () { if (!state.target) return; state.coinAmount = ''; $('coinAmountInput').value = ''; setStatus($('rechargeStatus'), '', ''); renderRechargeState(); openModal('rechargeModal'); window.setTimeout(function () { $('coinAmountInput').focus(); }, 80); }); $('coinAmountInput').addEventListener('input', function () { state.coinAmount = $('coinAmountInput').value.replace(/[^\d]/g, ''); $('coinAmountInput').value = state.coinAmount; setStatus($('rechargeStatus'), '', ''); renderRechargeState(); }); $('rechargeForm').addEventListener('submit', function (event) { event.preventDefault(); rechargeNow(); }); $('inviteSubSellerForm').addEventListener('submit', function (event) { event.preventDefault(); inviteSubSeller(); }); $('subSellerTransferAmountInput').addEventListener( 'input', function () { state.subSellerTransferAmount = $( 'subSellerTransferAmountInput' ).value.replace(/[^\d]/g, ''); $('subSellerTransferAmountInput').value = state.subSellerTransferAmount; setStatus($('subSellerTransferStatus'), '', ''); renderSubSellerTransferState(); } ); $('subSellerTransferForm').addEventListener('submit', function (event) { event.preventDefault(); transferToSubSeller(); }); document.querySelectorAll('[data-history-tab]').forEach(function (tab) { tab.addEventListener('click', function () { state.historyTab = tab.getAttribute('data-history-tab'); state.historyLimit = 3; renderHistory(); }); }); $('historySearchForm').addEventListener('submit', function (event) { event.preventDefault(); state.historyQuery = $('historySearchInput').value.trim(); state.historyLimit = 3; renderHistory(); }); $('historyClearButton').addEventListener('click', function () { state.historyQuery = ''; $('historySearchInput').value = ''; state.historyLimit = 3; renderHistory(); }); $('loadMoreButton').addEventListener('click', function () { state.historyLimit += 3; renderHistory(); }); document.addEventListener('keydown', function (event) { if (event.key !== 'Escape') return; closeModal('historyModal'); closeModal('helpModal'); closeModal('inviteSubSellerModal'); closeModal('subSellerTransferModal'); }); } function render() { renderProfile(); renderBalance(); renderQuickAmounts(); renderTarget(); renderRechargeState(); renderSubSellers(); renderSubSellerTransferState(); renderRecent(); } function init() { if (state.isMock) { applyMockData(); } else { applyEmptyData(); } render(); bindEvents(); if (window.HyAppBridge) { window.HyAppBridge.ready({ page: 'coinseller-center', mock: state.isMock, }); } if (!state.isMock) loadRealData(); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } window.addEventListener('hyapp:i18n-ready', function () { render(); if (!$('historyModal').hidden) renderHistory(); }); })();