web-mooc/resources/views/payment/va_show.blade.php
2026-01-22 10:19:24 +07:00

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>