Update Banyak

This commit is contained in:
WD - Dev
2025-09-21 21:40:17 +07:00
parent c6bee729c5
commit efca8f5f80
145 changed files with 195185 additions and 1378 deletions
+14
View File
@@ -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]
+57
View File
@@ -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>
+57
View File
@@ -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";
+443
View File
@@ -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>';
}
+3
View File
@@ -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;
}
+94
View File
@@ -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>
+193
View File
@@ -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
]);
}
}
+285
View File
@@ -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;
}