1189 lines
40 KiB
JavaScript
1189 lines
40 KiB
JavaScript
(function () {
|
||
var EXCHANGE_RATE = 80000;
|
||
var WITHDRAW_MIN_AMOUNT = 50;
|
||
var params = new URLSearchParams(window.location.search);
|
||
var isMock = params.get('mock') === '1';
|
||
var currentMode = normalizeMode(params.get('mode')) || 'transfer';
|
||
var identity = String(params.get('identity') || '').trim();
|
||
var salaryAssetType = String(
|
||
params.get('salaryType') || params.get('asset_type') || ''
|
||
).trim();
|
||
var state = {
|
||
balance: 0,
|
||
paymentInfo: null,
|
||
selectedPayee: null,
|
||
isSearching: false,
|
||
isSaving: false,
|
||
isEditingAddress: false,
|
||
isSavingContact: false,
|
||
isSubmitting: false,
|
||
contactInfo: '',
|
||
historyItems: [],
|
||
transferRateTiers: [],
|
||
};
|
||
var lastSearchTriggerAt = 0;
|
||
var autoSearchTimer = 0;
|
||
|
||
var mockBalances = {
|
||
HOST_SALARY_USD: 30650.75,
|
||
AGENCY_SALARY_USD: 30650.75,
|
||
BD_SALARY_USD: 48620.8,
|
||
ADMIN_SALARY_USD: 168250.5,
|
||
};
|
||
|
||
var mock = {
|
||
balance: mockBalances[salaryAssetType] || 30650.75,
|
||
paymentInfo: null,
|
||
payees: {
|
||
163003: {
|
||
id: 'payee_163003',
|
||
name: 'Yumi Star Agency',
|
||
account: '163003',
|
||
avatar: '',
|
||
},
|
||
163000: {
|
||
id: 'payee_163000',
|
||
name: 'quick_1780052629537',
|
||
account: '163000',
|
||
avatar: '',
|
||
},
|
||
},
|
||
history: [
|
||
{
|
||
key: historyTitleKey(),
|
||
title: historyTitle(),
|
||
amount: 18400.25,
|
||
balance: mockBalances[salaryAssetType] || 30650.75,
|
||
type: 0,
|
||
date: '2026-06-01 12:10',
|
||
},
|
||
{
|
||
key: 'withdraw_exchange.history_exchange_to_golds',
|
||
title: 'Exchange to golds',
|
||
amount: 800,
|
||
balance: 12250.5,
|
||
type: 1,
|
||
date: '2026-05-30 20:42',
|
||
},
|
||
{
|
||
key: 'withdraw_exchange.history_gift_reward',
|
||
title: 'Gift reward',
|
||
amount: 8250.5,
|
||
balance: 13050.5,
|
||
type: 0,
|
||
date: '2026-05-28 18:30',
|
||
},
|
||
],
|
||
};
|
||
|
||
var inputs = {
|
||
withdraw: $('withdrawAmount'),
|
||
exchange: $('exchangeAmount'),
|
||
transfer: $('transferAmount'),
|
||
};
|
||
|
||
function $(id) {
|
||
return document.getElementById(id);
|
||
}
|
||
|
||
function t(key, fallback) {
|
||
return window.HyAppI18n && window.HyAppI18n.t
|
||
? window.HyAppI18n.t(key, fallback)
|
||
: fallback || key;
|
||
}
|
||
|
||
function toast(message) {
|
||
if (window.HyAppToast) {
|
||
window.HyAppToast.show(message);
|
||
return;
|
||
}
|
||
window.alert(message);
|
||
}
|
||
|
||
function normalizeMode(mode) {
|
||
var value = String(mode || '').trim();
|
||
return ['withdraw', 'exchange', 'transfer'].indexOf(value) >= 0
|
||
? value
|
||
: '';
|
||
}
|
||
|
||
function normalizeIdentity(value) {
|
||
value = String(value || '')
|
||
.trim()
|
||
.toUpperCase()
|
||
.replace(/-/g, '_');
|
||
if (value === 'ADMIN') return 'BD_LEADER';
|
||
if (['HOST', 'AGENCY', 'BD', 'BD_LEADER'].indexOf(value) >= 0)
|
||
return value;
|
||
if (salaryAssetType === 'AGENCY_SALARY_USD') return 'AGENCY';
|
||
if (salaryAssetType === 'BD_SALARY_USD') return 'BD';
|
||
if (salaryAssetType === 'ADMIN_SALARY_USD') return 'BD_LEADER';
|
||
return 'HOST';
|
||
}
|
||
|
||
function api() {
|
||
if (window.HyAppAPI && window.HyAppAPI.salaryWallet)
|
||
return window.HyAppAPI.salaryWallet;
|
||
return window.HyApp && window.HyApp.api
|
||
? window.HyApp.api.salaryWallet
|
||
: null;
|
||
}
|
||
|
||
function userApi() {
|
||
if (window.HyAppAPI && window.HyAppAPI.user)
|
||
return window.HyAppAPI.user;
|
||
return window.HyApp && window.HyApp.api ? window.HyApp.api.user : null;
|
||
}
|
||
|
||
function commandId(prefix) {
|
||
return (
|
||
prefix +
|
||
'-' +
|
||
Date.now() +
|
||
'-' +
|
||
Math.random().toString(16).slice(2)
|
||
);
|
||
}
|
||
|
||
function money(value) {
|
||
var number = Number(value || 0);
|
||
return number.toLocaleString('en-US', {
|
||
minimumFractionDigits: 2,
|
||
maximumFractionDigits: 2,
|
||
});
|
||
}
|
||
|
||
function moneyInput(value) {
|
||
var number = Number(value || 0);
|
||
return Number.isFinite(number) ? number.toFixed(2) : '0.00';
|
||
}
|
||
|
||
function amount() {
|
||
var input = inputs[currentMode];
|
||
return Number(input && input.value ? input.value : 0) || 0;
|
||
}
|
||
|
||
function amountText(value) {
|
||
return moneyInput(value);
|
||
}
|
||
|
||
function usdMinorToNumber(value) {
|
||
return Number(value || 0) / 100;
|
||
}
|
||
|
||
function salaryBalanceAfterToNumber(value) {
|
||
if (value && typeof value === 'object') {
|
||
return usdMinorToNumber(value.available_amount);
|
||
}
|
||
return usdMinorToNumber(value);
|
||
}
|
||
|
||
function findTransferRate(value) {
|
||
var minor = Math.round(Number(value || 0) * 100);
|
||
return (state.transferRateTiers || []).find(function (tier) {
|
||
return (
|
||
String(tier.status || 'active') === 'active' &&
|
||
minor >= Number(tier.min_usd_minor || 0) &&
|
||
minor <= Number(tier.max_usd_minor || 0)
|
||
);
|
||
});
|
||
}
|
||
|
||
function delay(value) {
|
||
return new Promise(function (resolve) {
|
||
window.setTimeout(function () {
|
||
resolve(value);
|
||
}, 260);
|
||
});
|
||
}
|
||
|
||
function mockRequest(action, payload) {
|
||
if (action === 'balance') return delay(mock.balance);
|
||
if (action === 'payment') return delay(mock.paymentInfo);
|
||
if (action === 'history') return delay(mock.history.slice());
|
||
if (action === 'saveAddress') {
|
||
mock.paymentInfo = {
|
||
id: 'usdt_mock_1',
|
||
cardNo: payload.cardNo,
|
||
};
|
||
return delay(mock.paymentInfo);
|
||
}
|
||
if (action === 'searchPayee') {
|
||
var account = String(payload.account || '').trim();
|
||
var payee = mock.payees[account];
|
||
if (!payee && account && account !== '000000') {
|
||
payee = {
|
||
id: 'payee_' + account,
|
||
name: 'User ' + account,
|
||
nameKey: 'withdraw_exchange.mock_user_name',
|
||
account: account,
|
||
avatar: '',
|
||
};
|
||
}
|
||
return delay(payee || null);
|
||
}
|
||
if (action === 'submit') {
|
||
mock.balance = Math.max(0, mock.balance - payload.amount);
|
||
mock.history.unshift({
|
||
title: titleForMode(payload.mode),
|
||
amount: payload.amount,
|
||
balance: mock.balance,
|
||
type: 1,
|
||
date: new Date().toISOString().replace('T', ' ').slice(0, 16),
|
||
});
|
||
return delay({ ok: true });
|
||
}
|
||
return delay(null);
|
||
}
|
||
|
||
function titleForMode(mode) {
|
||
if (mode === 'withdraw')
|
||
return t('withdraw_exchange.withdraw', 'Withdraw');
|
||
if (mode === 'exchange')
|
||
return t('withdraw_exchange.exchange', 'Exchange');
|
||
return t('withdraw_exchange.transfer', 'Transfer');
|
||
}
|
||
|
||
function historyTitleKey() {
|
||
if (salaryAssetType === 'BD_SALARY_USD')
|
||
return 'withdraw_exchange.history_bd_salary';
|
||
if (salaryAssetType === 'ADMIN_SALARY_USD')
|
||
return 'withdraw_exchange.history_admin_salary';
|
||
if (salaryAssetType === 'AGENCY_SALARY_USD')
|
||
return 'withdraw_exchange.history_agency_salary';
|
||
return 'withdraw_exchange.history_host_salary';
|
||
}
|
||
|
||
function historyTitle() {
|
||
if (salaryAssetType === 'BD_SALARY_USD') return 'BD salary';
|
||
if (salaryAssetType === 'ADMIN_SALARY_USD') return 'Admin salary';
|
||
if (salaryAssetType === 'AGENCY_SALARY_USD') return 'Agency salary';
|
||
return 'Host salary';
|
||
}
|
||
|
||
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 contactFrom(value) {
|
||
if (!value || typeof value !== 'object') return '';
|
||
var source = value.profile || value.user || value;
|
||
return String(
|
||
firstValue(source, [
|
||
'contact_info',
|
||
'contactInfo',
|
||
'whatsapp_phone',
|
||
'whatsappPhone',
|
||
'contact',
|
||
]) || ''
|
||
).trim();
|
||
}
|
||
|
||
function withdrawAddressFrom(value) {
|
||
if (typeof value === 'string') return value.trim();
|
||
if (!value || typeof value !== 'object') return '';
|
||
if (typeof value.withdraw_address === 'string')
|
||
return value.withdraw_address.trim();
|
||
var source =
|
||
value.withdraw_address || value.user || value.profile || value;
|
||
return String(
|
||
firstValue(source, [
|
||
'cardNo',
|
||
'withdraw_usdt_trc20_address',
|
||
'withdrawUsdtTrc20Address',
|
||
'usdt_trc20_address',
|
||
'usdtTrc20Address',
|
||
'payment_address',
|
||
'paymentAddress',
|
||
'address',
|
||
]) || ''
|
||
).trim();
|
||
}
|
||
|
||
function paymentInfoFrom(value) {
|
||
var address = withdrawAddressFrom(value);
|
||
if (!address) return null;
|
||
return {
|
||
id: 'usdt_trc20',
|
||
cardNo: address,
|
||
};
|
||
}
|
||
|
||
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 historyLabelForBizType(bizType, delta) {
|
||
if (bizType === 'salary_exchange_to_coin') {
|
||
return {
|
||
key: 'withdraw_exchange.history_exchange_to_golds',
|
||
title: 'Exchange to golds',
|
||
};
|
||
}
|
||
if (bizType === 'salary_transfer_to_coin_seller') {
|
||
return {
|
||
key: 'withdraw_exchange.transfer',
|
||
title: 'Transfer',
|
||
};
|
||
}
|
||
if (bizType === 'host_salary_settlement' || delta >= 0) {
|
||
return {
|
||
key: historyTitleKey(),
|
||
title: historyTitle(),
|
||
};
|
||
}
|
||
return {
|
||
key: 'withdraw_exchange.withdraw',
|
||
title: 'Withdraw',
|
||
};
|
||
}
|
||
|
||
function normalizeHistoryItem(item, index) {
|
||
var delta = Number(
|
||
firstValue(item, ['available_delta', 'availableDelta'])
|
||
);
|
||
if (!Number.isFinite(delta)) delta = 0;
|
||
var balance = Number(
|
||
firstValue(item, ['available_after', 'availableAfter'])
|
||
);
|
||
if (!Number.isFinite(balance)) balance = 0;
|
||
var label = historyLabelForBizType(
|
||
String(firstValue(item, ['biz_type', 'bizType']) || ''),
|
||
delta
|
||
);
|
||
return {
|
||
id:
|
||
firstValue(item, ['transaction_id', 'transactionId']) ||
|
||
String(firstValue(item, ['entry_id', 'entryId']) || index),
|
||
key: label.key,
|
||
title: label.title,
|
||
amount: Math.abs(usdMinorToNumber(delta)),
|
||
balance: usdMinorToNumber(balance),
|
||
type: delta < 0 ? 1 : 0,
|
||
date: timeLabelFromMS(
|
||
firstValue(item, ['created_at_ms', 'createdAtMs'])
|
||
),
|
||
};
|
||
}
|
||
|
||
function normalizeHistoryItems(payload) {
|
||
var source = payload || {};
|
||
if (source.identity) identity = normalizeIdentity(source.identity);
|
||
if (source.salary_asset_type) {
|
||
salaryAssetType = source.salary_asset_type;
|
||
}
|
||
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 normalizeHistoryItem(item || {}, index);
|
||
});
|
||
}
|
||
|
||
function isValidTrc20(value) {
|
||
return /^T[1-9A-HJ-NP-Za-km-z]{33}$/.test(String(value || '').trim());
|
||
}
|
||
|
||
function activeSubmitLabel() {
|
||
if (currentMode === 'withdraw')
|
||
return t('withdraw_exchange.withdraw_now', 'Withdraw now');
|
||
if (currentMode === 'exchange')
|
||
return t('withdraw_exchange.exchange_now', 'Exchange now');
|
||
return t('withdraw_exchange.transfer_now', 'Transfer now');
|
||
}
|
||
|
||
function renderBalance() {
|
||
$('balanceAmount').textContent = money(state.balance);
|
||
Object.keys(inputs).forEach(function (key) {
|
||
if (inputs[key]) inputs[key].max = moneyInput(state.balance);
|
||
});
|
||
}
|
||
|
||
function renderMode() {
|
||
document.querySelectorAll('.mode-tab').forEach(function (tab) {
|
||
var active = tab.getAttribute('data-mode') === currentMode;
|
||
tab.classList.toggle('active', active);
|
||
tab.setAttribute('aria-selected', String(active));
|
||
});
|
||
document.querySelectorAll('.mode-panel').forEach(function (panel) {
|
||
panel.classList.toggle(
|
||
'active',
|
||
panel.getAttribute('data-panel') === currentMode
|
||
);
|
||
});
|
||
$('paymentSection').hidden = currentMode !== 'withdraw';
|
||
updateSubmit();
|
||
}
|
||
|
||
function renderPaymentInfo() {
|
||
var payment = state.paymentInfo;
|
||
var hasPayment = Boolean(payment);
|
||
$('emptyAddressState').hidden = hasPayment || state.isEditingAddress;
|
||
$('addressForm').hidden = hasPayment && !state.isEditingAddress;
|
||
$('boundAddressCard').hidden = !hasPayment || state.isEditingAddress;
|
||
if (payment) $('boundAddress').textContent = payment.cardNo || '-';
|
||
updateSubmit();
|
||
}
|
||
|
||
function renderPayee() {
|
||
var result = $('payeeResult');
|
||
result.textContent = '';
|
||
if (!state.selectedPayee) {
|
||
result.hidden = true;
|
||
updateSubmit();
|
||
return;
|
||
}
|
||
|
||
var payee = state.selectedPayee;
|
||
var payeeName = payee.nameKey
|
||
? t(payee.nameKey, payee.name || 'User {account}').replace(
|
||
'{account}',
|
||
payee.account
|
||
)
|
||
: payee.name;
|
||
var avatar = document.createElement('div');
|
||
var copy = document.createElement('div');
|
||
var name = document.createElement('div');
|
||
var id = document.createElement('div');
|
||
avatar.className = 'payee-avatar';
|
||
copy.className = 'payee-copy';
|
||
name.className = 'payee-name';
|
||
id.className = 'payee-id';
|
||
if (payee.avatar) {
|
||
var avatarImage = document.createElement('img');
|
||
avatarImage.src = payee.avatar;
|
||
avatarImage.alt = '';
|
||
avatar.appendChild(avatarImage);
|
||
} else {
|
||
avatar.textContent = String(payeeName || 'U')
|
||
.charAt(0)
|
||
.toUpperCase();
|
||
}
|
||
name.textContent = payeeName || '-';
|
||
id.textContent =
|
||
t('withdraw_exchange.uid_prefix', 'UID:') + ' ' + payee.account;
|
||
copy.appendChild(name);
|
||
copy.appendChild(id);
|
||
result.appendChild(avatar);
|
||
result.appendChild(copy);
|
||
result.hidden = false;
|
||
updateSubmit();
|
||
}
|
||
|
||
function renderHistory() {
|
||
$('salaryHistoryTotal').textContent = '$' + money(state.balance);
|
||
var list = $('salaryHistoryList');
|
||
list.textContent = '';
|
||
if (!state.historyItems.length) {
|
||
var empty = document.createElement('div');
|
||
empty.className = 'salary-history-empty';
|
||
empty.textContent = t(
|
||
'withdraw_exchange.no_salary_history',
|
||
'No salary history'
|
||
);
|
||
list.appendChild(empty);
|
||
return;
|
||
}
|
||
|
||
state.historyItems.forEach(function (item) {
|
||
var row = document.createElement('article');
|
||
var main = document.createElement('div');
|
||
var title = document.createElement('div');
|
||
var meta = document.createElement('div');
|
||
var value = document.createElement('div');
|
||
var isOut = Number(item.type) === 1;
|
||
row.className = 'salary-history-item';
|
||
main.className = 'salary-history-item-main';
|
||
title.className = 'salary-history-item-title';
|
||
meta.className = 'salary-history-item-meta';
|
||
value.className =
|
||
'salary-history-amount' + (isOut ? ' is-out' : '');
|
||
title.textContent = item.key ? t(item.key, item.title) : item.title;
|
||
meta.textContent =
|
||
item.date +
|
||
' ' +
|
||
t('withdraw_exchange.balance', 'Balance') +
|
||
': $' +
|
||
money(item.balance);
|
||
value.textContent = (isOut ? '-' : '+') + '$' + money(item.amount);
|
||
main.appendChild(title);
|
||
main.appendChild(meta);
|
||
row.appendChild(main);
|
||
row.appendChild(value);
|
||
list.appendChild(row);
|
||
});
|
||
}
|
||
|
||
function updateSubmit() {
|
||
var button = $('submitButton');
|
||
if (state.isSubmitting) {
|
||
button.textContent = t(
|
||
'withdraw_exchange.submitting',
|
||
'Submitting...'
|
||
);
|
||
button.disabled = true;
|
||
return;
|
||
}
|
||
|
||
var value = amount();
|
||
var transferRateMissing =
|
||
currentMode === 'transfer' && value > 0 && !findTransferRate(value);
|
||
var disabled =
|
||
value <= 0 ||
|
||
value > state.balance ||
|
||
(currentMode === 'withdraw' &&
|
||
(!state.paymentInfo || value < WITHDRAW_MIN_AMOUNT)) ||
|
||
(currentMode === 'transfer' &&
|
||
(!state.selectedPayee || transferRateMissing));
|
||
button.textContent = activeSubmitLabel();
|
||
button.disabled = disabled;
|
||
}
|
||
|
||
function needsContactForSubmit() {
|
||
return currentMode === 'exchange' || currentMode === 'transfer';
|
||
}
|
||
|
||
function normalizeAmount(event) {
|
||
var input = event.target;
|
||
var raw = String(input.value || '').replace(/[^\d.]/g, '');
|
||
var parts = raw.split('.');
|
||
var next =
|
||
parts.length > 2 ? parts[0] + '.' + parts.slice(1).join('') : raw;
|
||
var value = Number(next);
|
||
if (Number.isFinite(value) && value > state.balance)
|
||
next = moneyInput(state.balance);
|
||
input.value = next;
|
||
updateSubmit();
|
||
}
|
||
|
||
function setMode(mode) {
|
||
currentMode = normalizeMode(mode) || 'transfer';
|
||
renderMode();
|
||
}
|
||
|
||
function openHistory() {
|
||
$('salaryHistoryModal').hidden = false;
|
||
document.body.classList.add('modal-open');
|
||
$('salaryHistoryList').textContent = t(
|
||
'withdraw_exchange.loading',
|
||
'Loading...'
|
||
);
|
||
var historyRequest = isMock
|
||
? mockRequest('history')
|
||
: api() && api().history
|
||
? api().history(identity, 30).then(normalizeHistoryItems)
|
||
: Promise.resolve([]);
|
||
historyRequest
|
||
.then(function (items) {
|
||
state.historyItems = items || [];
|
||
renderHistory();
|
||
})
|
||
.catch(function (err) {
|
||
state.historyItems = [];
|
||
renderHistory();
|
||
toast(
|
||
err && err.message
|
||
? err.message
|
||
: t(
|
||
'withdraw_exchange.load_failed',
|
||
'Load failed. Try again later.'
|
||
)
|
||
);
|
||
});
|
||
}
|
||
|
||
function closeHistory() {
|
||
$('salaryHistoryModal').hidden = true;
|
||
document.body.classList.remove('modal-open');
|
||
}
|
||
|
||
function setContactStatus(message) {
|
||
var node = $('contactStatus');
|
||
node.textContent = message || '';
|
||
node.hidden = !message;
|
||
}
|
||
|
||
function setContactSaving(saving) {
|
||
state.isSavingContact = saving;
|
||
$('saveContactButton').disabled = saving;
|
||
$('saveContactButton').textContent = saving
|
||
? t('contact_card.saving', 'Saving...')
|
||
: t('contact_card.save', 'Save');
|
||
}
|
||
|
||
function openContactModal() {
|
||
$('contactInput').value = state.contactInfo || '';
|
||
setContactStatus('');
|
||
$('contactModal').hidden = false;
|
||
$('contactModal').setAttribute('aria-hidden', 'false');
|
||
document.body.classList.add('modal-open');
|
||
window.setTimeout(function () {
|
||
$('contactInput').focus();
|
||
}, 0);
|
||
}
|
||
|
||
function closeContactModal() {
|
||
$('contactModal').hidden = true;
|
||
$('contactModal').setAttribute('aria-hidden', 'true');
|
||
document.body.classList.remove('modal-open');
|
||
}
|
||
|
||
function saveContact(event) {
|
||
if (event) event.preventDefault();
|
||
var contact = String($('contactInput').value || '').trim();
|
||
if (!contact) {
|
||
setContactStatus(
|
||
t('contact_card.required', 'Enter WhatsApp contact.')
|
||
);
|
||
return;
|
||
}
|
||
var apiClient = userApi();
|
||
if (!apiClient || !apiClient.updateContact) {
|
||
setContactStatus(
|
||
t('contact_card.api_unavailable', 'Contact API unavailable.')
|
||
);
|
||
return;
|
||
}
|
||
|
||
// Exchange/Transfer 需要运营联系方式用于后台跟进;联系方式是用户主资料,保存成功后才允许继续提交工资兑换或转账请求。
|
||
setContactSaving(true);
|
||
setContactStatus('');
|
||
apiClient
|
||
.updateContact({ contact_info: contact })
|
||
.then(function (response) {
|
||
state.contactInfo = contactFrom(response) || contact;
|
||
closeContactModal();
|
||
updateSubmit();
|
||
toast(t('contact_card.saved', 'Saved.'));
|
||
})
|
||
.catch(function () {
|
||
setContactStatus(t('contact_card.save_failed', 'Save failed.'));
|
||
})
|
||
.finally(function () {
|
||
setContactSaving(false);
|
||
});
|
||
}
|
||
|
||
function saveAddress() {
|
||
var address = String($('trc20Address').value || '').trim();
|
||
if (!isValidTrc20(address)) {
|
||
toast(
|
||
t(
|
||
'withdraw_exchange.invalid_address',
|
||
'Please enter a valid TRC20 USDT address'
|
||
)
|
||
);
|
||
return;
|
||
}
|
||
state.isSaving = true;
|
||
$('saveAddressButton').disabled = true;
|
||
$('saveAddressButton').textContent = t(
|
||
'withdraw_exchange.saving',
|
||
'Saving...'
|
||
);
|
||
var saveRequest = isMock
|
||
? mockRequest('saveAddress', { cardNo: address })
|
||
: api() && api().saveWithdrawAddress
|
||
? api()
|
||
.saveWithdrawAddress({
|
||
payment_address: address,
|
||
})
|
||
.then(paymentInfoFrom)
|
||
: Promise.reject(
|
||
new Error(
|
||
t('withdraw_exchange.submit_failed', 'Submit failed.')
|
||
)
|
||
);
|
||
saveRequest
|
||
.then(function (payment) {
|
||
state.paymentInfo = payment || {
|
||
id: 'usdt_trc20',
|
||
cardNo: address,
|
||
};
|
||
state.isEditingAddress = false;
|
||
$('trc20Address').value = '';
|
||
renderPaymentInfo();
|
||
toast(t('withdraw_exchange.address_saved', 'Address saved'));
|
||
})
|
||
.catch(function (err) {
|
||
toast(
|
||
err && err.message
|
||
? err.message
|
||
: t('withdraw_exchange.submit_failed', 'Submit failed.')
|
||
);
|
||
})
|
||
.finally(function () {
|
||
state.isSaving = false;
|
||
$('saveAddressButton').disabled = false;
|
||
$('saveAddressButton').textContent = t(
|
||
'withdraw_exchange.save_address',
|
||
'Save address'
|
||
);
|
||
});
|
||
}
|
||
|
||
function editAddress() {
|
||
if (!state.paymentInfo) return;
|
||
state.isEditingAddress = true;
|
||
$('trc20Address').value = state.paymentInfo.cardNo || '';
|
||
renderPaymentInfo();
|
||
window.setTimeout(function () {
|
||
$('trc20Address').focus();
|
||
}, 0);
|
||
}
|
||
|
||
function searchPayee() {
|
||
var now = Date.now();
|
||
if (now - lastSearchTriggerAt < 240) return;
|
||
lastSearchTriggerAt = now;
|
||
|
||
var account = String($('transferAccount').value || '').trim();
|
||
if (!account) {
|
||
toast(
|
||
t(
|
||
'withdraw_exchange.missing_payee',
|
||
'Please search and select a user'
|
||
)
|
||
);
|
||
return;
|
||
}
|
||
state.selectedPayee = null;
|
||
renderPayee();
|
||
if (!isMock && !api()) {
|
||
toast(
|
||
t(
|
||
'withdraw_exchange.search_failed',
|
||
'Search failed. Try again later.'
|
||
)
|
||
);
|
||
return;
|
||
}
|
||
state.isSearching = true;
|
||
$('searchPayeeButton').disabled = true;
|
||
$('searchPayeeButton').textContent = t(
|
||
'withdraw_exchange.searching',
|
||
'Searching...'
|
||
);
|
||
var request = isMock
|
||
? mockRequest('searchPayee', { account: account })
|
||
: api()
|
||
.searchCoinSeller(account, identity)
|
||
.then(function (data) {
|
||
var seller = data && data.seller;
|
||
if (!seller) return null;
|
||
return {
|
||
id: seller.user_id || seller.userId,
|
||
name:
|
||
seller.username ||
|
||
seller.display_user_id ||
|
||
account,
|
||
account: seller.display_user_id || account,
|
||
avatar: seller.avatar || '',
|
||
};
|
||
});
|
||
request
|
||
.then(function (payee) {
|
||
state.isSearching = false;
|
||
$('searchPayeeButton').disabled = false;
|
||
$('searchPayeeButton').textContent = t(
|
||
'withdraw_exchange.search',
|
||
'Search'
|
||
);
|
||
if (!payee) {
|
||
toast(
|
||
t('withdraw_exchange.payee_not_found', 'User not found')
|
||
);
|
||
return;
|
||
}
|
||
state.selectedPayee = payee;
|
||
renderPayee();
|
||
})
|
||
.catch(function (err) {
|
||
state.isSearching = false;
|
||
$('searchPayeeButton').disabled = false;
|
||
$('searchPayeeButton').textContent = t(
|
||
'withdraw_exchange.search',
|
||
'Search'
|
||
);
|
||
toast(
|
||
err && err.message
|
||
? err.message
|
||
: t(
|
||
'withdraw_exchange.payee_not_found',
|
||
'User not found'
|
||
)
|
||
);
|
||
});
|
||
}
|
||
|
||
function validateSubmit() {
|
||
var value = amount();
|
||
if (value <= 0)
|
||
return t(
|
||
'withdraw_exchange.invalid_amount',
|
||
'Please enter a valid amount'
|
||
);
|
||
if (value > state.balance)
|
||
return t(
|
||
'withdraw_exchange.amount_exceeds_balance',
|
||
'Amount exceeds salary'
|
||
);
|
||
if (currentMode === 'withdraw' && !state.paymentInfo)
|
||
return t(
|
||
'withdraw_exchange.missing_payment_info',
|
||
'Please add a TRC20 USDT address'
|
||
);
|
||
if (currentMode === 'withdraw' && value < WITHDRAW_MIN_AMOUNT)
|
||
return t(
|
||
'withdraw_exchange.amount_too_low',
|
||
'Minimum withdrawal amount is 50 USD'
|
||
);
|
||
if (currentMode === 'transfer' && !state.selectedPayee)
|
||
return t(
|
||
'withdraw_exchange.missing_payee',
|
||
'Please search and select a user'
|
||
);
|
||
if (currentMode === 'transfer' && !findTransferRate(value))
|
||
return t(
|
||
'withdraw_exchange.rate_not_configured',
|
||
'Exchange rate not configured.'
|
||
);
|
||
return '';
|
||
}
|
||
|
||
function submit() {
|
||
var error = validateSubmit();
|
||
if (error) {
|
||
toast(error);
|
||
return;
|
||
}
|
||
if (needsContactForSubmit() && !state.contactInfo) {
|
||
openContactModal();
|
||
return;
|
||
}
|
||
state.isSubmitting = true;
|
||
updateSubmit();
|
||
submitRequest()
|
||
.then(function (result) {
|
||
var input = inputs[currentMode];
|
||
if (input) input.value = '';
|
||
if (currentMode === 'transfer') {
|
||
state.selectedPayee = null;
|
||
$('transferAccount').value = '';
|
||
renderPayee();
|
||
}
|
||
if (isMock) {
|
||
state.balance = mock.balance;
|
||
} else if (currentMode === 'withdraw') {
|
||
state.balance = salaryBalanceAfterToNumber(
|
||
result.salary_balance_after
|
||
);
|
||
} else if (currentMode === 'exchange') {
|
||
state.balance = salaryBalanceAfterToNumber(
|
||
result.salary_balance_after
|
||
);
|
||
} else if (currentMode === 'transfer') {
|
||
state.balance = usdMinorToNumber(
|
||
result.source_salary_balance_after
|
||
);
|
||
}
|
||
state.isSubmitting = false;
|
||
renderBalance();
|
||
updateSubmit();
|
||
var key =
|
||
currentMode === 'withdraw'
|
||
? 'withdraw_exchange.withdraw_success'
|
||
: currentMode === 'exchange'
|
||
? 'withdraw_exchange.exchange_success'
|
||
: 'withdraw_exchange.transfer_success';
|
||
toast(t(key, 'Submitted'));
|
||
})
|
||
.catch(function (err) {
|
||
state.isSubmitting = false;
|
||
updateSubmit();
|
||
toast(
|
||
err && err.message
|
||
? err.message
|
||
: t('withdraw_exchange.submit_failed', 'Submit failed.')
|
||
);
|
||
});
|
||
}
|
||
|
||
function submitRequest() {
|
||
var value = amount();
|
||
if (isMock) {
|
||
return mockRequest('submit', {
|
||
mode: currentMode,
|
||
amount: value,
|
||
payee: state.selectedPayee,
|
||
paymentInfo: state.paymentInfo,
|
||
});
|
||
}
|
||
if (!api()) {
|
||
return Promise.reject(
|
||
new Error(
|
||
t('withdraw_exchange.submit_failed', 'Submit failed.')
|
||
)
|
||
);
|
||
}
|
||
if (currentMode === 'exchange') {
|
||
return api().exchange({
|
||
command_id: commandId('salary-exchange'),
|
||
identity: identity,
|
||
amount_usd: amountText(value),
|
||
reason: 'salary exchange',
|
||
});
|
||
}
|
||
if (currentMode === 'withdraw') {
|
||
return api().withdraw({
|
||
command_id: commandId('salary-withdraw'),
|
||
identity: identity,
|
||
amount_usd: amountText(value),
|
||
payment_address: state.paymentInfo && state.paymentInfo.cardNo,
|
||
});
|
||
}
|
||
return api().transferToCoinSeller({
|
||
command_id: commandId('salary-transfer'),
|
||
identity: identity,
|
||
target_display_user_id: state.selectedPayee.account,
|
||
amount_usd: amountText(value),
|
||
reason: 'salary transfer',
|
||
});
|
||
}
|
||
|
||
function goBack() {
|
||
if (window.history.length > 1) {
|
||
window.history.back();
|
||
return;
|
||
}
|
||
var nextParams = new URLSearchParams(window.location.search);
|
||
nextParams.delete('mode');
|
||
var query = nextParams.toString();
|
||
var centerPath = '../host-center/index.html';
|
||
if (identity === 'BD') centerPath = '../bd-center/index.html';
|
||
if (identity === 'BD_LEADER')
|
||
centerPath = '../superadmin-center/index.html';
|
||
if (identity === 'AGENCY') centerPath = '../agency-center/index.html';
|
||
window.location.href = centerPath + (query ? '?' + query : '');
|
||
}
|
||
|
||
function bindEvents() {
|
||
function bindTap(id, handler) {
|
||
var node = $(id);
|
||
var last = 0;
|
||
function run(event) {
|
||
var now = Date.now();
|
||
if (now - last < 220) {
|
||
if (event) event.preventDefault();
|
||
return;
|
||
}
|
||
last = now;
|
||
// 只能在 click 阶段变更弹窗状态:WebView 里 pointerdown 先打开遮罩后,同一次触摸生成的 click 会落到新遮罩上,导致详情弹窗刚打开又被关闭。
|
||
if (event) event.preventDefault();
|
||
handler(event);
|
||
}
|
||
node.addEventListener('click', run);
|
||
}
|
||
|
||
bindTap('backButton', goBack);
|
||
document.querySelectorAll('.mode-tab').forEach(function (tab) {
|
||
tab.addEventListener('click', function () {
|
||
setMode(tab.getAttribute('data-mode'));
|
||
});
|
||
});
|
||
Object.keys(inputs).forEach(function (key) {
|
||
if (inputs[key])
|
||
inputs[key].addEventListener('input', normalizeAmount);
|
||
});
|
||
bindTap('allButton', function () {
|
||
inputs.withdraw.value = moneyInput(state.balance);
|
||
updateSubmit();
|
||
});
|
||
$('transferAccount').addEventListener('input', function () {
|
||
state.selectedPayee = null;
|
||
renderPayee();
|
||
window.clearTimeout(autoSearchTimer);
|
||
if (String($('transferAccount').value || '').trim().length >= 3) {
|
||
autoSearchTimer = window.setTimeout(searchPayee, 260);
|
||
}
|
||
});
|
||
$('transferAccount').addEventListener('keydown', function (event) {
|
||
if (event.key === 'Enter') searchPayee();
|
||
});
|
||
bindTap('searchPayeeButton', searchPayee);
|
||
bindTap('saveAddressButton', saveAddress);
|
||
bindTap('editAddressButton', editAddress);
|
||
bindTap('submitButton', submit);
|
||
bindTap('salaryHistoryButton', openHistory);
|
||
bindTap('salaryHistoryBackdrop', closeHistory);
|
||
bindTap('salaryHistoryCloseButton', closeHistory);
|
||
bindTap('contactBackdrop', closeContactModal);
|
||
bindTap('contactCloseButton', closeContactModal);
|
||
$('contactForm').addEventListener('submit', saveContact);
|
||
}
|
||
|
||
function renderStaticDynamicText() {
|
||
$('exchangeRateText').textContent = t(
|
||
'withdraw_exchange.exchange_rate_value',
|
||
'1$ = {amount} golds'
|
||
).replace('{amount}', EXCHANGE_RATE.toLocaleString('en-US'));
|
||
if (!$('salaryHistoryModal').hidden) renderHistory();
|
||
if (!$('contactModal').hidden && !state.isSavingContact) {
|
||
$('saveContactButton').textContent = t('contact_card.save', 'Save');
|
||
}
|
||
updateSubmit();
|
||
renderPayee();
|
||
}
|
||
|
||
function init() {
|
||
identity = normalizeIdentity(identity);
|
||
bindEvents();
|
||
setMode(currentMode);
|
||
renderStaticDynamicText();
|
||
loadOverview();
|
||
loadPaymentInfo();
|
||
loadContactInfo();
|
||
if (window.HyAppBridge) {
|
||
window.HyAppBridge.ready({
|
||
page: 'withdraw-exchange',
|
||
mock: isMock,
|
||
});
|
||
}
|
||
}
|
||
|
||
function loadOverview() {
|
||
if (isMock) {
|
||
mockRequest('balance').then(function (balance) {
|
||
state.balance = balance;
|
||
state.transferRateTiers = [
|
||
{
|
||
min_usd_minor: 100,
|
||
max_usd_minor: 1000,
|
||
coin_per_usd: 90000,
|
||
status: 'active',
|
||
},
|
||
{
|
||
min_usd_minor: 1100,
|
||
max_usd_minor: 5000,
|
||
coin_per_usd: 92000,
|
||
status: 'active',
|
||
},
|
||
];
|
||
renderBalance();
|
||
updateSubmit();
|
||
});
|
||
return;
|
||
}
|
||
if (!api()) {
|
||
toast(
|
||
t(
|
||
'withdraw_exchange.load_failed',
|
||
'Load failed. Try again later.'
|
||
)
|
||
);
|
||
return;
|
||
}
|
||
api()
|
||
.overview(identity)
|
||
.then(function (data) {
|
||
identity = normalizeIdentity(data.identity || identity);
|
||
salaryAssetType = data.salary_asset_type || salaryAssetType;
|
||
EXCHANGE_RATE = Number(data.exchange_rate || EXCHANGE_RATE);
|
||
var payment = paymentInfoFrom(data);
|
||
if (payment) state.paymentInfo = payment;
|
||
state.balance = usdMinorToNumber(
|
||
data.salary && data.salary.available_amount
|
||
);
|
||
state.transferRateTiers = data.transfer_rate_tiers || [];
|
||
renderStaticDynamicText();
|
||
renderBalance();
|
||
renderPaymentInfo();
|
||
updateSubmit();
|
||
})
|
||
.catch(function (err) {
|
||
toast(
|
||
err && err.message
|
||
? err.message
|
||
: t(
|
||
'withdraw_exchange.load_failed',
|
||
'Load failed. Try again later.'
|
||
)
|
||
);
|
||
});
|
||
}
|
||
|
||
function loadPaymentInfo() {
|
||
if (isMock) {
|
||
mockRequest('payment').then(function (payment) {
|
||
state.paymentInfo = payment;
|
||
renderPaymentInfo();
|
||
});
|
||
return;
|
||
}
|
||
state.paymentInfo = null;
|
||
state.isEditingAddress = false;
|
||
renderPaymentInfo();
|
||
if (!api() || !api().getWithdrawAddress) return;
|
||
api()
|
||
.getWithdrawAddress()
|
||
.then(function (data) {
|
||
state.paymentInfo = paymentInfoFrom(data);
|
||
renderPaymentInfo();
|
||
})
|
||
.catch(function () {
|
||
renderPaymentInfo();
|
||
});
|
||
}
|
||
|
||
function loadContactInfo() {
|
||
if (isMock) {
|
||
state.contactInfo = String(params.get('contact') || '').trim();
|
||
updateSubmit();
|
||
return;
|
||
}
|
||
var apiClient = userApi();
|
||
if (!apiClient || !apiClient.me) {
|
||
state.contactInfo = '';
|
||
updateSubmit();
|
||
return;
|
||
}
|
||
apiClient
|
||
.me()
|
||
.then(function (profile) {
|
||
state.contactInfo = contactFrom(profile);
|
||
updateSubmit();
|
||
})
|
||
.catch(function () {
|
||
state.contactInfo = '';
|
||
updateSubmit();
|
||
});
|
||
}
|
||
|
||
document.addEventListener('DOMContentLoaded', init);
|
||
window.addEventListener('hyapp:i18n-ready', renderStaticDynamicText);
|
||
})();
|