Update Banyak
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
RewriteEngine On
|
||||
|
||||
# Paksa hanya domain master
|
||||
RewriteCond %{HTTP_HOST} ^pay\.manjapro\.net$ [NC]
|
||||
|
||||
# HTTPS paksa
|
||||
RewriteCond %{HTTPS} !=on
|
||||
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
|
||||
|
||||
# Jangan rewrite file/folder nyata
|
||||
RewriteCond %{HTTP_HOST} ^pay\.manjapro\.net$ [NC]
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^ index.php [L,QSA]
|
||||
@@ -0,0 +1,57 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Error - Akses Ditolak</title>
|
||||
<style>
|
||||
/* Reset sederhana */
|
||||
* { margin:0; padding:0; box-sizing:border-box; font-family: Arial, sans-serif; }
|
||||
body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
background: #f8f9fa;
|
||||
color: #333;
|
||||
}
|
||||
.error-container {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
|
||||
max-width: 400px;
|
||||
}
|
||||
.error-code {
|
||||
font-size: 72px;
|
||||
font-weight: bold;
|
||||
color: #ff6b6b;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.error-message {
|
||||
font-size: 18px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.btn-home {
|
||||
text-decoration: none;
|
||||
padding: 12px 24px;
|
||||
background: #ff6b6b;
|
||||
color: #fff;
|
||||
border-radius: 8px;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
.btn-home:hover {
|
||||
background: #ff4b4b;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="error-container">
|
||||
<div class="error-code">403</div>
|
||||
<div class="error-message">
|
||||
Maaf, halaman tidak ditemukan atau akses Anda ditolak.
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
include '../../config/connect.php';
|
||||
include 'intruksi_pay.php';
|
||||
require_once 'jwtoken/url_paymnet_encrypt.php';
|
||||
|
||||
// Fungsi helper untuk redirect ke error.php
|
||||
function redirect_error() {
|
||||
include "error.php";
|
||||
exit;
|
||||
}
|
||||
|
||||
// Ambil URL segments
|
||||
$segments = explode("/", trim($_SERVER['REQUEST_URI'], "/"));
|
||||
|
||||
// Cek segments cukup panjang
|
||||
if (count($segments) < 3) {
|
||||
redirect_error();
|
||||
}
|
||||
|
||||
// Ambil order_id dari JWT
|
||||
$order_id = jwt_decrypt($segments[2]);
|
||||
if (!$order_id) {
|
||||
redirect_error();
|
||||
}
|
||||
|
||||
// Query aman pakai prepared statement
|
||||
$sql = "
|
||||
SELECT a.id, a.id_data_server, a.keterangan, a.nominal_tagihan, a.konpensasi,
|
||||
a.jumlah_konpensasi, a.jumlah_tagihan, a.ppn, a.kode_unik, a.verifikasi_tagihan,
|
||||
a.limit_pembayaran, a.nama, a.alamat, a.nama_paket, a.nama_server,
|
||||
b.id AS id_payment, b.order_id, b.data_get, b.status, b.input_time, b.update_time
|
||||
FROM v_tagihan a
|
||||
LEFT JOIN client_payment b ON a.id = b.id_tagihan
|
||||
WHERE a.id = :order_id
|
||||
ORDER BY b.id DESC
|
||||
LIMIT 1
|
||||
";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->bindParam(':order_id', $order_id, PDO::PARAM_STR);
|
||||
$stmt->execute();
|
||||
$pelanggan = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$pelanggan) {
|
||||
redirect_error();
|
||||
}
|
||||
|
||||
// Buat slug aman untuk URL
|
||||
$server = mb_strtolower(preg_replace('/[^a-zA-Z0-9]+/', '', $pelanggan['nama_server']));
|
||||
$nama = mb_strtolower(preg_replace('/[^a-zA-Z0-9]+/', '', $pelanggan['nama']));
|
||||
|
||||
// Validasi segments sesuai server & nama pelanggan
|
||||
if ($segments[0] !== $server || $segments[1] !== $nama) {
|
||||
redirect_error();
|
||||
}
|
||||
|
||||
// Jika semua valid, include payment
|
||||
include "payment.php";
|
||||
@@ -0,0 +1,443 @@
|
||||
<?php
|
||||
|
||||
function getIntruksi($data) {
|
||||
if (!$data) {
|
||||
return "<p>Tidak ada instruksi</p>";
|
||||
}
|
||||
|
||||
$data_get = json_decode($data, true);
|
||||
$uniq_code = $data_get['data']['unique_code'] ?? '';
|
||||
$expiredTime = $data_get['data']['expired'] ?? '';
|
||||
// $expiredTime = date('Y-m-d 18:47:10');
|
||||
$service = $data_get['data']['service'] ?? null;
|
||||
|
||||
if (!$service) {
|
||||
return "<p>Data instruksi tidak valid</p>";
|
||||
}
|
||||
|
||||
// 🔹 QRIS
|
||||
if ($service == 11) {
|
||||
$steps = "";
|
||||
if (!empty($data_get['payment_guide'][0]['content'])) {
|
||||
foreach ($data_get['payment_guide'][0]['content'] as $value) {
|
||||
$steps .= "<li>" . htmlspecialchars($value) . "</li>";
|
||||
}
|
||||
}
|
||||
|
||||
return "
|
||||
<div class='text-center'>
|
||||
<h6><b>QRIS</b></h6>
|
||||
<img src='{$data_get['data']['qrcode_url']}' width='300px' alt='QRIS'>
|
||||
<p class='mt-2'>Silakan scan QRIS di atas untuk melakukan pembayaran.</p>
|
||||
<p class='mt-2 text-secondary fw-bold' id='countDown'></p>
|
||||
|
||||
<a href='#' class='d-block mt-3 fw-bold text-primary' data-bs-toggle='collapse'
|
||||
data-bs-target='#caraBayar' aria-expanded='false'>
|
||||
Cara Pembayaran
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class='collapse mt-2' id='caraBayar'>
|
||||
<p><strong>{$data_get['payment_guide'][0]['title']}</strong></p>
|
||||
<ol>{$steps}</ol>
|
||||
</div>
|
||||
|
||||
<button class='btn btn-orange w-100' onclick='status()'>Cek Pembayaran</button>
|
||||
|
||||
<script>
|
||||
// Countdown Timer
|
||||
(function() {
|
||||
var expiredTime = new Date('{$expiredTime}').getTime();
|
||||
var countdownEl = document.getElementById('countDown');
|
||||
|
||||
var timer = setInterval(function() {
|
||||
var now = new Date().getTime();
|
||||
var distance = expiredTime - now;
|
||||
|
||||
if (distance <= 0) {
|
||||
clearInterval(timer);
|
||||
countdownEl.innerText = 'Waktu pembayaran sudah habis';
|
||||
status();
|
||||
setTimeout(function() {
|
||||
window.location.reload();
|
||||
}, 2000); // 2000ms = 2 detik
|
||||
return;
|
||||
}
|
||||
|
||||
var h = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000*60*60));
|
||||
var m = Math.floor((distance % (1000 * 60 * 60)) / (1000*60));
|
||||
var s = Math.floor((distance % (1000 * 60)) / 1000);
|
||||
|
||||
countdownEl.innerText = m + ' menit ' + s + ' detik';
|
||||
}, 1000);
|
||||
})();
|
||||
|
||||
function status() {
|
||||
let btn = document.querySelector('.btn.btn-orange.w-100');
|
||||
btn.disabled = true;
|
||||
btn.innerText = 'Memproses...';
|
||||
|
||||
fetch('/proses.php', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: 'action=status&idTagihan=' + encodeURIComponent('{$uniq_code}')
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
btn.disabled = false;
|
||||
btn.innerText = 'Cek Pembayaran';
|
||||
if(data.success){
|
||||
window.location.reload();
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
btn.disabled = false;
|
||||
btn.innerText = 'Cek Pembayaran';
|
||||
console.error(err);
|
||||
alert('Terjadi error saat menghubungi server.');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
";
|
||||
}
|
||||
|
||||
// 🔹 Virtual Account
|
||||
$bankLogos = [
|
||||
2 => "bri.png",
|
||||
4 => "bni.png",
|
||||
5 => "mandiri.png",
|
||||
];
|
||||
$img = $bankLogos[$service] ?? "";
|
||||
$va = htmlspecialchars($data_get['data']['virtual_account'] ?? '');
|
||||
|
||||
$steps = "";
|
||||
if (!empty($data_get['payment_guide'])) {
|
||||
foreach ($data_get['payment_guide'] as $title) {
|
||||
$steps .= "<p><strong>" . htmlspecialchars($title['title']) . "</strong></p><ol>";
|
||||
foreach ($title['content'] as $value) {
|
||||
$steps .= "<li>" . htmlspecialchars($value) . "</li>";
|
||||
}
|
||||
$steps .= "</ol>";
|
||||
}
|
||||
}
|
||||
|
||||
return "
|
||||
<div class='text-center'>
|
||||
<h6><b>Nomor Virtual Account</b></h6>
|
||||
<div class='card p-3' style='border-radius:15px; width:100%'>
|
||||
<div class='d-flex justify-content-between align-items-center'>
|
||||
<div class='d-flex align-items-center gap-2'>
|
||||
<span class='bank-icon-box'>
|
||||
<img src='https://manjapro.net/dist/img/{$img}' alt='Bank Logo' width='30'>
|
||||
</span>
|
||||
<span id='vaNumber' class='fw-bold fs-5 ml-4'>{$va}</span>
|
||||
</div>
|
||||
<button id='copyBtn' class='btn btn-md p-1 text-secondary' onclick='copyVA()'>
|
||||
<i id='iconCopy' class='fa fa-copy'></i>
|
||||
<i id='iconCheck' class='fa fa-check d-none'></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class='mt-3'>Silakan gunakan Virtual Account untuk melakukan pembayaran.</p>
|
||||
<p class='mt-2 text-secondary fw-bold' id='countDown'></p>
|
||||
|
||||
<a href='#' class='d-block mt-3 fw-bold text-primary' data-bs-toggle='collapse'
|
||||
data-bs-target='#caraBayar' aria-expanded='false'>
|
||||
Cara Pembayaran
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class='collapse mt-2 text-start' id='caraBayar'>
|
||||
{$steps}
|
||||
</div>
|
||||
|
||||
<button class='btn btn-orange w-100' onclick='status()'>Cek Pembayaran</button>
|
||||
|
||||
<script>
|
||||
// Copy VA
|
||||
function copyVA() {
|
||||
var va = document.getElementById('vaNumber').innerText;
|
||||
navigator.clipboard.writeText(va).then(function() {
|
||||
document.getElementById('iconCopy').classList.add('d-none');
|
||||
document.getElementById('iconCheck').classList.remove('d-none');
|
||||
setTimeout(function() {
|
||||
document.getElementById('iconCheck').classList.add('d-none');
|
||||
document.getElementById('iconCopy').classList.remove('d-none');
|
||||
}, 2000);
|
||||
}).catch(function(err) {
|
||||
console.error('Gagal menyalin: ', err);
|
||||
alert('Gagal menyalin Virtual Account.');
|
||||
});
|
||||
}
|
||||
|
||||
// Countdown Timer
|
||||
(function() {
|
||||
var expiredTime = new Date('{$expiredTime}').getTime();
|
||||
var countdownEl = document.getElementById('countDown');
|
||||
|
||||
var timer = setInterval(function() {
|
||||
var now = new Date().getTime();
|
||||
var distance = expiredTime - now;
|
||||
|
||||
if (distance <= 0) {
|
||||
clearInterval(timer);
|
||||
countdownEl.innerText = 'Waktu pembayaran sudah habis';
|
||||
status();
|
||||
setTimeout(function() {
|
||||
window.location.reload();
|
||||
}, 2000); // 2000ms = 2 detik
|
||||
return;
|
||||
}
|
||||
|
||||
var h = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000*60*60));
|
||||
var m = Math.floor((distance % (1000 * 60 * 60)) / (1000*60));
|
||||
var s = Math.floor((distance % (1000 * 60)) / 1000);
|
||||
|
||||
countdownEl.innerText = m + ' menit ' + s + ' detik';
|
||||
}, 1000);
|
||||
})();
|
||||
|
||||
function status() {
|
||||
let btn = document.querySelector('.btn.btn-orange.w-100');
|
||||
btn.disabled = true;
|
||||
btn.innerText = 'Memproses...';
|
||||
|
||||
fetch('/proses.php', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: 'action=status&idTagihan=' + encodeURIComponent('{$uniq_code}')
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
btn.disabled = false;
|
||||
btn.innerText = 'Cek Pembayaran';
|
||||
})
|
||||
.catch(err => {
|
||||
btn.disabled = false;
|
||||
btn.innerText = 'Cek Pembayaran';
|
||||
console.error(err);
|
||||
alert('Terjadi error saat menghubungi server.');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
";
|
||||
}
|
||||
|
||||
|
||||
function getListPembayaran($idTagihan) {
|
||||
return '
|
||||
<!-- Pilihan Metode -->
|
||||
<div id="pilihanMetode">
|
||||
<h6 class="mb-3">Metode Pembayaran</h6>
|
||||
|
||||
<div class="accordion" id="accordionMetode">
|
||||
<!-- Virtual Account -->
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="headingVA">
|
||||
<a class="accordion-button collapsed" type="button"
|
||||
data-bs-toggle="collapse" data-bs-target="#collapseVA"
|
||||
aria-expanded="false" aria-controls="collapseVA">
|
||||
<span class="accordion-left">
|
||||
<span class="fw-semibold">Virtual Account</span>
|
||||
<span class="accordion-icons">
|
||||
<span class="bank-icon-box"><img src="https://manjapro.net/dist/img/bri.png" alt="BRI"></span>
|
||||
<span class="bank-icon-box"><img src="https://manjapro.net/dist/img/bni.png" alt="BNI"></span>
|
||||
<span class="bank-icon-box"><img src="https://manjapro.net/dist/img/mandiri.png" alt="Mandiri"></span>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</h2>
|
||||
<div id="collapseVA" class="accordion-collapse collapse"
|
||||
aria-labelledby="headingVA" data-bs-parent="#accordionMetode">
|
||||
<div class="accordion-body p-0">
|
||||
<ul class="list-group list-group-flush bank-list">
|
||||
<li class="list-group-item">
|
||||
<a class="list-btn pay-method" onclick="pilihMetode(\'Virtual Account\', \'BRI\', \'https://manjapro.net/dist/img/bri.png\')">
|
||||
<div class="bank-logo-box"><img src="https://manjapro.net/dist/img/bri.png" alt="BRI"></div>
|
||||
<span class="bank-name">BRI VA</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<a class="list-btn pay-method" onclick="pilihMetode(\'Virtual Account\', \'BNI\', \'https://manjapro.net/dist/img/bni.png\')">
|
||||
<div class="bank-logo-box"><img src="https://manjapro.net/dist/img/bni.png" alt="BNI"></div>
|
||||
<span class="bank-name">BNI VA</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<a class="list-btn pay-method" onclick="pilihMetode(\'Virtual Account\', \'Mandiri\', \'https://manjapro.net/dist/img/mandiri.png\')">
|
||||
<div class="bank-logo-box"><img src="https://manjapro.net/dist/img/mandiri.png" alt="Mandiri"></div>
|
||||
<span class="bank-name">Mandiri VA</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- E-Wallet & QRIS -->
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="headingQRIS">
|
||||
<a class="accordion-button collapsed" type="button"
|
||||
data-bs-toggle="collapse" data-bs-target="#collapseQRIS"
|
||||
aria-expanded="false" aria-controls="collapseQRIS">
|
||||
<span class="accordion-left">
|
||||
<span class="fw-semibold">E-Wallet & QRIS</span>
|
||||
<span class="accordion-icons">
|
||||
<span class="bank-icon-box"><img src="https://manjapro.net/dist/img/qris.png" alt="QRIS"></span>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</h2>
|
||||
<div id="collapseQRIS" class="accordion-collapse collapse"
|
||||
aria-labelledby="headingQRIS" data-bs-parent="#accordionMetode">
|
||||
<div class="accordion-body p-0">
|
||||
<ul class="list-group list-group-flush bank-list">
|
||||
<li class="list-group-item">
|
||||
<a class="list-btn pay-method" onclick="pilihMetode(\'E-Wallet & QRIS\', \'QRIS\', \'https://manjapro.net/dist/img/qris.png\')">
|
||||
<div class="bank-logo-box"><img src="https://manjapro.net/dist/img/qris.png" alt="QRIS"></div>
|
||||
<span class="bank-name">QRIS</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="metodeTerpilih" class="d-none">
|
||||
<div class="method-card method-selected">
|
||||
<div class="bank-logo-box" style="margin-top: 0px;">
|
||||
<img src="https://manjapro.net/dist/img/qris.png" id="metodeLogo">
|
||||
</div>
|
||||
<span class="bank-name" id="metodeText"></span>
|
||||
<button type="button" class="btn btn-sm ubah-btn" onclick="ubahMetode()" style="font-size: 105%; color: #ff7f11;"><strong>Ubah</strong></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button id="btnBayar" class="btn btn-orange w-100 d-none" onclick="bayar()">Bayar</button>
|
||||
|
||||
<script>
|
||||
let metodeDipilih = "";
|
||||
let metodeService = "";
|
||||
let imgMetode = "";
|
||||
const idTagihan = '.$idTagihan.';
|
||||
|
||||
function pilihMetode(metode, service, logo){
|
||||
metodeDipilih = metode;
|
||||
metodeService = service;
|
||||
imgMetode = logo;
|
||||
|
||||
document.getElementById("pilihanMetode").classList.add("d-none");
|
||||
document.getElementById("metodeTerpilih").classList.remove("d-none");
|
||||
document.getElementById("metodeText").innerHTML = metode + "<br>" + service;
|
||||
document.getElementById("metodeLogo").src = logo;
|
||||
document.getElementById("btnBayar").classList.remove("d-none");
|
||||
|
||||
document.querySelectorAll(".accordion-button").forEach(el => el.classList.remove("selected-icon"));
|
||||
let opened = document.querySelector(".accordion-button[aria-expanded=\'true\']");
|
||||
if(opened) opened.classList.add("selected-icon");
|
||||
}
|
||||
|
||||
function ubahMetode(){
|
||||
metodeDipilih = "";
|
||||
metodeService = "";
|
||||
document.getElementById("pilihanMetode").classList.remove("d-none");
|
||||
document.getElementById("metodeTerpilih").classList.add("d-none");
|
||||
document.getElementById("btnBayar").classList.add("d-none");
|
||||
document.querySelectorAll(".accordion-button").forEach(el => el.classList.remove("selected-icon"));
|
||||
}
|
||||
|
||||
function bayar(){
|
||||
if(metodeDipilih !== ""){
|
||||
let btn = document.getElementById("btnBayar");
|
||||
btn.disabled = true;
|
||||
btn.innerText = "Memproses...";
|
||||
|
||||
fetch("/proses.php", {
|
||||
method: "POST",
|
||||
headers: {"Content-Type": "application/x-www-form-urlencoded"},
|
||||
body: "action=bayar"
|
||||
+ "&paymentMethod=" + encodeURIComponent(metodeService)
|
||||
+ "&idTagihan=" + encodeURIComponent(idTagihan)
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
btn.disabled = false;
|
||||
btn.innerText = "Bayar Sekarang";
|
||||
if(data.success){
|
||||
window.location.reload();
|
||||
} else {
|
||||
alert("Gagal: " + (data.message || "Terjadi kesalahan"));
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
btn.disabled = false;
|
||||
btn.innerText = "Bayar Sekarang";
|
||||
console.error(err);
|
||||
alert("Terjadi error saat menghubungi server.");
|
||||
});
|
||||
|
||||
} else {
|
||||
alert("Silakan pilih metode pembayaran terlebih dahulu!");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
';
|
||||
}
|
||||
|
||||
function getStatus($pelanggan) {
|
||||
return '
|
||||
<div id="statusBerhasil">
|
||||
<div class="card-body mb-4 text-center">
|
||||
<h5><b>Transaksi Berhasil</b></h5>
|
||||
<p>' . htmlspecialchars($pelanggan['tgl_lunas']) . '</p>
|
||||
<img src="https://i.pinimg.com/originals/89/86/fe/8986fef7a58272135c7c5d006a312554.gif" width="120px" alt="Success">
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="detail-row">
|
||||
<span class="label">Id Transaksi</span>
|
||||
<span class="value">' . htmlspecialchars('#PGPDN' . $pelanggan['order_id']) . '</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="detail-row">
|
||||
<span class="label">Customer</span>
|
||||
<span class="value">' . htmlspecialchars($pelanggan['nama']) . '</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="label">Metode</span>
|
||||
<span class="value">' . htmlspecialchars($pelanggan['metode']) . '</span>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="detail-row">
|
||||
<span class="label">Order</span>
|
||||
<span class="value">' . htmlspecialchars($pelanggan['keterangan']) . '</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="label">Subtotal</span>
|
||||
<span class="value">Rp ' . number_format($pelanggan['subtotal'], 0, ',', '.') . '</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="label">Service fee</span>
|
||||
<span class="value">Rp ' . number_format($pelanggan['servicefee'], 0, ',', '.') . '</span>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="detail-row">
|
||||
<span class="label">Total</span>
|
||||
<span class="value">
|
||||
<h5><b>Rp ' . number_format($pelanggan['total'], 0, ',', '.') . '</b></h5>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>';
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
// Simpan di luar folder public_html biar lebih aman
|
||||
define('JWT_SECRET', 'rahasia_super_aman_123456789');
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/secret.php'; // panggil secret
|
||||
|
||||
// Helper base64 url safe
|
||||
function base64url_encode($data) {
|
||||
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
|
||||
}
|
||||
function base64url_decode($data) {
|
||||
return base64_decode(strtr($data, '-_', '+/'));
|
||||
}
|
||||
|
||||
// Buat token dari order_id
|
||||
function jwt_encrypt($order_id) {
|
||||
$header = ['typ' => 'JWT', 'alg' => 'HS256'];
|
||||
$payload = ['order_id' => $order_id];
|
||||
|
||||
$header_encoded = base64url_encode(json_encode($header));
|
||||
$payload_encoded = base64url_encode(json_encode($payload));
|
||||
|
||||
$signature = hash_hmac('sha256', "$header_encoded.$payload_encoded", JWT_SECRET, true);
|
||||
$signature_encoded = base64url_encode($signature);
|
||||
|
||||
return "$header_encoded.$payload_encoded.$signature_encoded";
|
||||
}
|
||||
|
||||
// Baca token → dapat order_id
|
||||
function jwt_decrypt($jwt) {
|
||||
$parts = explode('.', $jwt);
|
||||
if (count($parts) !== 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
list($header_encoded, $payload_encoded, $signature_provided) = $parts;
|
||||
|
||||
$signature = hash_hmac('sha256', "$header_encoded.$payload_encoded", JWT_SECRET, true);
|
||||
$signature_verified = base64url_encode($signature);
|
||||
|
||||
if (!hash_equals($signature_verified, $signature_provided)) {
|
||||
return false; // Signature tidak cocok
|
||||
}
|
||||
|
||||
$payload = json_decode(base64url_decode($payload_encoded), true);
|
||||
return $payload['order_id'] ?? false;
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Payment</title>
|
||||
<link rel="icon" href="https://manjapro.net/dist/img/logo-sm.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<!-- Bootstrap -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
|
||||
<!-- Google Font -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap" rel="stylesheet">
|
||||
<!-- Font Awesome -->
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" rel="stylesheet">
|
||||
|
||||
<!-- CUSTOM CSS -->
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-wrapper">
|
||||
<div class="content">
|
||||
<div class="header-card">
|
||||
<!-- Header / Info Pelanggan -->
|
||||
<div class="header-box">
|
||||
<div class="brand-row">
|
||||
<img src="https://manjapro.net/dist/img/logo-sm.png" alt="Logo">
|
||||
<h5><?= $pelanggan['nama_server']; ?></h5>
|
||||
</div>
|
||||
<?php if($pelanggan['status'] != 'SUCCESS') { ?>
|
||||
<p class="mb-1 mt-2"><?= $pelanggan['keterangan']; ?></p>
|
||||
<div class="amount">Rp <?= number_format($pelanggan['verifikasi_tagihan'],0,',','.') ?></div>
|
||||
<div class="text-center" style="margin-top: 40px; margin-bottom: 20px;">
|
||||
<a class="btn btn-sm btn-light" data-bs-toggle="collapse" href="#detailTagihan">Lihat Detail</a>
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card Metode Pembayaran -->
|
||||
<div class="payment-card">
|
||||
<?php if($pelanggan['status'] != 'SUCCESS') { ?>
|
||||
<!-- Detail Tagihan -->
|
||||
<div class="collapse mt-2" id="detailTagihan">
|
||||
<hr>
|
||||
<div class="card-body">
|
||||
<div class="detail-row">
|
||||
<span class="label">Nama</span>
|
||||
<span class="value"><?= $pelanggan['nama'] ?></span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="label">Alamat</span>
|
||||
<span class="value"><?= $pelanggan['alamat'] ?></span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="label">Paket</span>
|
||||
<span class="value"><?= $pelanggan['nama_paket'] ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
<?php
|
||||
if ($pelanggan['status'] == 'SUCCESS') {
|
||||
echo getStatus($pelanggan);
|
||||
}
|
||||
elseif ($pelanggan['status'] == 'PENDING') {
|
||||
$data_get = json_decode($pelanggan['data_get'], true);
|
||||
$expired = $data_get['data']['expired'] ?? null;
|
||||
// $expired = date('Y-m-d 18:47:10');
|
||||
|
||||
if (!empty($expired) && $expired <= date('Y-m-d H:i:s')) {
|
||||
echo getListPembayaran($pelanggan['id']);
|
||||
} else {
|
||||
echo getIntruksi($pelanggan['data_get']);
|
||||
}
|
||||
}
|
||||
else {
|
||||
echo getListPembayaran($pelanggan['id']);
|
||||
}
|
||||
?>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- Footer -->
|
||||
<div class="powered">
|
||||
Powered by <img src="https://web.paydisini.co.id/assets/logo.png" alt="Logo">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
require __DIR__ . "/../../config/connect.php"; // koneksi PDO
|
||||
require __DIR__ . "/../paydisini/api_control.php"; // CreateTransaction,statusTransaction, deleteTransaction
|
||||
|
||||
$action = $_POST['action'] ?? '';
|
||||
|
||||
switch ($action) {
|
||||
case 'bayar':
|
||||
bayar($pdo);
|
||||
break;
|
||||
case 'status':
|
||||
status($pdo);
|
||||
break;
|
||||
default:
|
||||
echo json_encode(["success" => false, "msg" => "Action tidak dikenal"]);
|
||||
break;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fungsi untuk membuat order_id unik
|
||||
*/
|
||||
function generateOrderId($pdo) {
|
||||
do {
|
||||
$order_id = sprintf('%013d', mt_rand(0, 9999999999999));
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM apk_payment WHERE order_id = :order_id) +
|
||||
(SELECT COUNT(*) FROM client_payment WHERE order_id = :order_id)
|
||||
");
|
||||
$stmt->bindParam(':order_id', $order_id);
|
||||
$stmt->execute();
|
||||
$exists = $stmt->fetchColumn();
|
||||
} while ($exists > 0);
|
||||
|
||||
return $order_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fungsi untuk membuat instruksi pembayaran
|
||||
*/
|
||||
function bayar($pdo) {
|
||||
$idTagihan = intval($_POST['idTagihan'] ?? 0);
|
||||
$metode = strtolower($_POST['paymentMethod'] ?? '');
|
||||
|
||||
// Cek tagihan
|
||||
$stmt = $pdo->prepare("SELECT id_data_server, verifikasi_tagihan, keterangan, nomor_whatsapp, nama FROM v_tagihan WHERE id = :id");
|
||||
$stmt->bindParam(':id', $idTagihan, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
$dataTagihan = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$dataTagihan) {
|
||||
echo json_encode(["success" => false, "msg" => "Tagihan tidak ditemukan"]);
|
||||
return;
|
||||
}
|
||||
|
||||
$idServer = $dataTagihan['id_data_server'];
|
||||
$amount = $dataTagihan['verifikasi_tagihan'];
|
||||
$jumlah = $dataTagihan['verifikasi_tagihan'];
|
||||
$note = $dataTagihan['keterangan'];
|
||||
$nama = $dataTagihan['nama'];
|
||||
$ewallet_phone = isset($dataTagihan['nomor_whatsapp'])
|
||||
? preg_replace('/\D/', '', $dataTagihan['nomor_whatsapp'])
|
||||
: '';
|
||||
|
||||
$stmt = $pdo->prepare("SELECT biaya_payment_gateway FROM data_server WHERE id = :id_data_server");
|
||||
$stmt->bindParam(':id_data_server', $idServer, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
$dataBiaya = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
//perhitungan biaya payment gateway
|
||||
$jenisBiaya = $dataBiaya['biaya_payment_gateway'];
|
||||
|
||||
switch ($metode) {
|
||||
case 'qris': $biaya = $jumlah * 0.01; break;
|
||||
case 'mandiri':$biaya = 4500; break;
|
||||
default: $biaya = 2950; break;
|
||||
}
|
||||
|
||||
if ($metode == 'qris') {
|
||||
if ($jenisBiaya == 2) {
|
||||
$saldo = $jumlah - $biaya;
|
||||
$amount = $jumlah;
|
||||
} else {
|
||||
$saldo = $jumlah;
|
||||
$amount = $jumlah + $biaya;
|
||||
}
|
||||
} else {
|
||||
if ($jenisBiaya == 2) {
|
||||
$saldo = $jumlah - $biaya;
|
||||
$amount = $jumlah;
|
||||
} else {
|
||||
$saldo = $jumlah;
|
||||
$amount = $jumlah + $biaya;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($amount <= 0) {
|
||||
echo json_encode(["success" => false, "msg" => "Nominal tagihan tidak valid"]);
|
||||
return;
|
||||
}
|
||||
|
||||
$valid_time = ($metode === 'qris') ? 300 : 3600;
|
||||
$new_order_id = generateOrderId($pdo);
|
||||
|
||||
$data = [
|
||||
'unique_code' => $new_order_id,
|
||||
'service' => $metode,
|
||||
'amount' => $amount,
|
||||
'note' => $note,
|
||||
'valid_time' => $valid_time,
|
||||
'ewallet_phone' => $ewallet_phone,
|
||||
];
|
||||
|
||||
$response = CreateTransaction($data);
|
||||
$dataCreate = json_decode($response, true);
|
||||
|
||||
$jenis = $dataCreate['data']['service_name'];
|
||||
|
||||
if (!empty($dataCreate['success']) && $dataCreate['success'] === true) {
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO client_payment
|
||||
(id_data_server, id_tagihan, order_id, data_get, status, jumlah, saldo, biaya, jenis, keterangan, nama, input_time)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())
|
||||
");
|
||||
$stmt->execute([
|
||||
$idServer,
|
||||
$idTagihan,
|
||||
$new_order_id,
|
||||
json_encode($dataCreate),
|
||||
'PENDING',
|
||||
$amount,
|
||||
$saldo,
|
||||
$biaya,
|
||||
$jenis,
|
||||
$note,
|
||||
$nama
|
||||
|
||||
]);
|
||||
|
||||
echo json_encode([
|
||||
"success" => true,
|
||||
"msg" => "Berhasil membuat pembayaran",
|
||||
"data" => $dataCreate
|
||||
]);
|
||||
} else {
|
||||
echo json_encode([
|
||||
"success" => false,
|
||||
"msg" => "Gagal membuat pembayaran",
|
||||
"data" => $dataCreate,
|
||||
"metode" => $metode
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
function status($pdo) {
|
||||
$unique_code = $_POST['idTagihan'] ?? '';
|
||||
|
||||
$data = [
|
||||
'unique_code' => $unique_code,
|
||||
];
|
||||
$response = statusTransaction($data);
|
||||
$dataStatus = json_decode($response, true);
|
||||
|
||||
if (!empty($dataStatus['success']) && $dataStatus['success'] === true) {
|
||||
if ($dataStatus['data']['status'] == "Success") {
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE client_payment
|
||||
SET status = :status,
|
||||
update_time = :update_time
|
||||
WHERE order_id = :order_id
|
||||
");
|
||||
$stmt->bindParam(':status', strtoupper($dataStatus['data']['status']));
|
||||
$stmt->bindParam(':update_time', date('Y-m-d H:i:s'));
|
||||
$stmt->bindParam(':order_id', $unique_code);
|
||||
$stmt->execute();
|
||||
}
|
||||
echo json_encode([
|
||||
"success" => true,
|
||||
"msg" => "Berhasil cek status",
|
||||
"data" => $dataStatus
|
||||
]);
|
||||
} else {
|
||||
echo json_encode([
|
||||
"success" => false,
|
||||
"msg" => "Gagal cek status",
|
||||
"data" => $dataStatus
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
body {
|
||||
background: #fff;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
zoom: 80%;
|
||||
margin-bottom: -40px;
|
||||
}
|
||||
|
||||
.page-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1; /* isi utama ambil ruang sisa */
|
||||
}
|
||||
|
||||
.header-card {
|
||||
background: #ff7f11;
|
||||
width: 100%;
|
||||
margin-bottom: -50px;
|
||||
margin-top: -20px;
|
||||
padding-top: 20px;
|
||||
padding-bottom: 50px;
|
||||
}
|
||||
|
||||
/* ===== Header ===== */
|
||||
.header-box {
|
||||
max-width: 550px;
|
||||
margin: 20px auto 0;
|
||||
color: #fff;
|
||||
padding: 0 15px;
|
||||
text-align: center;
|
||||
}
|
||||
.brand-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 40px;
|
||||
zoom: 90%;
|
||||
}
|
||||
.brand-row img {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid #fff;
|
||||
object-fit: cover;
|
||||
}
|
||||
.brand-row h5 {
|
||||
margin: 0;
|
||||
}
|
||||
.amount {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
/* ===== Card Utama ===== */
|
||||
.payment-card {
|
||||
max-width: 550px;
|
||||
margin: 0 auto;
|
||||
border-radius: 22px 22px 0 0;
|
||||
padding: 25px;
|
||||
background: #fff;
|
||||
min-height: 70vh;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* ===== Tombol Bayar ===== */
|
||||
.btn-orange {
|
||||
background: #ff7f11;
|
||||
color: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 14px;
|
||||
font-weight: 600;
|
||||
font-size: 1.1rem;
|
||||
margin: 20px auto 0;
|
||||
display: block;
|
||||
max-width: 500px;
|
||||
}
|
||||
.btn-orange:hover {
|
||||
background: #e76e0d;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* ===== Accordion ===== */
|
||||
.accordion-item {
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 10px;
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,0.05);
|
||||
}
|
||||
.accordion-button {
|
||||
border-radius: 12px !important;
|
||||
background: #fff !important;
|
||||
color: inherit !important;
|
||||
box-shadow: none !important;
|
||||
padding: 0.5rem 1rem !important;
|
||||
display: flex;
|
||||
flex-direction: row; /* ubah ke row */
|
||||
justify-content: space-between;
|
||||
align-items: flex-start; /* panah sejajar atas */
|
||||
text-decoration: none !important;
|
||||
}
|
||||
.accordion-button:not(.collapsed) {
|
||||
background: #fff !important;
|
||||
}
|
||||
.accordion-button:focus {
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
.accordion-button .fw-semibold {
|
||||
margin-bottom: 0.25rem !important;
|
||||
}
|
||||
|
||||
/* group kiri = title + icons */
|
||||
.accordion-button .accordion-left {
|
||||
display: flex;
|
||||
flex-direction: column; /* tumpuk: nama di atas, icons di bawah */
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.accordion-button .accordion-icons {
|
||||
display: inline-flex;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.accordion-button:not(.collapsed) .accordion-icons {
|
||||
display: none !important;
|
||||
}
|
||||
.accordion-body {
|
||||
padding: 0.25rem 0 !important;
|
||||
}
|
||||
|
||||
/* Arrow tetap kanan atas */
|
||||
.accordion-button::after {
|
||||
margin-left: auto;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
|
||||
/* ===== List Metode ===== */
|
||||
.list-group-item {
|
||||
border-color: #dee2e6;
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
.list-btn {
|
||||
border: none;
|
||||
background: none;
|
||||
outline: none;
|
||||
padding: 8px 0;
|
||||
font-weight: 500;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.list-btn:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
/* ===== Metode Terpilih ===== */
|
||||
.method-card {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 12px;
|
||||
padding: 15px;
|
||||
margin-bottom: 15px;
|
||||
background: #fafafa;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
.method-selected {
|
||||
border: 2px solid #ff7f11;
|
||||
background: #fff9f4;
|
||||
}
|
||||
.ubah-btn {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
/* ===== Detail Tagihan ===== */
|
||||
#instruksiBox {
|
||||
font-size: 0.95rem;
|
||||
border-radius: 12px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
display: flex;
|
||||
margin-bottom: 6px;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.detail-row .label {
|
||||
font-weight: 600;
|
||||
min-width: 120px; /* kasih lebar minimum biar sejajar */
|
||||
}
|
||||
|
||||
.detail-row .value {
|
||||
flex: 1; /* biar ambil sisa ruang */
|
||||
text-align: right; /* tetap rata kanan */
|
||||
word-break: break-word; /* biar bisa pecah ke bawah kalau panjang */
|
||||
white-space: normal; /* izinkan turun ke bawah */
|
||||
}
|
||||
|
||||
.bank-list {
|
||||
text-align: left;
|
||||
}
|
||||
.bank-list .list-group-item {
|
||||
border: none;
|
||||
/* padding: 12px 0;*/
|
||||
}
|
||||
.bank-list .pay-method {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.bank-logo-box {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
flex-shrink: 0;
|
||||
margin-top: -20px;
|
||||
}
|
||||
|
||||
.bank-logo-box img {
|
||||
max-width: 70%;
|
||||
max-height: 70%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.bank-name {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
border-bottom: 2px solid #f0f0f0;
|
||||
padding-bottom: 5px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.accordion-icons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
.bank-icon-box {
|
||||
width: 60px;
|
||||
height: 40px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.bank-icon-box img {
|
||||
max-width: 70%;
|
||||
max-height: 70%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
/* ===== Footer Powered ===== */
|
||||
.powered {
|
||||
text-align: center;
|
||||
font-size: 0.9rem;
|
||||
color: #777;
|
||||
margin-top: 40px;
|
||||
}
|
||||
.powered img {
|
||||
height: 20px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
Reference in New Issue
Block a user