458 lines
19 KiB
PHP
458 lines
19 KiB
PHP
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
@php
|
|
$system_name = \App\Models\Setting::where('type', 'system_name')->value('description');
|
|
$system_favicon = \App\Models\Setting::where('type', 'system_fav_icon')->value('description');
|
|
@endphp
|
|
<title>Virtual Account - {{ $system_name }}</title>
|
|
<!-- all the meta tags -->
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<meta content="" name="description" />
|
|
<meta content="" name="author" />
|
|
<!-- CSRF Token for ajax for submission -->
|
|
<meta name="csrf_token" content="{{ csrf_token() }}" />
|
|
<!-- all the css files -->
|
|
<!-- fav icon -->
|
|
<link rel="shortcut icon" href="{{ asset(get_frontend_settings('favicon')) }}" />
|
|
<!-- Bootstrap CSS -->
|
|
<link rel="stylesheet" type="text/css" href="{{ asset('assets/payment/style/vendors/bootstrap-5.1.3/css/bootstrap.min.css') }}" />
|
|
<link rel="stylesheet" type="text/css" href="{{ asset('assets/payment/style/css/swiper-bundle.min.css') }}" />
|
|
<link rel="stylesheet" type="text/css" href="{{ asset('assets/payment/style/css/custom.css') }}" />
|
|
<link rel="stylesheet" type="text/css" href="{{ asset('assets/payment/style/css/style.css') }}" />
|
|
<link rel="stylesheet" type="text/css" href="{{ asset('assets/payment/style/vendors/bootstrap-icons-1.8.1/bootstrap-icons.css') }}" />
|
|
<link rel="stylesheet" type="text/css" href="{{ asset('assets/global/icons/uicons-regular-rounded/css/uicons-regular-rounded.css') }}" />
|
|
<link rel="stylesheet" type="text/css" href="{{ asset('assets/payment/style/css/own.css') }}" />
|
|
<!--Main Jquery-->
|
|
<script src="{{ asset('assets/payment/style/vendors/jquery/jquery-3.7.1.min.js') }}"></script>
|
|
<style>
|
|
.main_content {
|
|
min-height: calc(100% - 50px);
|
|
margin-top: 0px !important;
|
|
}
|
|
|
|
.va-card {
|
|
border-radius: 10px;
|
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
|
|
border: none;
|
|
}
|
|
|
|
.va-card .card-body {
|
|
padding: 2rem;
|
|
}
|
|
|
|
.va-number {
|
|
font-size: 1.5rem;
|
|
letter-spacing: 2px;
|
|
background: #f8f9fa;
|
|
padding: 10px 15px;
|
|
border-radius: 5px;
|
|
border: 1px dashed #dee2e6;
|
|
}
|
|
|
|
.payment-instruction {
|
|
background: #fff8e1;
|
|
border-left: 4px solid #ffc107;
|
|
border-radius: 5px;
|
|
}
|
|
|
|
.badge {
|
|
font-size: 0.9rem;
|
|
padding: 0.5rem 1rem;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body class="pt-lg-5 pb-4">
|
|
@if (session('app_url'))
|
|
@include('payment.go_back_to_mobile_app')
|
|
@endif
|
|
|
|
<div class="main_content paymentContent">
|
|
<div class="paymentHeader d-flex justify-content-between align-items-center px-4 px-sm-5 mb-4">
|
|
<h5 class="title text-capitalize">{{ get_phrase('Payment Details') }}</h5>
|
|
<a href="{{ route('purchase.history') }}" class="btn btn-light text-sm">
|
|
<i class="fi-rr-arrow-left"></i>
|
|
<span class="d-none d-sm-inline-block">{{ get_phrase('Back to History') }}</span>
|
|
</a>
|
|
</div>
|
|
|
|
<div class="px-4 px-sm-5 pt-2 pb-5">
|
|
<div class="card va-card">
|
|
<div class="card-body">
|
|
<h4 class="mb-4 pb-2 border-bottom">{{ get_phrase('Virtual Account Payment') }}</h4>
|
|
|
|
<table class="table table-bordered mb-4">
|
|
<tbody>
|
|
<tr>
|
|
<th class="bg-light" style="width: 30%">{{ get_phrase('Bank') }}</th>
|
|
<td>
|
|
<div class="d-flex align-items-center">
|
|
<span class="me-2">BTN</span>
|
|
<span class="badge bg-primary">{{ get_phrase('Virtual Account') }}</span>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th class="bg-light">{{ get_phrase('VA Number') }}</th>
|
|
<td>
|
|
<div class="va-number d-inline-block">
|
|
<strong>{{ chunk_split($vaPayment->va_number, 4, ' ') }}</strong>
|
|
</div>
|
|
<button class="btn btn-sm btn-outline-secondary ms-3" onclick="copyToClipboard('{{ $vaPayment->va_number }}')">
|
|
<i class="bi bi-clipboard"></i> {{ get_phrase('Copy') }}
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th class="bg-light">{{ get_phrase('Total Amount') }}</th>
|
|
<td class="h5 text-primary">
|
|
Rp {{ number_format($vaPayment->total_amount, 0, ',', '.') }}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th class="bg-light">{{ get_phrase('Time Remaining') }}</th>
|
|
<td>
|
|
<div id="countdownWrapper" class="d-flex align-items-center">
|
|
<i class="bi bi-hourglass-split me-2 text-warning"></i>
|
|
<span id="countdown" class="fs-5 fw-semibold text-black">
|
|
--:--:--
|
|
</span>
|
|
<span class="badge bg-warning text-white ms-3">
|
|
{{ get_phrase('Waiting for payment') }}
|
|
</span>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<tr>
|
|
<th class="bg-light">{{ get_phrase('Expired At') }}</th>
|
|
<td class="{{ \Carbon\Carbon::now()->gt($vaPayment->expired_at) ? 'text-danger' : '' }}">
|
|
<i class="bi bi-clock me-1"></i>
|
|
{{ \Carbon\Carbon::parse($vaPayment->expired_at)->translatedFormat('l, d F Y H:i') }}
|
|
@if(\Carbon\Carbon::now()->gt($vaPayment->expired_at))
|
|
<span class="badge bg-danger ms-2">{{ get_phrase('Expired') }}</span>
|
|
@endif
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th class="bg-light">{{ get_phrase('Status') }}</th>
|
|
<td>
|
|
@if ($vaPayment->status == 0)
|
|
<span class="badge bg-warning text-black">
|
|
<i class="bi bi-clock-history me-1"></i> {{ get_phrase('UNPAID') }}
|
|
</span>
|
|
@elseif ($vaPayment->status == 1)
|
|
<span class="badge bg-success">
|
|
<i class="bi bi-check-circle me-1"></i> {{ get_phrase('PAID') }}
|
|
</span>
|
|
@else
|
|
<span class="badge bg-danger">
|
|
<i class="bi bi-x-circle me-1"></i> {{ get_phrase('FAILED') }}
|
|
</span>
|
|
@endif
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<div class="alert payment-instruction mt-4">
|
|
<h6 class="alert-heading mb-2">
|
|
<i class="bi bi-info-circle me-2"></i> {{ get_phrase('Payment Instructions') }}
|
|
</h6>
|
|
<ol class="mb-0 ps-3">
|
|
<li>{{ get_phrase('Go to ATM or Mobile Banking BTN') }}</li>
|
|
<li>{{ get_phrase('Select menu "Transfer" or "Payment"') }}</li>
|
|
<li>{{ get_phrase('Select "Virtual Account" or "VA"') }}</li>
|
|
<li>{{ get_phrase('Enter the VA number above') }}</li>
|
|
<li>{{ get_phrase('Confirm the amount and complete the payment') }}</li>
|
|
<li>{{ get_phrase('Payment confirmation will be processed automatically') }}</li>
|
|
</ol>
|
|
</div>
|
|
|
|
<div class="d-flex justify-content-between align-items-center mt-4 pt-3 border-top">
|
|
<div>
|
|
<small class="text-muted">
|
|
<i class="bi bi-shield-check me-1"></i>
|
|
{{ get_phrase('Secure payment via BTN Virtual Account') }}
|
|
</small>
|
|
</div>
|
|
<div>
|
|
@if($vaPayment->status == 0 && \Carbon\Carbon::now()->lt($vaPayment->expired_at))
|
|
<button class="btn btn-primary" onclick="checkPaymentStatus()">
|
|
<i class="bi bi-arrow-clockwise me-1"></i> {{ get_phrase('Check Status') }}
|
|
</button>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!--Bootstrap bundle with popper-->
|
|
<script src="{{ asset('assets/payment/style/vendors/bootstrap-5.1.3/js/bootstrap.bundle.min.js') }}"></script>
|
|
<script src="{{ asset('assets/payment/style/js/swiper-bundle.min.js') }}"></script>
|
|
<!-- Datepicker js -->
|
|
<script src="{{ asset('assets/payment/style/js/moment.min.js') }}"></script>
|
|
<script src="{{ asset('assets/payment/style/js/sweetalert2@11.js') }}"></script>
|
|
<!-- toster file -->
|
|
@include('frontend.default.toaster')
|
|
</body>
|
|
|
|
</html>
|
|
|
|
|
|
<<script>
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
const expiredAt = new Date("{{ \Carbon\Carbon::parse($vaPayment->expired_at)->toIso8601String() }}").getTime();
|
|
const countdownEl = document.getElementById('countdown');
|
|
const wrapperEl = document.getElementById('countdownWrapper');
|
|
|
|
if (!countdownEl || !wrapperEl) return;
|
|
|
|
function updateCountdown() {
|
|
const now = new Date().getTime();
|
|
const distance = expiredAt - now;
|
|
|
|
if (distance < 10 * 60 * 1000) {
|
|
countdownEl.classList.remove('text-warning');
|
|
countdownEl.classList.add('text-danger');
|
|
}
|
|
|
|
if (distance <= 0) {
|
|
countdownEl.innerHTML = "{{ get_phrase('Expired') }}";
|
|
countdownEl.className = "fw-bold text-danger";
|
|
|
|
wrapperEl.innerHTML = `
|
|
<i class="bi bi-x-circle-fill me-2 text-danger"></i>
|
|
<span class="fw-bold text-danger">{{ get_phrase('Expired') }}</span>
|
|
<span class="badge bg-danger ms-3">{{ get_phrase('VA not valid') }}</span>
|
|
`;
|
|
clearInterval(timer);
|
|
return;
|
|
}
|
|
|
|
const hours = Math.floor((distance / (1000 * 60 * 60)));
|
|
const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
|
|
const seconds = Math.floor((distance % (1000 * 60)) / 1000);
|
|
|
|
countdownEl.innerHTML =
|
|
String(hours).padStart(2, '0') + ':' +
|
|
String(minutes).padStart(2, '0') + ':' +
|
|
String(seconds).padStart(2, '0');
|
|
}
|
|
|
|
updateCountdown();
|
|
const timer = setInterval(updateCountdown, 1000);
|
|
});
|
|
|
|
function copyToClipboard(text) {
|
|
navigator.clipboard.writeText(text).then(function() {
|
|
Swal.fire({
|
|
icon: 'success',
|
|
title: 'Copied!',
|
|
text: 'VA number copied to clipboard',
|
|
timer: 2000,
|
|
showConfirmButton: false
|
|
});
|
|
});
|
|
}
|
|
|
|
function checkPaymentStatus() {
|
|
Swal.fire({
|
|
title: 'Checking Payment Status',
|
|
text: 'Please wait...',
|
|
allowOutsideClick: false,
|
|
didOpen: () => {
|
|
Swal.showLoading();
|
|
|
|
// Simulate API call - replace with actual API endpoint
|
|
setTimeout(() => {
|
|
window.location.reload();
|
|
}, 1500);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Auto refresh page every 60 seconds if payment is still unpaid
|
|
@if($vaPayment->status == 0 && \Carbon\Carbon::now()->lt($vaPayment->expired_at))
|
|
setTimeout(function() {
|
|
window.location.reload();
|
|
}, 60000); // 60 seconds
|
|
@endif
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
const paymentId = {{ $vaPayment->id }};
|
|
const CHECK_DELAY = 15000; // 15 detik (sesuai permintaan)
|
|
const MAX_CHECKS = 240; // 1 jam (15s x 240)
|
|
|
|
let checkCount = 0;
|
|
let intervalId = null;
|
|
let isChecking = false;
|
|
|
|
function startAutoCheck() {
|
|
if (intervalId) return;
|
|
|
|
intervalId = setInterval(() => {
|
|
checkPaymentStatus(false);
|
|
}, CHECK_DELAY);
|
|
}
|
|
|
|
function stopAutoCheck() {
|
|
if (intervalId) {
|
|
clearInterval(intervalId);
|
|
intervalId = null;
|
|
}
|
|
}
|
|
|
|
async function checkPaymentStatus(manual = true) {
|
|
if (isChecking) return;
|
|
if (checkCount >= MAX_CHECKS) {
|
|
stopAutoCheck();
|
|
console.log('Stop checking: time limit reached (1 hour)');
|
|
showExpiredMessage();
|
|
return;
|
|
}
|
|
|
|
isChecking = true;
|
|
checkCount++;
|
|
|
|
console.log(`Check #${checkCount} initiated`);
|
|
|
|
if (manual) {
|
|
Swal.fire({
|
|
title: 'Checking payment status...',
|
|
allowOutsideClick: false,
|
|
didOpen: () => Swal.showLoading()
|
|
});
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`/payment/va/${paymentId}/status`, {
|
|
headers: {
|
|
'X-Requested-With': 'XMLHttpRequest',
|
|
'Accept': 'application/json'
|
|
}
|
|
});
|
|
|
|
// Cek jika response OK
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
console.log('API Response:', data);
|
|
|
|
if (data.status === 1) {
|
|
// Payment success
|
|
stopAutoCheck();
|
|
|
|
if (manual) Swal.close();
|
|
|
|
Swal.fire({
|
|
icon: 'success',
|
|
title: 'Payment Success',
|
|
text: 'Pembayaran berhasil diterima',
|
|
timer: 3000,
|
|
showConfirmButton: false
|
|
}).then(() => {
|
|
window.location.reload();
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (data.status === 2) {
|
|
// Expired
|
|
stopAutoCheck();
|
|
|
|
if (manual) Swal.close();
|
|
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: 'Payment Expired',
|
|
text: 'Virtual Account sudah tidak valid',
|
|
confirmButtonText: 'OK'
|
|
}).then(() => {
|
|
window.location.reload();
|
|
});
|
|
return;
|
|
}
|
|
|
|
// status === 0 → lanjut auto-check
|
|
console.log('Payment still pending');
|
|
|
|
} catch (error) {
|
|
console.error('Check status error:', error);
|
|
|
|
if (manual) {
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: 'Error',
|
|
text: 'Gagal memeriksa status pembayaran',
|
|
confirmButtonText: 'OK'
|
|
});
|
|
}
|
|
} finally {
|
|
if (manual && Swal.isVisible()) Swal.close();
|
|
isChecking = false;
|
|
}
|
|
}
|
|
|
|
function showExpiredMessage() {
|
|
// Update UI untuk expired
|
|
const statusCell = document.querySelector('tr:nth-child(5) td');
|
|
if (statusCell) {
|
|
statusCell.innerHTML = `
|
|
<span class="badge bg-secondary">
|
|
<i class="bi bi-clock me-1"></i> EXPIRED
|
|
</span>
|
|
`;
|
|
}
|
|
|
|
// Disable check button
|
|
const checkBtn = document.querySelector('button[onclick="checkPaymentStatus()"]');
|
|
if (checkBtn) {
|
|
checkBtn.disabled = true;
|
|
checkBtn.innerHTML = '<i class="bi bi-clock me-1"></i> Expired';
|
|
checkBtn.className = 'btn btn-secondary';
|
|
checkBtn.onclick = null;
|
|
}
|
|
|
|
// Show alert
|
|
Swal.fire({
|
|
icon: 'info',
|
|
title: 'Auto-check Stopped',
|
|
text: 'Pengecekan otomatis dihentikan setelah 1 jam',
|
|
confirmButtonText: 'OK'
|
|
});
|
|
}
|
|
|
|
// ▶️ START AUTO CHECK SAAT PAGE DIBUKA
|
|
@if($vaPayment->status == 0 )
|
|
startAutoCheck();
|
|
// Cek pertama setelah 2 detik
|
|
setTimeout(() => checkPaymentStatus(false), 2000);
|
|
@endif
|
|
|
|
// 🔘 EXPOSE KE BUTTON
|
|
window.checkPaymentStatus = function () {
|
|
checkPaymentStatus(true);
|
|
};
|
|
|
|
// Override fungsi lama jika ada
|
|
window.originalCheckPaymentStatus = window.checkPaymentStatus;
|
|
|
|
// ⛔ STOP CHECK JIKA TAB DITUTUP / PINDAH
|
|
document.addEventListener('visibilitychange', function () {
|
|
if (document.hidden) {
|
|
stopAutoCheck();
|
|
console.log('Auto-check paused (tab hidden)');
|
|
} else {
|
|
startAutoCheck();
|
|
console.log('Auto-check resumed');
|
|
}
|
|
});
|
|
});
|
|
</script> |