Autosave: 20260221-135325
This commit is contained in:
parent
d31b881957
commit
9a636e4275
@ -633,7 +633,7 @@ async function notifyMatchSuccess() {
|
|||||||
const r = await fetch('/api/admin_recharge.php?action=match_success', { method: 'POST', body: fd });
|
const r = await fetch('/api/admin_recharge.php?action=match_success', { method: 'POST', body: fd });
|
||||||
const res = await r.json();
|
const res = await r.json();
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
alert('匹配成功状态已更新');
|
alert('匹配成功!状态已更新为“匹配中”。若要向用户显示收款账户,请继续点击“发送账户”按钮。');
|
||||||
} else {
|
} else {
|
||||||
alert('错误: ' + res.error);
|
alert('错误: ' + res.error);
|
||||||
}
|
}
|
||||||
@ -647,33 +647,25 @@ async function sendPaymentInfo() {
|
|||||||
const account = document.getElementById('pay-account').value.trim();
|
const account = document.getElementById('pay-account').value.trim();
|
||||||
const amount = document.getElementById('pay-amount').value.trim();
|
const amount = document.getElementById('pay-amount').value.trim();
|
||||||
|
|
||||||
if (!bank || !name || !account || !amount) {
|
if (!bank || !name || !account) {
|
||||||
alert('请完整填写收款信息');
|
alert('请完整填写收款信息');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// First save the info (match success) then send it (status 2)
|
|
||||||
const fd = new URLSearchParams();
|
const fd = new URLSearchParams();
|
||||||
fd.append('user_id', selectedUser);
|
fd.append('user_id', selectedUser);
|
||||||
fd.append('bank', bank);
|
fd.append('bank', bank);
|
||||||
fd.append('name', name);
|
fd.append('name', name);
|
||||||
fd.append('account', account);
|
fd.append('account', account);
|
||||||
fd.append('amount', amount);
|
if (amount) fd.append('amount', amount);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Step 1: Save info and set status 1
|
console.log('Sending account info...', { bank, name, account, amount });
|
||||||
let r = await fetch('/api/admin_recharge.php?action=match_success', { method: 'POST', body: fd });
|
const r = await fetch('/api/admin_recharge.php?action=send_account', { method: 'POST', body: fd });
|
||||||
let res = await r.json();
|
const res = await r.json();
|
||||||
if (!res.success) {
|
|
||||||
alert('保存信息失败: ' + res.error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 2: Set status 2
|
|
||||||
r = await fetch('/api/admin_recharge.php?action=send_account', { method: 'POST', body: fd });
|
|
||||||
res = await r.json();
|
|
||||||
|
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
|
console.log('Account sent successfully');
|
||||||
if (paymentModal) paymentModal.hide();
|
if (paymentModal) paymentModal.hide();
|
||||||
// Clear inputs
|
// Clear inputs
|
||||||
document.getElementById('pay-bank').value = '';
|
document.getElementById('pay-bank').value = '';
|
||||||
@ -681,11 +673,13 @@ async function sendPaymentInfo() {
|
|||||||
document.getElementById('pay-account').value = '';
|
document.getElementById('pay-account').value = '';
|
||||||
document.getElementById('pay-amount').value = '';
|
document.getElementById('pay-amount').value = '';
|
||||||
document.getElementById('pay-note').value = '';
|
document.getElementById('pay-note').value = '';
|
||||||
alert('账户信息已保存,系统将通过订单状态自动更新用户页面');
|
alert('账户信息已发送,用户页面将立即显示收款账户');
|
||||||
} else {
|
} else {
|
||||||
|
console.error('Send failed:', res.error);
|
||||||
alert('发送失败: ' + res.error);
|
alert('发送失败: ' + res.error);
|
||||||
}
|
}
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
|
console.error('Network error:', err);
|
||||||
alert('网络请求失败');
|
alert('网络请求失败');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -47,8 +47,23 @@ try {
|
|||||||
echo json_encode(['success' => true]);
|
echo json_encode(['success' => true]);
|
||||||
}
|
}
|
||||||
elseif ($action === 'send_account') {
|
elseif ($action === 'send_account') {
|
||||||
$stmt = $db->prepare("UPDATE finance_requests SET status = 2 WHERE id = ?");
|
$bank = $_POST['bank'] ?? '';
|
||||||
$stmt->execute([$order_id]);
|
$name = $_POST['name'] ?? '';
|
||||||
|
$account = $_POST['account'] ?? '';
|
||||||
|
$amount = isset($_POST['amount']) ? (float)$_POST['amount'] : null;
|
||||||
|
|
||||||
|
if ($bank && $name && $account) {
|
||||||
|
if ($amount !== null) {
|
||||||
|
$stmt = $db->prepare("UPDATE finance_requests SET status = 2, account_bank = ?, account_name = ?, account_number = ?, amount = ? WHERE id = ?");
|
||||||
|
$stmt->execute([$bank, $name, $account, $amount, $order_id]);
|
||||||
|
} else {
|
||||||
|
$stmt = $db->prepare("UPDATE finance_requests SET status = 2, account_bank = ?, account_name = ?, account_number = ? WHERE id = ?");
|
||||||
|
$stmt->execute([$bank, $name, $account, $order_id]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$stmt = $db->prepare("UPDATE finance_requests SET status = 2 WHERE id = ?");
|
||||||
|
$stmt->execute([$order_id]);
|
||||||
|
}
|
||||||
echo json_encode(['success' => true]);
|
echo json_encode(['success' => true]);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/../db/config.php';
|
require_once __DIR__ . '/../db/config.php';
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
header('Cache-Control: no-cache, no-store, must-revalidate');
|
||||||
|
header('Pragma: no-cache');
|
||||||
|
header('Expires: 0');
|
||||||
|
|
||||||
if (session_status() === PHP_SESSION_NONE) session_start();
|
if (session_status() === PHP_SESSION_NONE) session_start();
|
||||||
|
|
||||||
|
|||||||
BIN
assets/pasted-20260221-130500-ea7d4319.png
Normal file
BIN
assets/pasted-20260221-130500-ea7d4319.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 107 KiB |
BIN
assets/pasted-20260221-133334-6ea3f71f.png
Normal file
BIN
assets/pasted-20260221-133334-6ea3f71f.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
BIN
assets/pasted-20260221-134832-445f385a.png
Normal file
BIN
assets/pasted-20260221-134832-445f385a.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
@ -550,16 +550,11 @@ $translations = [
|
|||||||
'unknown_error' => '发生未知错误',
|
'unknown_error' => '发生未知错误',
|
||||||
'rate_fetch_failed' => '获取汇率失败(服务商问题)',
|
'rate_fetch_failed' => '获取汇率失败(服务商问题)',
|
||||||
'matching_account' => '正在为您匹配充值账户',
|
'matching_account' => '正在为您匹配充值账户',
|
||||||
'matching_desc' => '系统正在为您匹配充值账户,请耐心等待 匹配期间请勿刷新页面或重复提交订单 若超过倒计时仍未匹配成功,请及时联系在线客服',
|
'matching_desc' => '系统正在为您分配专属收款账户,请耐心等待。 匹配成功后,页面将自动更新收款信息。 请勿关闭当前页面。',
|
||||||
'security_instructions' => '安全说明',
|
'matched_successfully' => '匹配成功',
|
||||||
'security_tip_1' => '请在倒计时结束前完成充值',
|
'matched_desc_short' => '您的充值订单已匹配成功。 客服正在为您发送收款账户信息,请稍候。 页面将自动显示收款账户,请勿刷新或关闭页面。',
|
||||||
'security_tip_2' => '转账时请务必备注您的用户ID',
|
'recharge_final_title' => '请按照以下账户信息进行转账',
|
||||||
'security_tip_3' => '如有任何疑问请及时联系客服',
|
'recharge_final_notice' => '请严格按照页面展示的收款账户信息进行转账。 请勿分笔转账或修改信息,转账完成后,请点击“完成转账”按钮。 转账完成后请提交转账凭证给在线客服,方便第一时间为你确认到账。',
|
||||||
'safe_payment' => '安全支付',
|
|
||||||
'instant_confirmation' => '即时确认',
|
|
||||||
'remaining_time' => '剩余时间',
|
|
||||||
'account_matched' => '匹配成功',
|
|
||||||
'account_matched_desc' => '匹配成功,请按页面信息完成单笔全额转账 转账完成后请将凭证提交给在线客服 核实通过后系统自动入账',
|
|
||||||
'bank_name' => '银行名称',
|
'bank_name' => '银行名称',
|
||||||
'payee_name' => '收款姓名',
|
'payee_name' => '收款姓名',
|
||||||
'account_number' => '收款账户',
|
'account_number' => '收款账户',
|
||||||
|
|||||||
721
recharge.php
721
recharge.php
@ -454,6 +454,11 @@ $bep20_addr = $settings['usdt_bep20_address'] ?? '0x742d35Cc6634C0532925a3b844Bc
|
|||||||
<script>
|
<script>
|
||||||
let currentNetwork = 'TRC20';
|
let currentNetwork = 'TRC20';
|
||||||
let currentAddress = '<?= $trc20_addr ?>';
|
let currentAddress = '<?= $trc20_addr ?>';
|
||||||
|
const userId = '<?= $user['uid'] ?? $user['id'] ?>';
|
||||||
|
let rechargeCountdownInterval;
|
||||||
|
let modalChatLastIds = new Set();
|
||||||
|
let remainingSeconds = 1800;
|
||||||
|
let modalChatPolling = false;
|
||||||
|
|
||||||
function notify(icon, title, text = '') {
|
function notify(icon, title, text = '') {
|
||||||
return Swal.fire({
|
return Swal.fire({
|
||||||
@ -487,8 +492,6 @@ function calculateUSDT() {
|
|||||||
function selectNetwork(net, addr) {
|
function selectNetwork(net, addr) {
|
||||||
currentNetwork = net;
|
currentNetwork = net;
|
||||||
currentAddress = addr;
|
currentAddress = addr;
|
||||||
|
|
||||||
// Update UI
|
|
||||||
const btns = document.querySelectorAll('#networkSelectors button');
|
const btns = document.querySelectorAll('#networkSelectors button');
|
||||||
btns.forEach(btn => {
|
btns.forEach(btn => {
|
||||||
if (btn.innerText === net) {
|
if (btn.innerText === net) {
|
||||||
@ -499,16 +502,10 @@ function selectNetwork(net, addr) {
|
|||||||
btn.classList.add('btn-outline-secondary');
|
btn.classList.add('btn-outline-secondary');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('cryptoAddress').value = addr;
|
document.getElementById('cryptoAddress').value = addr;
|
||||||
document.getElementById('qrCode').src = `https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=${addr}`;
|
document.getElementById('qrCode').src = `https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=${addr}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const userId = '<?= $user['uid'] ?? $user['id'] ?>';
|
|
||||||
let rechargeCountdownInterval;
|
|
||||||
let modalChatLastIds = new Set();
|
|
||||||
let remainingSeconds = 1800;
|
|
||||||
|
|
||||||
function saveRechargeState(state) {
|
function saveRechargeState(state) {
|
||||||
localStorage.setItem('recharge_state', JSON.stringify({
|
localStorage.setItem('recharge_state', JSON.stringify({
|
||||||
...state,
|
...state,
|
||||||
@ -520,586 +517,326 @@ function saveRechargeState(state) {
|
|||||||
function clearRechargeState() {
|
function clearRechargeState() {
|
||||||
localStorage.removeItem('recharge_state');
|
localStorage.removeItem('recharge_state');
|
||||||
if (rechargeCountdownInterval) clearInterval(rechargeCountdownInterval);
|
if (rechargeCountdownInterval) clearInterval(rechargeCountdownInterval);
|
||||||
|
if (window.statusPollingInterval) clearInterval(window.statusPollingInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
function finishTransfer() {
|
function finishTransfer() {
|
||||||
const state = JSON.parse(localStorage.getItem('recharge_state') || '{}');
|
const state = JSON.parse(localStorage.getItem('recharge_state') || '{}');
|
||||||
const orderId = state.orderId;
|
const orderId = state.orderId;
|
||||||
|
|
||||||
if (orderId) {
|
if (orderId) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('action', 'complete_transfer');
|
formData.append('action', 'complete_transfer');
|
||||||
formData.append('order_id', orderId);
|
formData.append('order_id', orderId);
|
||||||
|
fetch('/api/finance.php', { method: 'POST', body: formData })
|
||||||
fetch('/api/finance.php', {
|
.then(r => r.json())
|
||||||
method: 'POST',
|
.then(data => { if (data.success) finishTransferUI(); });
|
||||||
body: formData
|
|
||||||
}).then(r => r.json()).then(data => {
|
|
||||||
if (data.success) {
|
|
||||||
clearRechargeState();
|
|
||||||
bootstrap.Modal.getInstance(document.getElementById('rechargeModal'))?.hide();
|
|
||||||
notify('success', '<?= __("recharge_success_title") ?>', '<?= __("recharge_success_text") ?>');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
clearRechargeState();
|
finishTransferUI();
|
||||||
bootstrap.Modal.getInstance(document.getElementById('rechargeModal'))?.hide();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function finishTransferUI() {
|
||||||
|
clearRechargeState();
|
||||||
|
const modalEl = document.getElementById('rechargeModal');
|
||||||
|
const modalInstance = bootstrap.Modal.getInstance(modalEl);
|
||||||
|
if (modalInstance) modalInstance.hide();
|
||||||
|
notify('success', '<?= __("recharge_success_title") ?>', '<?= __("recharge_success_text") ?>');
|
||||||
|
}
|
||||||
|
|
||||||
function openRechargeModal(initialMessage, isRestore = false, orderId = null) {
|
function openRechargeModal(initialMessage, isRestore = false, orderId = null) {
|
||||||
const modalElement = document.getElementById('rechargeModal');
|
const modalElement = document.getElementById('rechargeModal');
|
||||||
|
if (!modalElement) return;
|
||||||
const modal = new bootstrap.Modal(modalElement);
|
const modal = new bootstrap.Modal(modalElement);
|
||||||
modal.show();
|
modal.show();
|
||||||
|
|
||||||
|
const currentOrderId = orderId || JSON.parse(localStorage.getItem('recharge_state') || '{}').orderId;
|
||||||
|
|
||||||
if (!isRestore) {
|
if (!isRestore) {
|
||||||
remainingSeconds = 1800;
|
remainingSeconds = 1800;
|
||||||
saveRechargeState({ phase: 'matching', initialMessage, orderId });
|
saveRechargeState({ phase: 'pending', initialMessage, orderId: currentOrderId });
|
||||||
|
sendModalMessage(initialMessage); // Automatically notify admin via chat
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start status polling if we have an orderId
|
if (currentOrderId) startStatusPolling(currentOrderId);
|
||||||
const currentOrderId = orderId || JSON.parse(localStorage.getItem('recharge_state') || '{}').orderId;
|
|
||||||
if (currentOrderId) {
|
|
||||||
startStatusPolling(currentOrderId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start countdown
|
|
||||||
if (rechargeCountdownInterval) clearInterval(rechargeCountdownInterval);
|
if (rechargeCountdownInterval) clearInterval(rechargeCountdownInterval);
|
||||||
rechargeCountdownInterval = setInterval(() => {
|
rechargeCountdownInterval = setInterval(() => {
|
||||||
const display = document.getElementById('modal-countdown');
|
const display = document.querySelectorAll('#modal-countdown');
|
||||||
let mins = Math.floor(remainingSeconds / 60);
|
let mins = Math.floor(remainingSeconds / 60);
|
||||||
let secs = remainingSeconds % 60;
|
let secs = remainingSeconds % 60;
|
||||||
if (display) display.innerText = `${mins}:${secs < 10 ? '0' : ''}${secs}`;
|
const timeStr = `${mins}:${secs < 10 ? '0' : ''}${secs}`;
|
||||||
|
display.forEach(d => d.innerText = timeStr);
|
||||||
|
|
||||||
const state = JSON.parse(localStorage.getItem('recharge_state') || '{}');
|
const state = JSON.parse(localStorage.getItem('recharge_state') || '{}');
|
||||||
state.remainingSeconds = remainingSeconds;
|
state.remainingSeconds = remainingSeconds;
|
||||||
localStorage.setItem('recharge_state', JSON.stringify(state));
|
localStorage.setItem('recharge_state', JSON.stringify(state));
|
||||||
|
if (--remainingSeconds < 0) clearInterval(rechargeCountdownInterval);
|
||||||
if (--remainingSeconds < 0) {
|
|
||||||
clearInterval(rechargeCountdownInterval);
|
|
||||||
}
|
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
// Clear chat last ids for new session if not restoring
|
if (!isRestore) renderRechargeUI({ status: 0 });
|
||||||
if (!isRestore) {
|
|
||||||
modalChatLastIds.clear();
|
|
||||||
document.getElementById('modal-chat-messages').innerHTML = `
|
|
||||||
<div class="text-center text-muted small mb-4 py-3 bg-white bg-opacity-5 rounded-3 border border-white border-opacity-5">
|
|
||||||
<i class="bi bi-shield-lock-fill text-success me-2"></i><?= __('welcome_support') ?>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
// Ping first to register visitor, then send message to ensure visibility
|
|
||||||
const userTime = new Date().toLocaleString('zh-CN');
|
|
||||||
fetch(`/api/chat.php?action=ping&user_time=${encodeURIComponent(userTime)}`)
|
|
||||||
.then(() => {
|
|
||||||
// Append locally first
|
|
||||||
appendModalMessage({
|
|
||||||
id: 'modal_temp_init_' + Date.now(),
|
|
||||||
sender: 'user',
|
|
||||||
message: initialMessage,
|
|
||||||
created_at: new Date().toISOString()
|
|
||||||
});
|
|
||||||
// Then send to server
|
|
||||||
return sendModalMessage(initialMessage);
|
|
||||||
})
|
|
||||||
.catch(err => console.error('Initial sequence failed:', err));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start polling for modal
|
|
||||||
initModalChat();
|
initModalChat();
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
function startStatusPolling(orderId) {
|
||||||
|
if (window.statusPollingInterval) clearInterval(window.statusPollingInterval);
|
||||||
|
const checkStatus = async () => {
|
||||||
|
const modalEl = document.getElementById('rechargeModal');
|
||||||
|
if (!modalEl || !modalEl.classList.contains('show')) return;
|
||||||
|
try {
|
||||||
|
const r = await fetch(`/api/recharge_status.php?id=${orderId}&_t=${Date.now()}`);
|
||||||
|
const data = await r.json();
|
||||||
|
if (data.success) {
|
||||||
|
console.log('Order status update:', data.status, data);
|
||||||
|
renderRechargeUI(data);
|
||||||
|
if (parseInt(data.status) === 3) clearInterval(window.statusPollingInterval);
|
||||||
|
}
|
||||||
|
} catch (e) { console.error('Status polling error:', e); }
|
||||||
|
};
|
||||||
|
checkStatus();
|
||||||
|
window.statusPollingInterval = setInterval(checkStatus, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderRechargeUI(data) {
|
||||||
|
const side = document.querySelector('.info-side');
|
||||||
|
if (!side) return;
|
||||||
|
const status = parseInt(data.status);
|
||||||
|
if (status === 3) { finishTransferUI(); return; }
|
||||||
|
|
||||||
|
if (status === 0) {
|
||||||
|
side.innerHTML = `
|
||||||
|
<div class="text-center text-lg-start position-relative" style="z-index: 2;">
|
||||||
|
<div class="mb-4 text-center">
|
||||||
|
<div class="d-inline-flex align-items-center gap-2 px-3 py-2 rounded-pill bg-primary bg-opacity-10 text-primary small fw-bold mb-3 border border-primary border-opacity-10" style="color: #ff4d94 !important; border-color: #ff4d94 !important;">
|
||||||
|
<span class="pulse-dot-pink"></span> <span style="letter-spacing: 1px;"><?= __('waiting_allocation') ?></span>
|
||||||
|
</div>
|
||||||
|
<h2 class="fw-bold text-dark mb-3" style="font-size: 2rem;"><?= __('matching_account') ?></h2>
|
||||||
|
<p class="text-muted fw-medium mb-0" style="font-size: 15px;"><?= __('matching_desc') ?></p>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4 py-4 px-4 rounded-4 border border-light shadow-sm" style="background: #fff0f5;">
|
||||||
|
<div class="row align-items-center text-center">
|
||||||
|
<div class="col-12 mb-3">
|
||||||
|
<div class="text-muted small mb-1 fw-bold"><?= __('waiting_countdown') ?></div>
|
||||||
|
<div class="display-5 fw-bold text-primary mb-0" id="modal-countdown" style="font-family: monospace; color: #ff4d94 !important;">30:00</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="p-4 rounded-4 bg-light border border-light">
|
||||||
|
<h6 class="text-dark fw-bold mb-3 d-flex align-items-center gap-2"><i class="bi bi-shield-lock-fill text-primary" style="color: #ff4d94 !important;"></i> <?= __('security_tips') ?></h6>
|
||||||
|
<div class="text-muted small lh-lg">
|
||||||
|
<div class="mb-2 d-flex gap-2"><i class="bi bi-check2-circle text-primary" style="color: #ff4d94 !important;"></i> <span><?= __('recharge_instruction_1') ?></span></div>
|
||||||
|
<div class="mb-2 d-flex gap-2"><i class="bi bi-check2-circle text-primary" style="color: #ff4d94 !important;"></i> <span><?= __('recharge_instruction_2') ?></span></div>
|
||||||
|
<div class="d-flex gap-2"><i class="bi bi-check2-circle text-primary" style="color: #ff4d94 !important;"></i> <span><?= __('recharge_instruction_3') ?></span></div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4"><button type="button" class="btn btn-primary w-100 rounded-pill py-2 fw-bold opacity-50" disabled style="background: #ff4d94 !important; border: none;"><?= __('waiting_system_allocation') ?>...</button></div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
} else if (status === 1) {
|
||||||
|
side.innerHTML = `
|
||||||
|
<div class="text-center text-lg-start position-relative" style="z-index: 2;">
|
||||||
|
<div class="mb-4 text-center">
|
||||||
|
<div class="d-inline-flex align-items-center gap-2 px-3 py-2 rounded-pill bg-success bg-opacity-10 text-success small fw-bold mb-3 border border-success border-opacity-10">
|
||||||
|
<i class="bi bi-check-circle-fill text-success"></i> <?= __("matched_successfully") ?>
|
||||||
|
</div>
|
||||||
|
<h2 class="fw-bold text-dark mb-3" style="font-size: 2rem;"><?= __("matched_successfully") ?></h2>
|
||||||
|
<p class="text-muted fw-medium mb-0" style="font-size: 15px;"><?= __("matched_desc_short") ?></p>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4 py-4 px-4 rounded-4 border border-light shadow-sm" style="background: #fff0f5;">
|
||||||
|
<div class="row align-items-center text-center">
|
||||||
|
<div class="col-12 mb-3">
|
||||||
|
<div class="text-muted small mb-1 fw-bold"><?= __('waiting_countdown') ?></div>
|
||||||
|
<div class="display-5 fw-bold text-primary mb-0" id="modal-countdown" style="font-family: monospace; color: #ff4d94 !important;">30:00</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="p-4 rounded-4 bg-light border border-light">
|
||||||
|
<h6 class="text-dark fw-bold mb-3 d-flex align-items-center gap-2"><i class="bi bi-shield-lock-fill text-primary" style="color: #ff4d94 !important;"></i> <?= __('security_tips') ?></h6>
|
||||||
|
<div class="mt-4"><button type="button" class="btn btn-primary w-100 rounded-pill py-2 fw-bold opacity-50" disabled style="background: #ff4d94 !important; border: none;"><?= __("getting_account_details") ?>...</button></div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
} else if (status === 2) {
|
||||||
|
const bank = data.account_bank || '---';
|
||||||
|
const account = data.account_number || '---';
|
||||||
|
const name = data.account_name || '---';
|
||||||
|
side.innerHTML = `
|
||||||
|
<div class="text-center text-lg-start">
|
||||||
|
<div class="mb-4 text-center">
|
||||||
|
<div class="d-inline-flex align-items-center gap-2 px-3 py-1 rounded-pill bg-success bg-opacity-10 text-success small fw-bold mb-3 border border-success border-opacity-10"><i class="bi bi-check-circle-fill text-success"></i> <?= __("matched_successfully") ?></div>
|
||||||
|
<h2 class="fw-bold text-dark mb-2" style="font-size: 1.8rem;"><?= __("recharge_final_title") ?></h2>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 p-4 rounded-4 shadow-sm border border-light" style="background: #fff0f5;">
|
||||||
|
<div class="d-flex flex-column gap-3">
|
||||||
|
<div class="payment-item">
|
||||||
|
<div class="text-muted small mb-1 fw-bold text-start"><?= __("receiving_bank") ?>:</div>
|
||||||
|
<div class="d-flex justify-content-between align-items-center gap-2">
|
||||||
|
<div class="h6 mb-0 fw-bold text-dark text-start" style="word-break: break-all; font-size: 1.1rem;">${bank}</div>
|
||||||
|
<button class="btn btn-sm rounded-pill px-3 fw-bold flex-shrink-0" style="font-size: 10px; background: #ff4d94; color: white;" onclick="copyText('${bank}')"><?= __("copy") ?></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="payment-item border-top border-white border-opacity-50 pt-2">
|
||||||
|
<div class="text-muted small mb-1 fw-bold text-start"><?= __("receiving_account") ?>:</div>
|
||||||
|
<div class="d-flex justify-content-between align-items-center gap-2">
|
||||||
|
<div class="h5 mb-0 fw-bold text-start" style="word-break: break-all; font-family: monospace; font-size: 1.3rem; color: #ff4d94;">${account}</div>
|
||||||
|
<button class="btn btn-sm rounded-pill px-3 fw-bold flex-shrink-0" style="font-size: 10px; background: #ff4d94; color: white;" onclick="copyText('${account}')"><?= __("copy") ?></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="payment-item border-top border-white border-opacity-50 pt-2">
|
||||||
|
<div class="text-muted small mb-1 fw-bold text-start"><?= __("receiving_name") ?>:</div>
|
||||||
|
<div class="d-flex justify-content-between align-items-center gap-2">
|
||||||
|
<div class="h6 mb-0 fw-bold text-dark text-start" style="word-break: break-all; font-size: 1.1rem;">${name}</div>
|
||||||
|
<button class="btn btn-sm rounded-pill px-3 fw-bold flex-shrink-0" style="font-size: 10px; background: #ff4d94; color: white;" onclick="copyText('${name}')"><?= __("copy") ?></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 py-3 px-4 rounded-4 shadow-sm border border-light" style="background: #ffffff;">
|
||||||
|
<div class="row align-items-center text-center text-lg-start">
|
||||||
|
<div class="col-12 col-lg-7 mb-2 mb-lg-0">
|
||||||
|
<div class="text-muted small mb-0 fw-bold"><?= __("remaining_time") ?></div>
|
||||||
|
<div class="h2 fw-bold mb-0" id="modal-countdown" style="font-family: monospace; color: #ff4d94;">--:--</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-lg-5 border-start-lg border-light ps-lg-3">
|
||||||
|
<div class="small fw-bold" style="color: #ff4d94;"><i class="bi bi-shield-check me-1"></i><?= __("secure_pay") ?></div>
|
||||||
|
<div class="text-muted" style="font-size: 10px;"><?= __("encrypted_channel") ?></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="p-2 text-center mb-3"><p class="text-muted small fw-bold mb-0"><?= __("recharge_final_notice") ?></p></div>
|
||||||
|
<button type="button" class="btn btn-primary w-100 rounded-pill py-3 fw-bold shadow-sm" onclick="finishTransfer()" style="background: #ff4d94 !important; border: none; color: white;"><?= __("complete_transfer") ?></button>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync countdown if exists
|
||||||
|
if (remainingSeconds > 0) {
|
||||||
|
let mins = Math.floor(remainingSeconds / 60), secs = remainingSeconds % 60;
|
||||||
|
const timeStr = `${mins}:${secs < 10 ? '0' : ''}${secs}`;
|
||||||
|
document.querySelectorAll('#modal-countdown').forEach(d => d.innerText = timeStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
const savedState = localStorage.getItem('recharge_state');
|
const savedState = localStorage.getItem('recharge_state');
|
||||||
if (savedState) {
|
if (savedState) {
|
||||||
const state = JSON.parse(savedState);
|
const state = JSON.parse(savedState);
|
||||||
const elapsed = Math.floor((Date.now() - state.timestamp) / 1000);
|
const elapsed = Math.floor((Date.now() - state.timestamp) / 1000);
|
||||||
remainingSeconds = (state.remainingSeconds || 1800) - elapsed;
|
remainingSeconds = (state.remainingSeconds || 1800) - elapsed;
|
||||||
|
if (remainingSeconds > 0 && state.orderId) {
|
||||||
if (remainingSeconds > 0) {
|
openRechargeModal(state.initialMessage, true, state.orderId);
|
||||||
if (state.phase === 'matched') {
|
try {
|
||||||
openRechargeModal(state.initialMessage, true);
|
const r = await fetch(`/api/recharge_status.php?id=${state.orderId}&_t=${Date.now()}`);
|
||||||
updateMatchingSide(state.info, true);
|
const data = await r.json();
|
||||||
} else if (state.phase === 'matched_status') {
|
if (data.success) renderRechargeUI(data);
|
||||||
openRechargeModal(state.initialMessage, true);
|
} catch (e) {}
|
||||||
updateMatchingStatus('matched');
|
} else { localStorage.removeItem('recharge_state'); }
|
||||||
} else {
|
|
||||||
openRechargeModal(state.initialMessage, true);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
localStorage.removeItem('recharge_state');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let modalChatPolling = false;
|
|
||||||
|
|
||||||
function initModalChat() {
|
function initModalChat() {
|
||||||
if (modalChatPolling) return; // Prevent multiple polling loops
|
if (modalChatPolling) return;
|
||||||
modalChatPolling = true;
|
modalChatPolling = true;
|
||||||
|
const modalChatForm = document.getElementById('modal-chat-form'), modalChatInput = document.getElementById('modal-chat-input');
|
||||||
const modalChatForm = document.getElementById('modal-chat-form');
|
const modalChatUpload = document.getElementById('modal-chat-upload'), modalChatFile = document.getElementById('modal-chat-file');
|
||||||
const modalChatInput = document.getElementById('modal-chat-input');
|
if (!modalChatForm || !modalChatUpload || !modalChatFile) return;
|
||||||
const modalChatUpload = document.getElementById('modal-chat-upload');
|
|
||||||
const modalChatFile = document.getElementById('modal-chat-file');
|
|
||||||
|
|
||||||
modalChatUpload.onclick = () => modalChatFile.click();
|
modalChatUpload.onclick = () => modalChatFile.click();
|
||||||
|
|
||||||
modalChatFile.onchange = async () => {
|
modalChatFile.onchange = async () => {
|
||||||
if (!modalChatFile.files[0]) return;
|
if (!modalChatFile.files[0]) return;
|
||||||
const file = modalChatFile.files[0];
|
const file = modalChatFile.files[0], tempId = 'modal_temp_img_' + Date.now(), localUrl = URL.createObjectURL(file);
|
||||||
|
appendModalMessage({ id: tempId, sender: 'user', message: `<img src="${localUrl}" class="img-fluid rounded" style="max-height: 250px; opacity: 0.6;">`, created_at: new Date().toISOString() });
|
||||||
const tempId = 'modal_temp_img_' + Date.now();
|
|
||||||
const localUrl = URL.createObjectURL(file);
|
|
||||||
appendModalMessage({
|
|
||||||
id: tempId,
|
|
||||||
sender: 'user',
|
|
||||||
message: `<img src="${localUrl}" class="img-fluid rounded chat-img-preview" style="max-width: 100%; max-height: 250px; opacity: 0.6;">`,
|
|
||||||
created_at: new Date().toISOString()
|
|
||||||
});
|
|
||||||
scrollModalToBottom();
|
scrollModalToBottom();
|
||||||
|
const formData = new FormData(); formData.append('file', file); formData.append('action', 'upload_image');
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('file', file);
|
|
||||||
formData.append('action', 'upload_image');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const resp = await fetch('/api/chat.php', { method: 'POST', body: formData });
|
const resp = await fetch('/api/chat.php', { method: 'POST', body: formData }), data = await resp.json();
|
||||||
const data = await resp.json();
|
|
||||||
document.querySelector(`[data-modal-id="${tempId}"]`)?.remove();
|
document.querySelector(`[data-modal-id="${tempId}"]`)?.remove();
|
||||||
if (data.success) {
|
if (data.success) { appendModalMessage(data.message); scrollModalToBottom(); }
|
||||||
appendModalMessage(data.message);
|
|
||||||
scrollModalToBottom();
|
|
||||||
}
|
|
||||||
} catch (err) { console.error(err); }
|
} catch (err) { console.error(err); }
|
||||||
modalChatFile.value = '';
|
modalChatFile.value = ''; setTimeout(() => URL.revokeObjectURL(localUrl), 5000);
|
||||||
setTimeout(() => URL.revokeObjectURL(localUrl), 5000);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
modalChatForm.onsubmit = async (e) => {
|
modalChatForm.onsubmit = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault(); const msg = modalChatInput.value.trim(); if (!msg) return;
|
||||||
const msg = modalChatInput.value.trim();
|
modalChatInput.value = ''; const tempId = 'modal_temp_msg_' + Date.now();
|
||||||
if (!msg) return;
|
appendModalMessage({ id: tempId, sender: 'user', message: msg, created_at: new Date().toISOString() });
|
||||||
modalChatInput.value = '';
|
|
||||||
|
|
||||||
const tempId = 'modal_temp_msg_' + Date.now();
|
|
||||||
appendModalMessage({
|
|
||||||
id: tempId,
|
|
||||||
sender: 'user',
|
|
||||||
message: msg,
|
|
||||||
created_at: new Date().toISOString()
|
|
||||||
});
|
|
||||||
scrollModalToBottom();
|
scrollModalToBottom();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const resp = await fetch('/api/chat.php?action=send_message', {
|
const resp = await fetch('/api/chat.php?action=send_message', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: `message=${encodeURIComponent(msg)}` });
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
||||||
body: `message=${encodeURIComponent(msg)}`
|
|
||||||
});
|
|
||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
document.querySelector(`[data-modal-id="${tempId}"]`)?.remove();
|
document.querySelector(`[data-modal-id="${tempId}"]`)?.remove();
|
||||||
if (data.success) {
|
if (data.success) { appendModalMessage(data.message); scrollModalToBottom(); }
|
||||||
appendModalMessage(data.message);
|
|
||||||
scrollModalToBottom();
|
|
||||||
}
|
|
||||||
} catch (err) { console.error(err); }
|
} catch (err) { console.error(err); }
|
||||||
};
|
};
|
||||||
|
|
||||||
// Modal Polling
|
|
||||||
const modalPoll = async () => {
|
const modalPoll = async () => {
|
||||||
if (!document.getElementById('rechargeModal').classList.contains('show')) return;
|
if (!document.getElementById('rechargeModal').classList.contains('show')) {
|
||||||
|
modalChatPolling = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
// Periodic ping to keep session alive and visitor status active
|
|
||||||
fetch(`/api/chat.php?action=ping&user_time=${encodeURIComponent(new Date().toLocaleString())}`);
|
fetch(`/api/chat.php?action=ping&user_time=${encodeURIComponent(new Date().toLocaleString())}`);
|
||||||
|
const resp = await fetch('/api/chat.php?action=get_messages'), data = await resp.json();
|
||||||
const resp = await fetch('/api/chat.php?action=get_messages');
|
if (Array.isArray(data)) { data.forEach(m => { if (!modalChatLastIds.has(m.id)) { appendModalMessage(m); modalChatLastIds.add(m.id); scrollModalToBottom(); } }); }
|
||||||
const data = await resp.json();
|
|
||||||
if (Array.isArray(data)) {
|
|
||||||
data.forEach(m => {
|
|
||||||
if (!modalChatLastIds.has(m.id)) {
|
|
||||||
appendModalMessage(m);
|
|
||||||
modalChatLastIds.add(m.id);
|
|
||||||
scrollModalToBottom();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
setTimeout(modalPoll, 1000);
|
setTimeout(modalPoll, 2000);
|
||||||
};
|
};
|
||||||
modalPoll();
|
modalPoll();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendModalMessage(msg) {
|
async function sendModalMessage(msg) {
|
||||||
try {
|
try { await fetch('/api/chat.php?action=send_message', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: `message=${encodeURIComponent(msg)}` }); } catch (err) {}
|
||||||
await fetch('/api/chat.php?action=send_message', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
||||||
body: `message=${encodeURIComponent(msg)}`
|
|
||||||
});
|
|
||||||
} catch (err) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
function startStatusPolling(orderId) {
|
|
||||||
if (window.statusPollingInterval) clearInterval(window.statusPollingInterval);
|
|
||||||
window.statusPollingInterval = setInterval(async () => {
|
|
||||||
try {
|
|
||||||
const r = await fetch(`/api/recharge_status.php?id=${orderId}`);
|
|
||||||
const data = await r.json();
|
|
||||||
if (data.success) {
|
|
||||||
const state = JSON.parse(localStorage.getItem('recharge_state') || '{}');
|
|
||||||
|
|
||||||
if (data.status == 1 && state.phase !== 'matched_status' && state.phase !== 'matched') {
|
|
||||||
updateMatchingStatus('matched');
|
|
||||||
saveRechargeState({ ...state, phase: 'matched_status', orderId: orderId });
|
|
||||||
} else if (data.status == 2 && state.phase !== 'matched') {
|
|
||||||
updateMatchingSide({
|
|
||||||
bank: data.account_bank,
|
|
||||||
account: data.account_number,
|
|
||||||
name: data.account_name
|
|
||||||
});
|
|
||||||
saveRechargeState({
|
|
||||||
...state,
|
|
||||||
phase: 'matched',
|
|
||||||
orderId: orderId,
|
|
||||||
info: {
|
|
||||||
bank: data.account_bank,
|
|
||||||
account: data.account_number,
|
|
||||||
name: data.account_name
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (data.status == 3) {
|
|
||||||
finishTransferUI();
|
|
||||||
clearInterval(window.statusPollingInterval);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function finishTransferUI() {
|
|
||||||
clearRechargeState();
|
|
||||||
bootstrap.Modal.getInstance(document.getElementById('rechargeModal'))?.hide();
|
|
||||||
notify('success', '<?= __("recharge_success_title") ?>', '<?= __("recharge_success_text") ?>');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function appendModalMessage(m) {
|
function appendModalMessage(m) {
|
||||||
const container = document.getElementById('modal-chat-messages');
|
const container = document.getElementById('modal-chat-messages');
|
||||||
if (!container || document.querySelector(`[data-modal-id="${m.id}"]`)) return;
|
if (!container || document.querySelector(`[data-modal-id="${m.id}"]`)) return;
|
||||||
|
const sender = m.sender; let text = (m.message || '').toString(), displayMsg = text;
|
||||||
const sender = m.sender;
|
|
||||||
let text = (m.message || '').toString();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let displayMsg = text;
|
|
||||||
const isImage = text.includes('<img') || text.includes('/assets/images/chat/') || text.includes('data:image');
|
const isImage = text.includes('<img') || text.includes('/assets/images/chat/') || text.includes('data:image');
|
||||||
if (isImage) {
|
if (isImage && !displayMsg.includes('<img')) displayMsg = `<img src="${displayMsg.includes('assets/') ? '/'+displayMsg : displayMsg}" class="img-fluid rounded" style="max-height: 250px; cursor: zoom-in;" onclick="window.open(this.src)">`;
|
||||||
if (!displayMsg.includes('<img')) {
|
let timeStr = ''; try { const d = m.created_at ? (m.created_at.includes('-') ? new Date(m.created_at.replace(/-/g, "/")) : new Date(m.created_at)) : new Date(); timeStr = d.toLocaleTimeString('zh-CN', {hour:'2-digit', minute:'2-digit'}); } catch(e) { timeStr = '--:--'; }
|
||||||
displayMsg = `<img src="${displayMsg}" class="img-fluid rounded chat-img-preview" style="max-width: 100%; max-height: 250px; object-fit: contain; margin: 5px 0; cursor: zoom-in;" onclick="window.showLightbox ? window.showLightbox(this.src) : window.open(this.src)">`;
|
|
||||||
}
|
|
||||||
if (!displayMsg.includes('chat-img-preview')) {
|
|
||||||
displayMsg = displayMsg.replace('<img ', '<img class="chat-img-preview" ');
|
|
||||||
}
|
|
||||||
if (displayMsg.includes('src="assets/')) {
|
|
||||||
displayMsg = displayMsg.replace('src="assets/', 'src="/assets/');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let timeStr = '';
|
|
||||||
try {
|
|
||||||
if (m.created_at) {
|
|
||||||
const dateObj = m.created_at.includes('-') ? new Date(m.created_at.replace(/-/g, "/")) : new Date(m.created_at);
|
|
||||||
timeStr = dateObj.toLocaleTimeString('zh-CN', {hour:'2-digit', minute:'2-digit'});
|
|
||||||
} else {
|
|
||||||
timeStr = new Date().toLocaleTimeString('zh-CN', {hour:'2-digit', minute:'2-digit'});
|
|
||||||
}
|
|
||||||
} catch(e) {
|
|
||||||
timeStr = '--:--';
|
|
||||||
}
|
|
||||||
|
|
||||||
const html = `
|
const html = `
|
||||||
<div class="mb-3 d-flex flex-column ${sender === 'user' ? 'align-items-end' : 'align-items-start'} modal-msg" data-modal-id="${m.id}">
|
<div class="mb-3 d-flex flex-column ${sender === 'user' ? 'align-items-end' : 'align-items-start'} modal-msg" data-modal-id="${m.id}">
|
||||||
<div class="msg-bubble p-2 px-3 rounded-4 small ${sender === 'user' ? 'bg-primary text-white' : 'bg-dark text-white border border-secondary border-opacity-30'}" style="max-width: 85%; position: relative; ${isImage ? 'padding: 5px !important;' : 'padding-bottom: 22px !important;'}">
|
<div class="msg-bubble p-2 px-3 rounded-4 small ${sender === 'user' ? 'bg-primary text-white' : 'bg-dark text-white border border-secondary border-opacity-30'}" style="max-width: 85%; position: relative; padding-bottom: 22px !important;">
|
||||||
<div class="message-content" style="text-shadow: 0 1px 2px rgba(0,0,0,0.2); font-size: 14px; line-height: 1.5;">
|
<div class="message-content">${displayMsg}</div>
|
||||||
${displayMsg}
|
<div style="font-size: 9px; opacity: 0.6; position: absolute; bottom: 4px; ${sender === 'user' ? 'right: 12px;' : 'left: 12px;'}">${timeStr}</div>
|
||||||
</div>
|
|
||||||
<div style="font-size: 9px; opacity: 0.6; position: absolute; bottom: 4px; ${sender === 'user' ? 'right: 12px;' : 'left: 12px;'} ${isImage ? 'background: rgba(0,0,0,0.5); padding: 2px 6px; border-radius: 4px; bottom: 8px;' : ''}">${timeStr}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>`;
|
||||||
`;
|
container.insertAdjacentHTML('beforeend', html); modalChatLastIds.add(m.id);
|
||||||
container.insertAdjacentHTML('beforeend', html);
|
|
||||||
modalChatLastIds.add(m.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateMatchingStatus(status) {
|
|
||||||
const side = document.querySelector('.info-side');
|
|
||||||
if (!side) return;
|
|
||||||
|
|
||||||
if (status === 'matched') {
|
|
||||||
const badge = side.querySelector('.pulse-dot-pink')?.parentElement || side.querySelector('.bg-primary.bg-opacity-10');
|
|
||||||
if (badge) {
|
|
||||||
badge.innerHTML = '<i class="bi bi-check-circle-fill text-success"></i> <span style="letter-spacing: 1px;"><?= __("matched_successfully") ?></span>';
|
|
||||||
badge.className = 'd-inline-flex align-items-center gap-2 px-3 py-2 rounded-pill bg-success bg-opacity-10 text-success small fw-bold mb-3 border border-success border-opacity-20 shadow-sm';
|
|
||||||
}
|
|
||||||
const title = side.querySelector('h2');
|
|
||||||
if (title) title.innerText = '<?= __("matched_successfully") ?>';
|
|
||||||
const descBox = side.querySelector('p');
|
|
||||||
if (descBox) descBox.innerText = '<?= __("matched_desc_short") ?>';
|
|
||||||
|
|
||||||
const btn = side.querySelector('button[disabled]');
|
|
||||||
if (btn) {
|
|
||||||
btn.innerText = '<?= __("getting_account_details") ?>...';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateMatchingSide(info, isRestore = false) {
|
|
||||||
const side = document.querySelector('.info-side');
|
|
||||||
if (!side) return;
|
|
||||||
|
|
||||||
side.innerHTML = `
|
|
||||||
<div class="text-center text-lg-start" style="animation: fadeIn 0.5s ease;">
|
|
||||||
<!-- 1. Title -->
|
|
||||||
<div class="mb-4 text-center">
|
|
||||||
<div class="d-inline-flex align-items-center gap-2 px-3 py-1 rounded-pill bg-success bg-opacity-10 text-success small fw-bold mb-3 border border-success border-opacity-10">
|
|
||||||
<i class="bi bi-check-circle-fill text-success"></i> <?= __("matched_successfully") ?>
|
|
||||||
</div>
|
|
||||||
<h2 class="fw-bold text-dark mb-2" style="font-size: 1.8rem;"><?= __("matched_successfully") ?></h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 2. Account Information (EXACTLY ABOVE COUNTDOWN) -->
|
|
||||||
<div class="mb-3 p-4 rounded-4 shadow-sm border border-light" style="background: #fff0f5;">
|
|
||||||
<div class="d-flex flex-column gap-3">
|
|
||||||
<div class="payment-item">
|
|
||||||
<div class="text-muted small mb-1 fw-bold text-start"><?= __("receiving_bank") ?>:</div>
|
|
||||||
<div class="d-flex justify-content-between align-items-center gap-2">
|
|
||||||
<div class="h6 mb-0 fw-bold text-dark text-start" style="word-break: break-all; font-size: 1.1rem;">${info.bank}</div>
|
|
||||||
<button class="btn btn-sm rounded-pill px-3 fw-bold flex-shrink-0" style="font-size: 10px; background: #ff4d94; color: white;" onclick="copyText('${info.bank}')"><?= __("copy") ?></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="payment-item border-top border-white border-opacity-50 pt-2">
|
|
||||||
<div class="text-muted small mb-1 fw-bold text-start"><?= __("receiving_account") ?>:</div>
|
|
||||||
<div class="d-flex justify-content-between align-items-center gap-2">
|
|
||||||
<div class="h5 mb-0 fw-bold text-start" style="word-break: break-all; font-family: monospace; font-size: 1.3rem; color: #ff4d94;">${info.account}</div>
|
|
||||||
<button class="btn btn-sm rounded-pill px-3 fw-bold flex-shrink-0" style="font-size: 10px; background: #ff4d94; color: white;" onclick="copyText('${info.account}')"><?= __("copy") ?></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="payment-item border-top border-white border-opacity-50 pt-2">
|
|
||||||
<div class="text-muted small mb-1 fw-bold text-start"><?= __("receiving_name") ?>:</div>
|
|
||||||
<div class="d-flex justify-content-between align-items-center gap-2">
|
|
||||||
<div class="h6 mb-0 fw-bold text-dark text-start" style="word-break: break-all; font-size: 1.1rem;">${info.name}</div>
|
|
||||||
<button class="btn btn-sm rounded-pill px-3 fw-bold flex-shrink-0" style="font-size: 10px; background: #ff4d94; color: white;" onclick="copyText('${info.name}')"><?= __("copy") ?></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 3. Countdown Box (EXACTLY BELOW ACCOUNT INFO) -->
|
|
||||||
<div class="mb-3 py-3 px-4 rounded-4 shadow-sm border border-light" style="background: #ffffff;">
|
|
||||||
<div class="row align-items-center text-center text-lg-start">
|
|
||||||
<div class="col-12 col-lg-7 mb-2 mb-lg-0">
|
|
||||||
<div class="text-muted small mb-0 fw-bold"><?= __("remaining_time") ?></div>
|
|
||||||
<div class="h2 fw-bold mb-0" id="modal-countdown" style="font-family: monospace; color: #ff4d94;">--:--</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-lg-5 border-start-lg border-light ps-lg-3">
|
|
||||||
<div class="small fw-bold" style="color: #ff4d94;"><i class="bi bi-shield-check me-1"></i><?= __("secure_pay") ?></div>
|
|
||||||
<div class="text-muted" style="font-size: 10px;"><?= __("encrypted_channel") ?></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 4. Tip Text -->
|
|
||||||
<div class="p-2 text-center mb-3">
|
|
||||||
<p class="text-muted small fw-bold mb-0">
|
|
||||||
<?= __("recharge_final_notice") ?>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 5. Action Button -->
|
|
||||||
<button type="button" class="btn btn-primary w-100 rounded-pill py-3 fw-bold shadow-sm" onclick="finishTransfer()" style="background: #ff4d94 !important; border: none; color: white;">
|
|
||||||
<?= __("complete_transfer") ?>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
|
|
||||||
@media (min-width: 992px) { .border-start-lg { border-left: 1px solid #dee2e6 !important; } }
|
|
||||||
</style>
|
|
||||||
`;
|
|
||||||
|
|
||||||
if (remainingSeconds > 0) {
|
|
||||||
let mins = Math.floor(remainingSeconds / 60);
|
|
||||||
let secs = remainingSeconds % 60;
|
|
||||||
const display = document.getElementById('modal-countdown');
|
|
||||||
if (display) display.innerText = `${mins}:${secs < 10 ? '0' : ''}${secs}`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyText(text) {
|
function copyText(text) {
|
||||||
const el = document.createElement('textarea');
|
const el = document.createElement('textarea'); el.value = text; document.body.appendChild(el); el.select(); document.execCommand('copy'); document.body.removeChild(el);
|
||||||
el.value = text;
|
Swal.fire({ icon: 'success', title: '<?= __("copy_success") ?>', toast: true, position: 'top-end', showConfirmButton: false, timer: 2000 });
|
||||||
document.body.appendChild(el);
|
|
||||||
el.select();
|
|
||||||
document.execCommand('copy');
|
|
||||||
document.body.removeChild(el);
|
|
||||||
|
|
||||||
Swal.fire({
|
|
||||||
icon: 'success',
|
|
||||||
title: '<?= __("copy_success") ?>',
|
|
||||||
toast: true,
|
|
||||||
position: 'top-end',
|
|
||||||
showConfirmButton: false,
|
|
||||||
timer: 2000,
|
|
||||||
background: '#fff',
|
|
||||||
color: '#333'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollModalToBottom() {
|
function scrollModalToBottom() { const c = document.getElementById('modal-chat-messages'); if (c) c.scrollTop = c.scrollHeight; }
|
||||||
const container = document.getElementById('modal-chat-messages');
|
|
||||||
if (container) container.scrollTop = container.scrollHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
function confirmFiatOrder(btn, event) {
|
function confirmFiatOrder(btn, event) {
|
||||||
if (event) event.preventDefault();
|
if (event) event.preventDefault();
|
||||||
const amountInput = document.getElementById('fiatAmount');
|
const amount = parseFloat(document.getElementById('fiatAmount').value), select = document.getElementById('fiatCurrency'), currency = select.value, rate = parseFloat(select.options[select.selectedIndex].getAttribute('data-rate'));
|
||||||
const amount = parseFloat(amountInput.value);
|
if (isNaN(amount) || amount <= 0) { notify('warning', '<?= __("enter_amount") ?>'); return; }
|
||||||
const select = document.getElementById('fiatCurrency');
|
const originalText = btn.innerHTML; btn.disabled = true; btn.innerHTML = `<span class="spinner-border spinner-border-sm me-2"></span>${originalText}`;
|
||||||
const currency = select.value;
|
const formData = new FormData(); formData.append('action', 'recharge'); formData.append('amount', amount / rate); formData.append('symbol', 'USDT'); formData.append('fiat_amount', amount); formData.append('fiat_currency', currency); formData.append('method', '<?= __("fiat_recharge") ?> (' + currency + ')');
|
||||||
const rate = parseFloat(select.options[select.selectedIndex].getAttribute('data-rate'));
|
fetch('/api/finance.php', { method: 'POST', body: formData }).then(r => r.json()).then(data => {
|
||||||
|
btn.disabled = false; btn.innerHTML = originalText;
|
||||||
if (isNaN(amount) || amount <= 0) {
|
|
||||||
notify('warning', '<?= __("enter_amount") ?>');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const estUsdt = amount / rate;
|
|
||||||
|
|
||||||
// Show loading state
|
|
||||||
const originalText = btn.innerHTML;
|
|
||||||
btn.disabled = true;
|
|
||||||
btn.innerHTML = `<span class="spinner-border spinner-border-sm me-2"></span>${originalText}`;
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('action', 'recharge');
|
|
||||||
formData.append('amount', estUsdt);
|
|
||||||
formData.append('symbol', 'USDT');
|
|
||||||
formData.append('fiat_amount', amount);
|
|
||||||
formData.append('fiat_currency', currency);
|
|
||||||
formData.append('method', '<?= __("fiat_recharge") ?> (' + currency + ')');
|
|
||||||
|
|
||||||
fetch('/api/finance.php', {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
|
||||||
})
|
|
||||||
.then(r => {
|
|
||||||
if (!r.ok) throw new Error('HTTP error ' + r.status);
|
|
||||||
return r.json();
|
|
||||||
})
|
|
||||||
.then(data => {
|
|
||||||
btn.disabled = false;
|
|
||||||
btn.innerHTML = originalText;
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
let message = `<?= __("recharge_msg_fiat") ?>`;
|
let msg = `<?= __("recharge_msg_fiat") ?>`; msg = msg.replace('%uid%', userId).replace('%amount%', amount).replace('%currency%', currency).replace('%rate%', rate).replace('%res%', (amount / rate).toFixed(4));
|
||||||
const preciseRes = (amount / rate).toFixed(4);
|
openRechargeModal(msg, false, data.id);
|
||||||
message = message.replace('%uid%', userId)
|
} else notify('error', data.error || '<?= __("request_failed") ?>');
|
||||||
.replace('%amount%', amount)
|
}).catch(err => { btn.disabled = false; btn.innerHTML = originalText; notify('error', err.message); });
|
||||||
.replace('%currency%', currency)
|
|
||||||
.replace('%rate%', rate)
|
|
||||||
.replace('%res%', preciseRes);
|
|
||||||
openRechargeModal(message, false, data.id);
|
|
||||||
} else {
|
|
||||||
notify('error', data.error || '<?= __("request_failed") ?>');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error(err);
|
|
||||||
btn.disabled = false;
|
|
||||||
btn.innerHTML = originalText;
|
|
||||||
notify('error', '<?= __("request_failed") ?>: ' + err.message);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function confirmCryptoOrder(btn, event) {
|
function confirmCryptoOrder(btn, event) {
|
||||||
if (event) event.preventDefault();
|
if (event) event.preventDefault();
|
||||||
const amountInput = document.getElementById('cryptoAmount');
|
const amountInput = document.getElementById('cryptoAmount'), amount = parseFloat(amountInput.value);
|
||||||
const amount = parseFloat(amountInput.value);
|
if (isNaN(amount) || amount <= 0) { notify('warning', '<?= __("enter_amount") ?>'); return; }
|
||||||
|
const originalText = btn.innerHTML; btn.disabled = true; btn.innerHTML = `<span class="spinner-border spinner-border-sm me-2"></span>${originalText}`;
|
||||||
if (isNaN(amount) || amount <= 0) {
|
const formData = new FormData(); formData.append('action', 'recharge'); formData.append('amount', amount); formData.append('symbol', 'USDT'); formData.append('method', currentNetwork);
|
||||||
notify('warning', '<?= __("enter_amount") ?>');
|
fetch('/api/finance.php', { method: 'POST', body: formData }).then(r => r.json()).then(data => {
|
||||||
return;
|
btn.disabled = false; btn.innerHTML = originalText;
|
||||||
}
|
if (data.success) { Swal.fire({ icon: 'success', title: '<?= __("recharge_request_submitted") ?>', text: '<?= __("recharge_request_submitted_text") ?>', toast: true, position: 'top-end', showConfirmButton: false, timer: 3000 }); amountInput.value = ''; }
|
||||||
|
else notify('error', data.error || '<?= __("request_failed") ?>');
|
||||||
const originalText = btn.innerHTML;
|
}).catch(err => { btn.disabled = false; btn.innerHTML = originalText; notify('error', err.message); });
|
||||||
btn.disabled = true;
|
|
||||||
btn.innerHTML = `<span class="spinner-border spinner-border-sm me-2"></span>${originalText}`;
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('action', 'recharge');
|
|
||||||
formData.append('amount', amount);
|
|
||||||
formData.append('symbol', 'USDT');
|
|
||||||
formData.append('method', currentNetwork);
|
|
||||||
|
|
||||||
fetch('/api/finance.php', {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
|
||||||
})
|
|
||||||
.then(r => {
|
|
||||||
if (!r.ok) throw new Error('HTTP error ' + r.status);
|
|
||||||
return r.json();
|
|
||||||
})
|
|
||||||
.then(data => {
|
|
||||||
btn.disabled = false;
|
|
||||||
btn.innerHTML = originalText;
|
|
||||||
if (data.success) {
|
|
||||||
// Send notification to customer service
|
|
||||||
let message = `<?= __("recharge_msg_crypto") ?>`;
|
|
||||||
message = message.replace('%uid%', userId)
|
|
||||||
.replace('%amount%', amount.toFixed(2))
|
|
||||||
.replace('%network%', currentNetwork);
|
|
||||||
|
|
||||||
fetch('/api/chat.php?action=send_message', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
||||||
body: `message=${encodeURIComponent(message)}`
|
|
||||||
});
|
|
||||||
|
|
||||||
notify('success', '<?= __("recharge_success_title") ?>', '<?= __("recharge_success_text") ?>');
|
|
||||||
amountInput.value = '';
|
|
||||||
} else {
|
|
||||||
notify('error', data.error || '<?= __("request_failed") ?>');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error(err);
|
|
||||||
btn.disabled = false;
|
|
||||||
btn.innerHTML = originalText;
|
|
||||||
notify('error', '<?= __("request_failed") ?>: ' + err.message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendToCS(message) {
|
|
||||||
// Legacy support or fallback
|
|
||||||
openRechargeModal(message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyAddress() {
|
function copyAddress() {
|
||||||
const addr = document.getElementById('cryptoAddress');
|
const addr = document.getElementById('cryptoAddress'); addr.select(); document.execCommand('copy');
|
||||||
addr.select();
|
Swal.fire({ icon: 'success', title: '<?= __("copy_success") ?>', toast: true, position: 'top-end', showConfirmButton: false, timer: 3000, background: '#1e2329', color: '#fff' });
|
||||||
document.execCommand('copy');
|
|
||||||
Swal.fire({
|
|
||||||
icon: 'success',
|
|
||||||
title: '<?= __("copy_success") ?>',
|
|
||||||
toast: true,
|
|
||||||
position: 'top-end',
|
|
||||||
showConfirmButton: false,
|
|
||||||
timer: 3000,
|
|
||||||
timerProgressBar: true,
|
|
||||||
background: '#1e2329',
|
|
||||||
color: '#fff'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user