Files
api-wipay/src/Controllers/PembayaranController.php

645 lines
26 KiB
PHP
Raw Normal View History

<?php
namespace App\Controllers;
use App\Helpers\HttpHelper;
use App\Helpers\KodeHelper;
use App\Helpers\QrisHelper;
use App\Helpers\ResponseHelper;
use App\Helpers\TelegramHelper;
use App\Helpers\WhatsAppHelper;
use App\Config\Database;
use App\Models\PembayaranModel;
use App\Models\UserModel;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class PembayaranController
{
private $pembayaranModel;
private $userModel;
private $db;
public function __construct()
{
$this->pembayaranModel = new PembayaranModel();
$this->userModel = new UserModel();
$this->db = Database::getInstance();
}
public function requestPembayaran(Request $request, Response $response): Response
{
$data = $request->getParsedBody();
$token = $data['token'] ?? '';
$no_sl = $data['no_sl'] ?? '';
$nama_bank = $data['nama_bank'] ?? '';
$no_rek = $data['no_rek'] ?? '';
$payment_method = $data['payment_method'] ?? 'transfer'; // transfer, qris
// Format response awal sama dengan API lama
$responseData = [
'status' => 404,
'pesan' => 'Gagal mendapatkan detail Tagihan anda, silahkan coba beberapa saat lagi'
];
if (empty($token) || empty($no_sl)) {
$responseData['pesan'] = 'Token dan nomor SL harus diisi';
return ResponseHelper::custom($response, $responseData, 404);
}
$pengguna = $this->userModel->findById($token);
if (!$pengguna) {
$responseData['pesan'] = 'Token tidak Valid. Silahkan Login dan Ulangi transaksi. Terima kasih';
return ResponseHelper::custom($response, $responseData, 404);
}
// Cek apakah ada pembayaran yang masih aktif
$cek_pembayaran = $this->pembayaranModel->findByTokenAndSL($token, $no_sl, 'DIBUAT');
if ($cek_pembayaran && strtotime($cek_pembayaran->waktu_expired) > time()) {
$responseData = [
'status' => 200,
'pesan' => '',
'data' => $cek_pembayaran
];
return ResponseHelper::custom($response, $responseData, 200);
}
// Jika ada pembayaran yang expired, update status
if ($cek_pembayaran) {
$this->pembayaranModel->update($cek_pembayaran->id_pembayaran, ['status_bayar' => 'EXPIRED']);
}
// Buat pembayaran baru
$biaya_admin = 0;
$promo = 0;
$jumlah_unik = 0; // QRIS tidak pakai kode unik
// Cek tagihan dari API TIMO
$respon = HttpHelper::doCurl('https://timo.tirtaintan.co.id/enquiry/' . $no_sl);
if (!$respon || $respon->errno != 0) {
$responseData['pesan'] = 'Gagal mendapatkan data tagihan dari server';
return ResponseHelper::custom($response, $responseData, 404);
}
if (count($respon->data) > 0) {
$total_tagihan = 0;
foreach ($respon->data as $d) {
$total_tagihan += $d->rek_total;
$biaya_admin += $pengguna->biaya_admin;
}
$total_pembayaran = $total_tagihan + $biaya_admin;
// Validasi QRIS: hanya untuk transaksi < 70 ribu
if ($payment_method === 'qris') {
if ($total_pembayaran > 70000) {
$responseData['pesan'] = 'QRIS hanya tersedia untuk transaksi di bawah Rp 70.000';
return ResponseHelper::custom($response, $responseData, 404);
}
// QRIS tidak pakai kode unik
$jumlah_unik = 0;
} else {
// BRI/Manual pakai kode unik
$jumlah_unik = KodeHelper::generateKodeUnikPrioritas();
}
$ins = [
'no_trx' => '#TIMO' . $respon->token,
'token' => $token,
'no_sl' => $no_sl,
'nama_bank' => $payment_method === 'qris' ? 'QRIS' : $nama_bank,
'no_rekening' => $no_rek,
'jumlah_tagihan' => (string)$total_tagihan,
'biaya_admin' => (string)$biaya_admin,
'jumlah_unik' => (string)$jumlah_unik,
'promo' => (string)$promo,
'raw_data' => json_encode($respon->data),
'waktu_expired' => date('Y-m-d H:i:s', strtotime('+1 days')),
'status_bayar' => 'DIBUAT',
'tanggal_bayar' => '0000-00-00 00:00:00',
'jumlah_bayar' => '0',
'bukti_transfer' => '',
'tanggal_request' => date('Y-m-d H:i:s'),
];
$pembayaranId = $this->pembayaranModel->create($ins);
// Jika QRIS, generate QR code
if ($payment_method === 'qris' && $pembayaranId) {
$qrisResponse = QrisHelper::createInvoice($ins['no_trx'], (int)$total_pembayaran, false);
if ($qrisResponse && isset($qrisResponse['data'])) {
$qrisData = $qrisResponse['data'];
$qrisRequestDate = date('Y-m-d H:i:s');
$expiredMinutes = QrisHelper::getExpiredMinutes();
$expiredAt = date('Y-m-d H:i:s', strtotime("+{$expiredMinutes} minutes"));
// Update pembayaran dengan data QRIS
$this->db->update('pembayaran', [
'qris_qr_code' => $qrisData['qris_content'] ?? '',
'qris_invoiceid' => $qrisData['qris_invoiceid'] ?? '',
'qris_nmid' => $qrisData['qris_nmid'] ?? QrisHelper::getNmid(),
'qris_request_date' => $qrisRequestDate,
'qris_expired_at' => $expiredAt,
'qris_check_count' => 0,
'qris_last_check_at' => null,
'qris_status' => 'unpaid' // Initial status
], 'id_pembayaran = :id', ['id' => $pembayaranId]);
$ins['qris_qr_code'] = $qrisData['qris_content'] ?? '';
$ins['qris_invoiceid'] = $qrisData['qris_invoiceid'] ?? '';
$ins['qris_nmid'] = $qrisData['qris_nmid'] ?? QrisHelper::getNmid();
$ins['qris_request_date'] = $qrisRequestDate;
$ins['qris_expired_at'] = $expiredAt;
$ins['qris_status'] = 'unpaid';
} else {
$responseData['pesan'] = 'Gagal generate QRIS, silahkan coba lagi';
return ResponseHelper::custom($response, $responseData, 404);
}
}
if ($total_tagihan > 0) {
$responseData = [
'status' => 200,
'pesan' => '',
'data' => $ins
];
} else {
$responseData['pesan'] = "Tidak ada tagihan untuk no SL $no_sl";
}
} else {
$responseData['pesan'] = "Tidak ada tagihan untuk no SL $no_sl";
}
return ResponseHelper::custom($response, $responseData, $responseData['status']);
}
public function cekPembayaran(Request $request, Response $response): Response
{
$data = $request->getParsedBody();
$token = $data['token'] ?? '';
$no_sl = $data['no_sl'] ?? '';
// Format response awal sama dengan API lama
$responseData = [
'status' => 404,
'pesan' => 'Gagal mendapatkan detail Tagihan anda, silahkan coba beberapa saat lagi'
];
if (empty($token) || empty($no_sl)) {
$responseData['pesan'] = 'Token dan nomor SL harus diisi';
return ResponseHelper::custom($response, $responseData, 404);
}
$pengguna = $this->userModel->findById($token);
if (!$pengguna) {
$responseData['pesan'] = 'Token tidak Valid. Silahkan Login dan Ulangi transaksi. Terima kasih';
return ResponseHelper::custom($response, $responseData, 404);
}
// Cek pembayaran dengan status DIBUAT atau MENUNGGU VERIFIKASI
$cek_pembayaran = $this->pembayaranModel->findByTokenAndSL($token, $no_sl, ['DIBUAT', 'MENUNGGU VERIFIKASI']);
if ($cek_pembayaran && strtotime($cek_pembayaran->waktu_expired) > time()) {
$responseData = [
'status' => 200,
'pesan' => '',
'data' => $cek_pembayaran
];
}
return ResponseHelper::custom($response, $responseData, $responseData['status']);
}
public function cekTransfer(Request $request, Response $response): Response
{
$data = $request->getParsedBody();
$token = $data['token'] ?? '';
$no_rek = $data['no_rek'] ?? '';
// Format response awal sama dengan API lama
$responseData = [
'status' => 404,
'pesan' => 'Gagal membatalkan pembayaran, silahkan coba beberapa saat lagi'
];
if (empty($token) || empty($no_rek)) {
$responseData['pesan'] = 'Token dan nomor rekening harus diisi';
return ResponseHelper::custom($response, $responseData, 404);
}
$pengguna = $this->userModel->findById($token);
if (!$pengguna) {
$responseData['pesan'] = 'Token tidak Valid. Silahkan Login dan Ulangi transaksi. Terima kasih';
return ResponseHelper::custom($response, $responseData, 404);
}
$cek_pembayaran = $this->pembayaranModel->findByNoTrx($token, $no_rek);
if ($cek_pembayaran) {
$responseData = [
'status' => 200,
'pesan' => '',
'data' => $cek_pembayaran
];
}
return ResponseHelper::custom($response, $responseData, $responseData['status']);
}
public function batalPembayaran(Request $request, Response $response): Response
{
$data = $request->getParsedBody();
$token = $data['token'] ?? '';
$no_rek = $data['no_rek'] ?? '';
// Format response awal sama dengan API lama
$responseData = [
'status' => 404,
'pesan' => 'Gagal membatalkan pembayaran, silahkan coba beberapa saat lagi'
];
if (empty($token) || empty($no_rek)) {
$responseData['pesan'] = 'Token dan nomor rekening harus diisi';
return ResponseHelper::custom($response, $responseData, 404);
}
$pengguna = $this->userModel->findById($token);
if (!$pengguna) {
$responseData['pesan'] = 'Token tidak Valid. Silahkan Login dan Ulangi transaksi. Terima kasih';
return ResponseHelper::custom($response, $responseData, 404);
}
$cek_pembayaran = $this->pembayaranModel->findByNoTrx($token, $no_rek);
if ($cek_pembayaran) {
$this->pembayaranModel->update($cek_pembayaran->id_pembayaran, ['status_bayar' => 'DIBATALKAN']);
// Format response sama dengan API lama: status 200, pesan tetap ada (tidak diubah)
$responseData['status'] = 200;
// Pesan tetap dengan nilai default, tidak diubah (sesuai API lama)
} else {
$responseData['pesan'] = 'Tidak ada data dengan no SL $';
}
return ResponseHelper::custom($response, $responseData, $responseData['status']);
}
public function confirmPembayaran(Request $request, Response $response): Response
{
$data = $request->getParsedBody();
$token = $data['token'] ?? '';
$no_rek = $data['no_rek'] ?? ''; // API lama menggunakan no_rek (no_trx)
// Format response awal sama dengan API lama
$responseData = [
'status' => 404,
'pesan' => 'Gagal membatalkan pembayaran, silahkan coba beberapa saat lagi'
];
if (empty($token) || empty($no_rek)) {
$responseData['pesan'] = 'Token dan nomor rekening harus diisi';
return ResponseHelper::custom($response, $responseData, 404);
}
$pengguna = $this->userModel->findById($token);
if (!$pengguna) {
$responseData['pesan'] = 'Token tidak Valid. Silahkan Login dan Ulangi transaksi. Terima kasih';
return ResponseHelper::custom($response, $responseData, 404);
}
// Cari pembayaran berdasarkan no_trx (no_rek)
$cek_pembayaran = $this->pembayaranModel->findByNoTrx($token, $no_rek);
if ($cek_pembayaran) {
// Update status ke MENUNGGU VERIFIKASI
$this->pembayaranModel->update($cek_pembayaran->id_pembayaran, [
'status_bayar' => 'MENUNGGU VERIFIKASI'
]);
// Kirim notifikasi Telegram
$pesan = "🔔 *TRANSAKSI BARU*\n\n"
. "No. Transaksi: " . $cek_pembayaran->no_trx . "\n"
. "No. SL: " . $cek_pembayaran->no_sl . "\n"
. "Jumlah: Rp " . number_format($cek_pembayaran->jumlah_tagihan + $cek_pembayaran->biaya_admin, 0, ',', '.') . "\n"
. "Status: MENUNGGU VERIFIKASI\n\n"
. "Silahkan verifikasi pembayaran.";
TelegramHelper::sendToTransactionAdmin($pesan);
// Update respon_wa field
$this->pembayaranModel->update($cek_pembayaran->id_pembayaran, [
'respon_wa' => 'TELEGRAM_SENT_' . date('Y-m-d H:i:s') . ' | SUCCESS'
]);
// Format response sama dengan API lama: status 200, pesan tetap ada (tidak diubah)
$responseData['status'] = 200;
// Pesan tetap dengan nilai default, tidak diubah (sesuai API lama)
}
return ResponseHelper::custom($response, $responseData, $responseData['status']);
}
public function historyBayar(Request $request, Response $response): Response
{
$data = $request->getParsedBody();
$token = $data['token'] ?? '';
// Format response awal sama dengan API lama
$responseData = [
'status' => 404,
'pesan' => '-'
];
if (empty($token)) {
$responseData['pesan'] = 'Token harus diisi';
return ResponseHelper::custom($response, $responseData, 404);
}
$pengguna = $this->userModel->findById($token);
if (!$pengguna) {
$responseData['pesan'] = 'Token tidak Valid';
return ResponseHelper::custom($response, $responseData, 404);
}
// History bayar hanya menampilkan yang status DIBAYAR (sama dengan API lama)
$history = $this->pembayaranModel->getHistoryByToken($token, 'DIBAYAR', 20);
$responseData = [
'status' => 200,
'pesan' => '',
'data' => $history
];
return ResponseHelper::custom($response, $responseData, 200);
}
/**
* POST /timo/cek_status_qris
* Check QRIS payment status (user-triggered)
*/
public function cekStatusQris(Request $request, Response $response): Response
{
$data = $request->getParsedBody();
$token = $data['token'] ?? '';
$no_sl = $data['no_sl'] ?? '';
$responseData = [
'status' => 404,
'pesan' => 'Gagal cek status QRIS'
];
if (empty($token) || empty($no_sl)) {
$responseData['pesan'] = 'Token dan nomor SL harus diisi';
return ResponseHelper::custom($response, $responseData, 404);
}
$pengguna = $this->userModel->findById($token);
if (!$pengguna) {
$responseData['pesan'] = 'Token tidak Valid';
return ResponseHelper::custom($response, $responseData, 404);
}
// Cari pembayaran QRIS dengan status DIBUAT
$pembayaran = $this->db->fetchOne(
"SELECT * FROM pembayaran
WHERE token = :token AND no_sl = :no_sl
AND nama_bank = 'QRIS'
AND status_bayar = 'DIBUAT'
AND qris_invoiceid IS NOT NULL
ORDER BY id_pembayaran DESC LIMIT 1",
['token' => $token, 'no_sl' => $no_sl]
);
if (!$pembayaran) {
$responseData['pesan'] = 'Pembayaran QRIS tidak ditemukan';
return ResponseHelper::custom($response, $responseData, 404);
}
// Cek apakah sudah expired
if ($pembayaran->qris_expired_at && strtotime($pembayaran->qris_expired_at) < time()) {
// Update status ke expired
$this->db->update('pembayaran', [
'qris_status' => 'expired'
], 'id_pembayaran = :id', ['id' => $pembayaran->id_pembayaran]);
$responseData['pesan'] = 'QRIS sudah expired';
$responseData['data'] = [
'status' => 'expired',
'message' => 'QRIS sudah expired. Silahkan buat pembayaran baru.'
];
return ResponseHelper::custom($response, $responseData, 404);
}
// Cek apakah sudah mencapai max attempts (3)
$checkCount = ($pembayaran->qris_check_count ?? 0);
if ($checkCount >= 3) {
$responseData = [
'status' => 200,
'pesan' => 'Silahkan upload bukti pembayaran atau hubungi customer service',
'data' => [
'status' => 'pending_verification',
'check_count' => $checkCount,
'message' => 'Pembayaran belum terdeteksi. Silahkan upload bukti pembayaran atau hubungi CS.',
'show_upload_proof' => true,
'show_contact_cs' => true
]
];
return ResponseHelper::custom($response, $responseData, 200);
}
// Cek status dari QRIS API dengan retry mechanism
$totalBayar = (int)$pembayaran->jumlah_tagihan + (int)$pembayaran->biaya_admin;
$transactionDate = $pembayaran->qris_request_date ?? $pembayaran->tanggal_request;
$transactionDate = date('Y-m-d', strtotime($transactionDate)); // Format: YYYY-MM-DD
// Gunakan checkStatusWithRetry (max 3 attempts, 15 seconds interval)
$qrisStatus = QrisHelper::checkStatusWithRetry(
(int)$pembayaran->qris_invoiceid,
$totalBayar,
$transactionDate
);
// Update check count
$checkCount = $checkCount + 1;
$this->db->update('pembayaran', [
'qris_check_count' => $checkCount,
'qris_last_check_at' => date('Y-m-d H:i:s')
], 'id_pembayaran = :id', ['id' => $pembayaran->id_pembayaran]);
// Check response
if ($qrisStatus && isset($qrisStatus['status']) && $qrisStatus['status'] == 'success') {
$qrisData = $qrisStatus['data'] ?? [];
$paymentStatus = $qrisData['qris_status'] ?? 'unpaid';
if ($paymentStatus == 'paid') {
// Payment sudah dibayar, auto approve
$this->autoApproveQris($pembayaran->id_pembayaran, $qrisData);
$responseData = [
'status' => 200,
'pesan' => 'Pembayaran berhasil',
'data' => [
'status' => 'paid',
'message' => 'Pembayaran QRIS berhasil diverifikasi',
'payment_method' => $qrisData['qris_payment_methodby'] ?? '',
'customer_name' => $qrisData['qris_payment_customername'] ?? ''
]
];
} else {
// Masih unpaid
if ($checkCount >= 3) {
$responseData = [
'status' => 200,
'pesan' => 'Silahkan upload bukti pembayaran atau hubungi customer service',
'data' => [
'status' => 'pending_verification',
'check_count' => $checkCount,
'message' => 'Pembayaran belum terdeteksi setelah 3x pengecekan. Silahkan upload bukti pembayaran.',
'show_upload_proof' => true,
'show_contact_cs' => true
]
];
} else {
$responseData = [
'status' => 200,
'pesan' => 'Menunggu pembayaran',
'data' => [
'status' => 'unpaid',
'check_count' => $checkCount,
'remaining_attempts' => 3 - $checkCount,
'message' => 'Silahkan scan QR code dan lakukan pembayaran'
]
];
}
}
} else {
// Request gagal atau response tidak valid
if ($checkCount >= 3) {
$responseData = [
'status' => 200,
'pesan' => 'Silahkan upload bukti pembayaran atau hubungi customer service',
'data' => [
'status' => 'pending_verification',
'check_count' => $checkCount,
'message' => 'Gagal mengecek status pembayaran. Silahkan upload bukti pembayaran.',
'show_upload_proof' => true,
'show_contact_cs' => true
]
];
} else {
$responseData = [
'status' => 200,
'pesan' => 'Menunggu pembayaran',
'data' => [
'status' => 'unpaid',
'check_count' => $checkCount,
'remaining_attempts' => 3 - $checkCount,
'message' => 'Silahkan scan QR code dan lakukan pembayaran'
]
];
}
}
return ResponseHelper::custom($response, $responseData, 200);
}
/**
* Auto approve QRIS payment setelah verified paid
*
* @param int $pembayaranId ID pembayaran
* @param array $qrisData Data dari QRIS API response
*/
private function autoApproveQris($pembayaranId, $qrisData = [])
{
try {
$pembayaran = $this->db->fetchOne(
"SELECT * FROM pembayaran WHERE id_pembayaran = :id LIMIT 1",
['id' => $pembayaranId]
);
if (!$pembayaran || $pembayaran->status_bayar !== 'DIBUAT') {
return false;
}
// Update status ke MENUNGGU VERIFIKASI (sementara)
$this->db->update('pembayaran', [
'status_bayar' => 'MENUNGGU VERIFIKASI',
'qris_status' => 'paid',
'qris_payment_method' => $qrisData['qris_payment_methodby'] ?? '',
'qris_payment_customer_name' => $qrisData['qris_payment_customername'] ?? ''
], 'id_pembayaran = :id', ['id' => $pembayaranId]);
// Approve ke PDAM (sama seperti SiteController::approve)
$token = str_replace('#TIMO', '', $pembayaran->no_trx);
$url = "https://timo.tirtaintan.co.id/payment/$token";
$data = [];
$rincian = json_decode($pembayaran->raw_data);
if (is_array($rincian) && count($rincian) > 0) {
foreach ($rincian as $r) {
$data[] = [
'rek_nomor' => $r->rek_nomor ?? $r->rek_no ?? '',
'rek_total' => $r->rek_total ?? 0,
'serial' => '#TM' . time(),
'byr_tgl' => date('YmdHis'),
'loket' => 'TIMO',
];
}
}
$post = [
'token' => $token,
'data' => $data
];
$headers = [
'Content-Type: application/json',
'Accept-Encoding: gzip, deflate',
'Cache-Control: max-age=0',
'Connection: keep-alive',
'Accept-Language: en-US,en;q=0.8,id;q=0.6'
];
$paymentResponse = HttpHelper::doCurl($url, 'POST', $post, true, $headers);
if ($paymentResponse && isset($paymentResponse->errno) && $paymentResponse->errno == 0) {
$totalBayar = (int)$pembayaran->jumlah_tagihan + (int)$pembayaran->biaya_admin;
$paidAt = date('Y-m-d H:i:s');
$this->db->update('pembayaran', [
'status_bayar' => 'DIBAYAR',
'tanggal_bayar' => $paidAt,
'jumlah_bayar' => (string)$totalBayar,
'raw_bayar' => json_encode($paymentResponse),
'qris_paid_at' => $paidAt
], 'id_pembayaran = :id', ['id' => $pembayaranId]);
// Kirim notifikasi WhatsApp ke user
$user = $this->userModel->findById($pembayaran->token);
if ($user && $user->no_hp) {
$pesan = "✅ *Pembayaran Berhasil*\n\n"
. "No. Transaksi: " . $pembayaran->no_trx . "\n"
. "No. SL: " . $pembayaran->no_sl . "\n"
. "Jumlah: Rp " . number_format($totalBayar, 0, ',', '.') . "\n"
. "Metode: QRIS\n"
. "E-Wallet: " . ($qrisData['qris_payment_methodby'] ?? '-') . "\n\n"
. "Terima kasih telah melakukan pembayaran.";
WhatsAppHelper::sendWa($user->no_hp, $pesan);
}
return true;
}
return false;
} catch (\Exception $e) {
error_log("Error in autoApproveQris: " . $e->getMessage());
return false;
}
}
}