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;
|
||||
}
|
||||
@@ -16,6 +16,7 @@ class FlipPaymentGateway
|
||||
public function createBill($title, $amount, $sender_email, $sender_name, $sender_phone_number, $sender_address, $urlback = '')
|
||||
{
|
||||
$endpoint = '/bill';
|
||||
$sender_real_name = ucwords(strtolower(trim(preg_replace("/[^a-zA-Z\s]/", " ", $sender_name))));
|
||||
if(!empty($urlback)){
|
||||
$data = [
|
||||
"title" => $title,
|
||||
@@ -26,7 +27,7 @@ class FlipPaymentGateway
|
||||
"step" => 2,
|
||||
"redirect_url" => $urlback,
|
||||
"sender_email" => $sender_email,
|
||||
"sender_name" => $sender_name,
|
||||
"sender_name" => $sender_real_name,
|
||||
"sender_phone_number" => $sender_phone_number,
|
||||
"sender_address" => $sender_address
|
||||
];
|
||||
@@ -39,7 +40,7 @@ class FlipPaymentGateway
|
||||
"is_phone_number_required" => 0,
|
||||
"step" => 2,
|
||||
"sender_email" => $sender_email,
|
||||
"sender_name" => $sender_name,
|
||||
"sender_name" => $sender_real_name,
|
||||
"sender_phone_number" => $sender_phone_number,
|
||||
"sender_address" => $sender_address
|
||||
];
|
||||
|
||||
@@ -1,141 +1,159 @@
|
||||
<?php
|
||||
// Callback Paydisini
|
||||
$secret = '4f120457653057e4362b56639478af67';
|
||||
$key = $_POST['key'] ?? '';
|
||||
$order_id = $_POST['unique_code'] ?? '';
|
||||
$status = $_POST['status'] ?? '';
|
||||
$signature = $_POST['signature'] ?? '';
|
||||
$sign = md5($secret . $order_id . 'CallbackStatus');
|
||||
|
||||
// simple callback (make sure whitelist paydisini 194.233.92.170)
|
||||
if ($_POST['key'] == '4f120457653057e4362b56639478af67') {
|
||||
$result = ['success' => false, 'message' => 'Invalid request'];
|
||||
|
||||
$key = $_POST['key'];
|
||||
$order_id = $_POST['unique_code'];
|
||||
$status = $_POST['status'];
|
||||
$signature = $_POST['signature'];
|
||||
$sign = md5('4f120457653057e4362b56639478af67' . $order_id . 'CallbackStatus');
|
||||
// --- Fungsi logging ---
|
||||
function writeLog($message) {
|
||||
$dir = __DIR__ . "/logs";
|
||||
if (!is_dir($dir)) {
|
||||
mkdir($dir, 0755, true);
|
||||
}
|
||||
$file = $dir . '/callback_' . date('Y-m-d') . '.log';
|
||||
$log = "[" . date('Y-m-d H:i:s') . "] " . $message . PHP_EOL;
|
||||
file_put_contents($file, $log, FILE_APPEND);
|
||||
}
|
||||
|
||||
try {
|
||||
if ($key !== $secret) {
|
||||
throw new Exception("Invalid key");
|
||||
}
|
||||
|
||||
include '../../../config/connect.php';
|
||||
include '../../../config/whatsapp_api.php';
|
||||
|
||||
// Cari data payment
|
||||
$stmt = $pdo->prepare("SELECT * FROM apk_payment WHERE order_id = :order_id");
|
||||
$stmt->bindParam(':order_id', $order_id);
|
||||
$stmt->execute();
|
||||
$result = $stmt->fetch();
|
||||
$stmt->execute([':order_id' => $order_id]);
|
||||
$apk = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($apk) {
|
||||
$apk['table_name'] = 'apk_payment';
|
||||
$data = $apk;
|
||||
} else {
|
||||
$stmt = $pdo->prepare("SELECT * FROM client_payment WHERE order_id = :order_id");
|
||||
$stmt->execute([':order_id' => $order_id]);
|
||||
$client = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if ($client) {
|
||||
$client['table_name'] = 'client_payment';
|
||||
$data = $client;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!$data) {
|
||||
throw new Exception("Order not found: $order_id");
|
||||
}
|
||||
|
||||
// Generate log file name based on current date and time
|
||||
$logFileName = 'log_' . date('Y-m-d H:i:s') . '.log';
|
||||
// Log awal callback
|
||||
// writeLog("Callback received | Order=$order_id | Status=$status | Signature=$signature | Expected=$sign");
|
||||
|
||||
// Prepare log message
|
||||
$logMessage = "[" . date('Y-m-d H:i:s') . "] Received Callback - Order ID: $order_id, Status: $status, Signature: $signature, Expected Signature: $sign\n";
|
||||
if ($signature !== $sign) {
|
||||
throw new Exception("Signature mismatch");
|
||||
}
|
||||
|
||||
// Write log message to file without opening the file manually (appending mode)
|
||||
// file_put_contents($logFileName, $logMessage, FILE_APPEND);
|
||||
// === APK_PAYMENT ===
|
||||
if ($data['table_name'] === 'apk_payment') {
|
||||
if ($status === 'Success') {
|
||||
if ($data['jenis'] === 'PPPOE') {
|
||||
// TODO: Update sukses PPPOE (kosong dulu)
|
||||
// writeLog("Order $order_id: PPPOE payment success");
|
||||
} elseif ($data['jenis'] === 'Hotspot') {
|
||||
$stmt = $pdo->prepare("SELECT * FROM v_voucher_paygw WHERE order_id = :order_id");
|
||||
$stmt->execute([':order_id' => $order_id]);
|
||||
$vcr = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$vcr) throw new Exception("Voucher tidak ditemukan | Order=$order_id");
|
||||
|
||||
if ($signature != $sign) {
|
||||
$result = array('success' => false);
|
||||
$logMessage = "Signature mismatch, callback ignored.\n";
|
||||
// file_put_contents($logFileName, $logMessage, FILE_APPEND);
|
||||
} else if ($status == 'Success') {
|
||||
|
||||
if ($result['jenis'] == 'PPPOE') {
|
||||
//Update Succesc untuk PPPOE ( di gunakan nanati );
|
||||
} else if($result['jenis'] == 'Hotspot') {
|
||||
// Ambil data voucher berdasarkan order_id
|
||||
$stmt = $pdo->prepare("SELECT * FROM v_voucher_paygw WHERE order_id = :order_id");
|
||||
$stmt->bindParam(':order_id', $order_id, PDO::PARAM_STR);
|
||||
$stmt->execute();
|
||||
$vcr = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$vcr) {
|
||||
// Tidak ditemukan
|
||||
exit("Voucher tidak ditemukan untuk order_id: $order_id");
|
||||
}
|
||||
|
||||
// Konfigurasi koneksi MikroTik
|
||||
$m_ip = $vcr['ip_address'];
|
||||
$m_user = $vcr['username'];
|
||||
$m_pass = $vcr['password'];
|
||||
$m_port = $vcr['port_api'];
|
||||
|
||||
require('../../../config/routeros_api.class.php');
|
||||
$API = new RouterosAPI();
|
||||
$API->debug = false;
|
||||
|
||||
// Coba koneksi ke MikroTik
|
||||
if ($API->connect2($m_ip, $m_user, $m_pass, $m_port)) {
|
||||
try {
|
||||
// Tambah user ke hotspot
|
||||
$API->comm('/ip/hotspot/user/add', [
|
||||
"name" => $vcr['voucher'],
|
||||
"password" => $vcr['voucher'],
|
||||
"profile" => $vcr['nama_profile'],
|
||||
"server" => "all"
|
||||
]);
|
||||
|
||||
// Update status berhasil
|
||||
$update = $pdo->prepare("UPDATE ganerate_voucher SET status_create = '1' WHERE id = :id");
|
||||
$update->execute([':id' => $vcr['id']]);
|
||||
|
||||
// Kirim pesan WhatsApp
|
||||
$nomorList = array_filter(array_map('trim', explode(',', $vcr['nomor_whatsapp'])));
|
||||
$APIwa = new PesanWA();
|
||||
|
||||
$Pesan = "Pembelian voucher kamu *BERHASIL!*\n\n"
|
||||
. "Tanggal : " . $vcr['waktu_transaksi'] . "\n"
|
||||
. "Pembayaran : QRIS\n\n"
|
||||
. "Paket : " . $vcr['nama_paket'] . "\n"
|
||||
. "Harga : Rp " . number_format($vcr['harga']) . "\n"
|
||||
. "Kode Voucher : *" . $vcr['voucher'] . "*\n\n"
|
||||
. "_Voucher hanya bisa digunakan pada 1 perangkat / device._\n\n"
|
||||
. "Terima kasih telah menggunakan layanan kami.";
|
||||
require('../../../config/routeros_api.class.php');
|
||||
$API = new RouterosAPI();
|
||||
$API->debug = false;
|
||||
|
||||
if (!empty($nomorList)) {
|
||||
$APIwa->Send_Pesan($vcr['id_data_server'], 'sendMessage', $nomorList, $Pesan);
|
||||
if ($API->connect2($vcr['ip_address'], $vcr['username'], $vcr['password'], $vcr['port_api'])) {
|
||||
try {
|
||||
$API->comm('/ip/hotspot/user/add', [
|
||||
"name" => $vcr['voucher'],
|
||||
"password" => $vcr['voucher'],
|
||||
"profile" => $vcr['nama_profile'],
|
||||
"server" => "all"
|
||||
]);
|
||||
$pdo->prepare("UPDATE ganerate_voucher SET status_create = '1' WHERE id = ?")
|
||||
->execute([$vcr['id']]);
|
||||
|
||||
$nomorList = array_filter(array_map('trim', explode(',', $vcr['nomor_whatsapp'])));
|
||||
if ($nomorList) {
|
||||
$Pesan = "Pembelian voucher kamu *BERHASIL!*\n\n"
|
||||
. "Tanggal : {$vcr['waktu_transaksi']}\n"
|
||||
. "Pembayaran : QRIS\n\n"
|
||||
. "Paket : {$vcr['nama_paket']}\n"
|
||||
. "Harga : Rp " . number_format($vcr['harga']) . "\n"
|
||||
. "Kode Voucher : *{$vcr['voucher']}*\n\n"
|
||||
. "_Voucher hanya bisa digunakan pada 1 perangkat / device._\n\n"
|
||||
. "Terima kasih telah menggunakan layanan kami.";
|
||||
(new PesanWA())->Send_Pesan($vcr['id_data_server'], 'sendMessage', $nomorList, $Pesan);
|
||||
}
|
||||
// writeLog("Voucher {$vcr['voucher']} sukses ditambahkan di MikroTik & WA dikirim | Order=$order_id");
|
||||
} catch (Exception $e) {
|
||||
$pdo->prepare("UPDATE ganerate_voucher SET status_create = '0' WHERE id = ?")
|
||||
->execute([$vcr['id']]);
|
||||
// writeLog("Gagal tambah user hotspot | Order=$order_id | Error=" . $e->getMessage());
|
||||
} finally {
|
||||
$API->disconnect();
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
// Jika gagal saat perintah ke router
|
||||
$update = $pdo->prepare("UPDATE ganerate_voucher SET status_create = '0' WHERE id = :id");
|
||||
$update->execute([':id' => $vcr['id']]);
|
||||
// error_log("Gagal menambahkan user hotspot: " . $e->getMessage());
|
||||
} finally {
|
||||
$API->disconnect();
|
||||
} else {
|
||||
$pdo->prepare("UPDATE ganerate_voucher SET status_create = '0' WHERE id = ?")
|
||||
->execute([$vcr['id']]);
|
||||
// writeLog("Gagal koneksi MikroTik | IP={$vcr['ip_address']} | Order=$order_id");
|
||||
}
|
||||
} else {
|
||||
// Jika gagal konek ke MikroTik
|
||||
$update = $pdo->prepare("UPDATE ganerate_voucher SET status_create = '0' WHERE id = :id");
|
||||
$update->execute([':id' => $vcr['id']]);
|
||||
// error_log("Gagal koneksi ke MikroTik pada IP: $m_ip");
|
||||
$ids = array_map('intval', explode(',', $data['id_data_server']));
|
||||
if ($ids) {
|
||||
$in = implode(',', array_fill(0, count($ids), '?'));
|
||||
$sql = "UPDATE data_server SET status='Aktif', expaired_date=? WHERE id IN ($in)";
|
||||
$params = array_merge([$data['expaired_time']], $ids);
|
||||
$pdo->prepare($sql)->execute($params);
|
||||
}
|
||||
// writeLog("Server status diupdate aktif | Order=$order_id");
|
||||
}
|
||||
|
||||
|
||||
$pdo->prepare("UPDATE apk_payment SET status='LUNAS' WHERE order_id=?")->execute([$order_id]);
|
||||
$result = ['success' => true, 'message' => "Payment success, updated to LUNAS"];
|
||||
// writeLog("Order $order_id status updated to LUNAS");
|
||||
} elseif ($status === 'Canceled') {
|
||||
$pdo->prepare("UPDATE apk_payment SET status='CANCELED' WHERE order_id=?")->execute([$order_id]);
|
||||
$result = ['success' => true, 'message' => "Payment canceled"];
|
||||
// writeLog("Order $order_id status updated to CANCELED");
|
||||
} else {
|
||||
// Pisahkan dan pastikan hanya angka
|
||||
$idArray = array_map('intval', explode(',', $result['id_data_server']));
|
||||
$placeholders = implode(',', array_fill(0, count($idArray), '?'));
|
||||
$sql = "UPDATE data_server SET status = 'Aktif', expaired_date = ? WHERE id IN ($placeholders)";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$params = array_merge([$result['expaired_time']], $idArray);
|
||||
$stmt->execute($params);
|
||||
throw new Exception("Invalid status for apk_payment | Order=$order_id");
|
||||
}
|
||||
// Update payment status to "LUNAS"
|
||||
$stmt = $pdo->prepare("UPDATE apk_payment SET status = 'LUNAS' WHERE order_id = :order_id");
|
||||
$stmt->bindParam(':order_id', $order_id);
|
||||
$stmt->execute();
|
||||
|
||||
$result = array('success' => true);
|
||||
$logMessage = "Payment success for Order ID: $order_id, Status updated to LUNAS.\n";
|
||||
// file_put_contents($logFileName, $logMessage, FILE_APPEND);
|
||||
} else if ($status == 'Canceled') {
|
||||
// Update payment status to "CANCELED"
|
||||
$stmt = $pdo->prepare("UPDATE apk_payment SET status = 'CANCELED' WHERE order_id = :order_id");
|
||||
$stmt->bindParam(':order_id', $order_id);
|
||||
$stmt->execute();
|
||||
|
||||
$result = array('success' => true);
|
||||
$logMessage = "Payment canceled for Order ID: $order_id, Status updated to CANCELED.\n";
|
||||
// file_put_contents($logFileName, $logMessage, FILE_APPEND);
|
||||
} else {
|
||||
$result = array('success' => false);
|
||||
$logMessage = "Invalid status received for Order ID: $order_id\n";
|
||||
// file_put_contents($logFileName, $logMessage, FILE_APPEND);
|
||||
}
|
||||
|
||||
// === CLIENT_PAYMENT ===
|
||||
elseif ($data['table_name'] === 'client_payment') {
|
||||
if ($status === 'Success') {
|
||||
$pdo->prepare("UPDATE client_payment SET status='SUCCESS', update_time=NOW() WHERE order_id=?")
|
||||
->execute([$order_id]);
|
||||
$result = ['success' => true, 'message' => "Client payment success"];
|
||||
// writeLog("Client payment sukses | Order=$order_id");
|
||||
} elseif ($status === 'Canceled') {
|
||||
$pdo->prepare("UPDATE client_payment SET status='CANCELED', update_time=NOW() WHERE order_id=?")
|
||||
->execute([$order_id]);
|
||||
$result = ['success' => true, 'message' => "Client payment canceled"];
|
||||
// writeLog("Client payment canceled | Order=$order_id");
|
||||
} else {
|
||||
throw new Exception("Invalid status for client_payment | Order=$order_id");
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
$result = ['success' => false, 'message' => $e->getMessage()];
|
||||
writeLog("ERROR | Order=$order_id | " . $e->getMessage());
|
||||
}
|
||||
|
||||
// Set response header and return JSON result
|
||||
header('Content-type: application/json');
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($result);
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
[2025-09-18 01:50:22] ERROR | Order=7733908392871 | Order not found: 7733908392871
|
||||
[2025-09-18 01:56:26] ERROR | Order=4199868221255 | Order not found: 4199868221255
|
||||
[2025-09-18 01:56:28] ERROR | Order=2580772857198 | Order not found: 2580772857198
|
||||
[2025-09-18 22:25:16] ERROR | Order=0458599414057 | Order not found: 0458599414057
|
||||
@@ -50,7 +50,7 @@ session_start();
|
||||
<br>
|
||||
<div class="container" style="max-width: 900px;">
|
||||
<center>
|
||||
<img src="../../dist/img/logo.png" width="230px" class="mb-4">
|
||||
<img src="../../dist/img/logo_clr.png" width="230px" class="mb-4">
|
||||
</center>
|
||||
<div class="card">
|
||||
<div class="card-header bg-warning">
|
||||
@@ -59,7 +59,7 @@ session_start();
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<table class="table table-borderless">
|
||||
<table class="table table-borderless table-respon">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Nama Usaha</td>
|
||||
@@ -104,7 +104,7 @@ session_start();
|
||||
<div class="modal-dialog modal-dialog-centered modal-sm">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-warning">
|
||||
<img src="../../dist/img/logo_long_white.png" width="100px">
|
||||
<img src="../../dist/img/logo_wht.png" width="100px">
|
||||
</div>
|
||||
<small><small>
|
||||
<div class="modal-body" style="background-color: rgb(240, 240, 240); text-align: left;">
|
||||
|
||||
@@ -70,7 +70,7 @@ function generateOrderId($pdo) {
|
||||
<br>
|
||||
<div class="container" style="max-width: 900px;">
|
||||
<center>
|
||||
<img src="../../dist/img/logo.png" width="230px" class="mb-4">
|
||||
<img src="../../dist/img/logo_clr.png" width="230px" class="mb-4">
|
||||
</center>
|
||||
<div class="card">
|
||||
<div class="card-header bg-warning">
|
||||
@@ -186,7 +186,7 @@ function generateOrderId($pdo) {
|
||||
<div class="modal-dialog modal-dialog-centered modal-sm">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-warning">
|
||||
<img src="../../dist/img/logo_long_white.png" width="100px">
|
||||
<img src="../../dist/img/logo_wht.png" width="100px">
|
||||
</div>
|
||||
<small><small>
|
||||
<div class="modal-body" style="background-color: rgb(240, 240, 240); text-align: left;">
|
||||
|
||||
Reference in New Issue
Block a user