Initial commit: API Wipay dengan fix CORS untuk GET request

This commit is contained in:
mwpn
2026-01-21 10:13:38 +07:00
commit 4895761764
70 changed files with 12036 additions and 0 deletions

View File

@@ -0,0 +1,68 @@
<?php
namespace App\Controllers;
use App\Config\Database;
use App\Helpers\ResponseHelper;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class ApiController
{
private $db;
public function __construct()
{
$this->db = Database::getInstance();
}
/**
* GET /api/mandiri/{tanggal}
* Data catat meter Mandiri berdasarkan tanggal
*/
public function mandiri(Request $request, Response $response, array $args): Response
{
$tanggal = $args['tanggal'] ?? '';
if (empty($tanggal)) {
$response->getBody()->write('DATE NOT SPECIFIED');
return $response->withStatus(400);
}
// Parse tanggal format ddmmyyyy
$format = "dmY";
$date = \DateTime::createFromFormat($format, $tanggal);
if ($date) {
$tanggal_cari = $date->format('Y-m-d');
} else {
$tanggal_cari = date('Y-m-d');
}
// Get base URL from environment or request
$baseUrl = $_ENV['BASE_URL'] ??
($request->getUri()->getScheme() . '://' . $request->getUri()->getHost());
// Query data
$sql = "SELECT cm.no_sl, pt.no_hp, cm.tanggal_catat as tanggal_baca,
cm.angka_meter,
CONCAT(:base_url, '/assets/uploads/catat_meter/', cm.photo) as photo
FROM catat_meter cm
LEFT JOIN pengguna_timo pt ON cm.token = pt.id_pengguna_timo
WHERE DATE(cm.tanggal_catat) = :tanggal_cari";
$data = $this->db->fetchAll($sql, [
'base_url' => $baseUrl,
'tanggal_cari' => $tanggal_cari
]);
// Format response sama dengan API lama: status: 1 (bukan 200)
$responseData = [
'status' => 1,
'date' => $tanggal,
'data' => $data
];
return ResponseHelper::json($response, $responseData, 200);
}
}

View File

@@ -0,0 +1,272 @@
<?php
namespace App\Controllers;
use App\Helpers\ResponseHelper;
use App\Models\UserModel;
use App\Services\AuthService;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class AuthController
{
private $authService;
private $userModel;
public function __construct()
{
$this->authService = new AuthService();
$this->userModel = new UserModel();
}
public function daftar(Request $request, Response $response): Response
{
try {
$data = $request->getParsedBody();
$nama = $data['nama'] ?? '';
$username = $data['username'] ?? '';
$email = $data['email'] ?? '';
$no_hp = $data['no_hp'] ?? '';
$password = $data['password'] ?? '';
// Format response awal sama dengan API lama
$responseData = [
'status' => 404,
'pesan' => '-'
];
if (empty($username) || empty($password) || empty($email) || empty($no_hp)) {
$responseData['pesan'] = 'Data tidak lengkap';
return ResponseHelper::custom($response, $responseData, 404);
}
// Cek username sudah ada
$cekUsername = $this->userModel->findByUsername($username);
if ($cekUsername) {
$responseData['pesan'] = 'Username yang anda pilih tidak bisa digunakan';
return ResponseHelper::custom($response, $responseData, 404);
}
// Cek email sudah ada
$cekEmail = $this->userModel->findByEmail($email);
if ($cekEmail) {
$responseData['pesan'] = 'Email yang anda masukan sudah ada yang menggunakan!, silahkan gunakan email lain';
return ResponseHelper::custom($response, $responseData, 404);
}
// Default biaya admin (sesuai backend lama: 2500)
$biayaAdmin = 2500; // Default value dari config.php: default_biaya_admin
$userData = [
'nama_lengkap' => $nama,
'username' => $username,
'password' => md5($password),
'email' => $email,
'no_hp' => $no_hp,
'biaya_admin' => $biayaAdmin,
];
$this->userModel->create($userData);
// Format response sukses sama dengan API lama
$responseData = [
'status' => 200,
'pesan' => 'Akun berhasil dibuat, silahkan login'
];
return ResponseHelper::custom($response, $responseData, 200);
} catch (\Exception $e) {
error_log("Error in daftar: " . $e->getMessage());
return ResponseHelper::custom($response, [
'status' => 404,
'pesan' => 'Gagal membuat akun: ' . $e->getMessage()
], 404);
}
}
public function login(Request $request, Response $response): Response
{
$data = $request->getParsedBody();
$username = $data['username'] ?? '';
$password = $data['password'] ?? '';
$fcm_token = $data['fcm_token'] ?? '';
if (empty($username) || empty($password)) {
return ResponseHelper::error($response, 'Username dan password harus diisi', 404);
}
$user = $this->userModel->findByUsername($username);
if (!$user || $user->password !== md5($password)) {
return ResponseHelper::error($response, 'Akun tidak ditemukan, pastikan username dan password yang anda masukan sudah terdaftar', 404);
}
// Update FCM token
if (!empty($fcm_token)) {
$this->userModel->update($user->id_pengguna_timo, ['ftoken' => $fcm_token]);
}
// Get SL list
$slList = $this->userModel->getSLList($user->id_pengguna_timo);
// Convert user object ke array
$userArray = [
'id_pengguna_timo' => $user->id_pengguna_timo,
'nama_lengkap' => $user->nama_lengkap ?? '',
'username' => $user->username ?? '',
'email' => $user->email ?? '',
'no_hp' => $user->no_hp ?? '',
'biaya_admin' => $user->biaya_admin ?? '1500',
'photo' => $user->photo ?? '',
'ftoken' => $user->ftoken ?? '',
];
// Convert SL list objects ke array
$slListArray = [];
foreach ($slList as $sl) {
$slListArray[] = [
'pel_no' => $sl->pel_no ?? '',
'pel_nama' => $sl->pel_nama ?? '',
'pel_alamat' => $sl->pel_alamat ?? '',
'dkd_kd' => $sl->dkd_kd ?? '',
'rek_gol' => $sl->rek_gol ?? '',
];
}
return ResponseHelper::success($response, 'Selamat Datang ' . $user->nama_lengkap, [
'user' => $userArray,
'data_sl' => $slListArray
], 200);
}
public function loginToken(Request $request, Response $response): Response
{
$data = $request->getParsedBody();
$username = $data['username'] ?? '';
$password = $data['password'] ?? ''; // Password sudah di-hash
$fcm_token = $data['fcm_token'] ?? '';
// Format response awal sama dengan API lama
$responseData = [
'status' => 404,
'pesan' => '-'
];
if (empty($username) || empty($password)) {
$responseData['pesan'] = 'Username dan password harus diisi';
return ResponseHelper::custom($response, $responseData, 404);
}
$user = $this->userModel->findByUsername($username);
if (!$user || $user->password !== $password) {
$responseData['pesan'] = 'Akun tidak ditemukan, pastikan username dan password yang anda masukan sudah terdaftar';
return ResponseHelper::custom($response, $responseData, 404);
}
// Update FCM token
if (!empty($fcm_token)) {
$this->userModel->update($user->id_pengguna_timo, ['ftoken' => $fcm_token]);
}
// Get SL list
$slList = $this->userModel->getSLList($user->id_pengguna_timo);
// Format response sama dengan API lama: status, pesan, user, data_sl langsung di root
$responseData = [
'status' => 200,
'pesan' => 'Selamat Datang ' . $user->nama_lengkap,
'user' => $user,
'data_sl' => $slList
];
return ResponseHelper::custom($response, $responseData, 200);
}
public function updateAkun(Request $request, Response $response): Response
{
$data = $request->getParsedBody();
$token = $data['token'] ?? '';
$nama = $data['nama'] ?? '';
$email = $data['email'] ?? '';
$hp = $data['hp'] ?? '';
// Format response awal sama dengan API lama
$responseData = [
'status' => 404,
'pesan' => 'Gagal ubah data, silahkan coba beberapa saat lagi'
];
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. Silahkan Login dan Ulangi transaksi. Terima kasih';
return ResponseHelper::custom($response, $responseData, 404);
}
// Update fields
$updateData = [];
if (!empty($nama)) $updateData['nama_lengkap'] = $nama;
if (!empty($email)) $updateData['email'] = $email;
if (!empty($hp)) $updateData['no_hp'] = $hp;
if (!empty($updateData)) {
$this->userModel->update($token, $updateData);
}
// Get updated user data
$updatedUser = $this->userModel->findById($token);
$responseData = [
'status' => 200,
'pesan' => 'Data berhasil di ubah',
'data' => $updatedUser
];
return ResponseHelper::custom($response, $responseData, 200);
}
public function updatePassword(Request $request, Response $response): Response
{
$data = $request->getParsedBody();
$token = $data['token'] ?? '';
$passlama = $data['passlama'] ?? '';
$passbaru = $data['passbaru'] ?? '';
// Format response awal sama dengan API lama
$responseData = [
'status' => 404,
'pesan' => 'Gagal ubah data, silahkan coba beberapa saat lagi'
];
if (empty($token) || empty($passlama) || empty($passbaru)) {
$responseData['pesan'] = 'Data tidak lengkap';
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);
}
if (md5($passlama) == $pengguna->password) {
$this->userModel->update($token, ['password' => md5($passbaru)]);
$responseData = [
'status' => 200,
'pesan' => 'Password berhasil di ubah'
];
} else {
$responseData['pesan'] = 'Password lama tidak sesuai, silahkan coba lagi';
}
return ResponseHelper::custom($response, $responseData, $responseData['status']);
}
}

View File

@@ -0,0 +1,635 @@
<?php
namespace App\Controllers;
use App\Config\Database;
use App\Helpers\HttpHelper;
use App\Helpers\ResponseHelper;
use App\Models\ApiKeyModel;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class FastController
{
private $db;
private $apiKeyModel;
public function __construct()
{
$this->db = Database::getInstance();
$this->apiKeyModel = new ApiKeyModel();
}
/**
* GET /fast/test
* Test endpoint (tidak perlu auth)
*/
public function test(Request $request, Response $response): Response
{
$baseUrl = $_ENV['BASE_URL'] ??
($request->getUri()->getScheme() . '://' . $request->getUri()->getHost());
return ResponseHelper::json($response, [
'status' => 'success',
'message' => 'Fast WIPAY API is working!',
'timestamp' => date('Y-m-d H:i:s'),
'controller' => 'Fast',
'method' => 'test',
'url' => $baseUrl . $request->getUri()->getPath()
], 200);
}
/**
* POST /fast/check_bill
* Cek tagihan PDAM
*/
public function checkBill(Request $request, Response $response): Response
{
try {
// Get API key from request attributes (set by middleware)
$apiKey = $request->getAttribute('api_key');
if (!$apiKey) {
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'Invalid API key'
], 401);
}
// Get input from multiple sources
$data = $request->getParsedBody() ?? [];
$query = $request->getQueryParams();
$no_sl = $data['no_sl'] ?? $query['no_sl'] ?? '';
if (empty($no_sl)) {
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'No SL is required'
], 400);
}
// Get admin user and timo user
$adminUser = $this->db->fetchOne(
"SELECT * FROM admin_users WHERE id = :id LIMIT 1",
['id' => $apiKey->admin_user_id]
);
if (!$adminUser || !$adminUser->timo_user) {
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'Admin user tidak memiliki user TIMO'
], 404);
}
$timoUser = $this->db->fetchOne(
"SELECT * FROM pengguna_timo WHERE id_pengguna_timo = :id LIMIT 1",
['id' => $adminUser->timo_user]
);
if (!$timoUser) {
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'User TIMO tidak ditemukan'
], 404);
}
// Get WIPAY user (optional for check_bill)
$wipayUser = null;
if ($timoUser->wipay) {
$wipayUser = $this->db->fetchOne(
"SELECT * FROM wipay_pengguna WHERE id_wipay = :id LIMIT 1",
['id' => $timoUser->wipay]
);
}
// Call TIMO API untuk cek tagihan
$timoUrl = 'https://timo.tirtaintan.co.id/enquiry/' . $no_sl;
$timoResponse = HttpHelper::doCurl($timoUrl, 'GET');
// Handle response format - HttpHelper returns object with status/body or decoded JSON
if (!$timoResponse) {
$this->apiKeyModel->logApiUsage($apiKey->id, 'check_bill', 'failed', [
'no_sl' => $no_sl,
'error' => 'Failed to connect to TIMO API'
]);
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'Gagal cek tagihan: Tidak dapat menghubungi server TIMO'
], 500);
}
// Check if it's an HTTP error response
if (is_object($timoResponse) && isset($timoResponse->status) && $timoResponse->status != 200) {
$this->apiKeyModel->logApiUsage($apiKey->id, 'check_bill', 'failed', [
'no_sl' => $no_sl,
'error' => $timoResponse->error ?? 'HTTP Error'
]);
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'Gagal cek tagihan: ' . ($timoResponse->error ?? 'HTTP Error')
], $timoResponse->status);
}
// Normal response with errno
if (isset($timoResponse->errno)) {
// Log API usage
$this->apiKeyModel->logApiUsage($apiKey->id, 'check_bill',
$timoResponse->errno == 0 ? 'success' : 'api_error', [
'no_sl' => $no_sl,
'timo_user_id' => $timoUser->id_pengguna_timo,
'wipay_user_id' => $wipayUser ? $wipayUser->id_wipay : null
]);
return ResponseHelper::json($response, [
'status' => 'success',
'data' => $timoResponse,
'message' => 'Tagihan berhasil dicek'
], 200);
} else {
// Log failed API call
$this->apiKeyModel->logApiUsage($apiKey->id, 'check_bill', 'failed', [
'no_sl' => $no_sl,
'error' => 'Failed to connect to TIMO API'
]);
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'Gagal cek tagihan: Tidak dapat menghubungi server TIMO'
], 500);
}
} catch (\Exception $e) {
error_log("Error in checkBill: " . $e->getMessage());
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'Gagal cek tagihan: ' . $e->getMessage()
], 500);
}
}
/**
* POST /fast/process_payment
* Proses pembayaran PDAM
*/
public function processPayment(Request $request, Response $response): Response
{
try {
// Get API key
$apiKey = $request->getAttribute('api_key');
if (!$apiKey) {
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'Invalid API key'
], 401);
}
// Get input
$data = $request->getParsedBody() ?? [];
$query = $request->getQueryParams();
$no_sl = $data['no_sl'] ?? $query['no_sl'] ?? '';
$amount = $data['amount'] ?? $query['amount'] ?? 0;
$token = $data['token'] ?? $query['token'] ?? '';
if (empty($no_sl) || empty($amount) || empty($token)) {
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'No SL, amount, and token are required'
], 400);
}
if (!is_numeric($amount)) {
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'Amount must be a valid number'
], 400);
}
$amount = (float)$amount;
// Get admin user and timo user
$adminUser = $this->db->fetchOne(
"SELECT * FROM admin_users WHERE id = :id LIMIT 1",
['id' => $apiKey->admin_user_id]
);
if (!$adminUser || !$adminUser->timo_user) {
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'Admin user tidak memiliki user TIMO'
], 404);
}
$timoUser = $this->db->fetchOne(
"SELECT * FROM pengguna_timo WHERE id_pengguna_timo = :id LIMIT 1",
['id' => $adminUser->timo_user]
);
if (!$timoUser) {
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'User TIMO tidak ditemukan'
], 404);
}
// Get WIPAY user
$wipayUser = $this->db->fetchOne(
"SELECT * FROM wipay_pengguna WHERE id_wipay = :id LIMIT 1",
['id' => $timoUser->wipay]
);
if (!$wipayUser) {
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'User tidak memiliki akun WIPAY'
], 400);
}
// Get current saldo
$saldo = $this->getWipaySaldo($wipayUser->id_wipay);
// Calculate total payment
$biayaAdmin = $timoUser->biaya_admin ?: 0;
$totalPayment = $amount + $biayaAdmin;
// Validate saldo
if ($saldo < $totalPayment) {
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'Saldo WIPAY tidak mencukupi. Saldo: Rp ' . number_format($saldo, 0, ',', '.') . ', Total: Rp ' . number_format($totalPayment, 0, ',', '.')
], 400);
}
// Get tagihan detail from PDAM API
$timoUrl = 'https://timo.tirtaintan.co.id/enquiry/' . $no_sl;
$enquiryResponse = HttpHelper::doCurl($timoUrl, 'GET');
// Handle HTTP error
if (is_object($enquiryResponse) && isset($enquiryResponse->status) && $enquiryResponse->status != 200) {
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'Gagal mendapatkan data tagihan dari PDAM'
], 500);
}
if (!$enquiryResponse || !isset($enquiryResponse->data)) {
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'Data tagihan tidak valid'
], 500);
}
// Prepare payment data for PDAM
$pdamData = [];
foreach ($enquiryResponse->data as $d) {
$pdamData[] = [
'rek_nomor' => $d->rek_nomor ?? $d->rek_no ?? '',
'rek_total' => $d->rek_total ?? 0,
'serial' => '#TM' . time(),
'byr_tgl' => date('YmdHis'),
'loket' => 'TIMO',
];
}
$paymentPost = [
'token' => $token,
'data' => $pdamData
];
// Send payment to PDAM API
$timoPaymentUrl = 'https://timo.tirtaintan.co.id/payment/' . $token;
$paymentResponse = HttpHelper::doCurl($timoPaymentUrl, 'POST', $paymentPost, true);
if ($paymentResponse && isset($paymentResponse->errno) && $paymentResponse->errno == 0) {
// Payment successful - Record to database
$pembayaranData = [
'no_trx' => '#TIMO' . $token,
'token' => $adminUser->timo_user,
'no_sl' => $no_sl,
'nama_bank' => 'WIPAY',
'no_rekening' => $wipayUser->no_hp ?? '',
'jumlah_tagihan' => (string)$amount,
'biaya_admin' => (string)$biayaAdmin,
'jumlah_unik' => '0',
'promo' => '0',
'raw_data' => json_encode($enquiryResponse->data),
'waktu_expired' => date('Y-m-d H:i:s', strtotime('+1 days')),
'status_bayar' => 'DIBAYAR',
'tanggal_bayar' => date('Y-m-d H:i:s'),
'jumlah_bayar' => (string)$totalPayment,
'bukti_transfer' => '',
'tanggal_request' => date('Y-m-d H:i:s'),
'respon_wa' => '',
'admin_2' => '0',
'raw_bayar' => json_encode($paymentResponse),
'banyak_cek' => '0'
];
$pembayaranId = $this->db->insert('pembayaran', $pembayaranData);
// Deduct WIPAY saldo
$this->db->insert('wipay_mutasi', [
'wipay_user' => $wipayUser->id_wipay,
'waktu_transaksi' => date('Y-m-d H:i:s'),
'jumlah_mutasi' => $totalPayment * -1,
'saldo_akhir' => $saldo - $totalPayment,
'ket_mutasi' => "PEMBAYARAN PDAM SL $no_sl via Fast API",
'detail_transaksi' => serialize($pembayaranData),
'sumber_transaksi' => 'TRANSAKSI',
]);
// Update WIPAY saldo
$this->db->update('wipay_pengguna', [
'saldo' => $saldo - $totalPayment
], 'id_wipay = :id', ['id' => $wipayUser->id_wipay]);
// Log API usage
$this->apiKeyModel->logApiUsage($apiKey->id, 'process_payment', 'success', [
'no_sl' => $no_sl,
'amount' => $amount,
'token' => $token,
'pembayaran_id' => $pembayaranId
]);
return ResponseHelper::json($response, [
'status' => 'success',
'message' => 'Pembayaran berhasil diproses',
'data' => [
'pembayaran_id' => $pembayaranId,
'no_trx' => $pembayaranData['no_trx'],
'no_sl' => $no_sl,
'amount' => $amount,
'biaya_admin' => $biayaAdmin,
'total_payment' => $totalPayment,
'saldo_akhir' => $saldo - $totalPayment,
'status' => 'DIBAYAR',
'tanggal_pembayaran' => date('Y-m-d H:i:s')
]
], 200);
} else {
// Payment failed
$this->apiKeyModel->logApiUsage($apiKey->id, 'process_payment', 'failed', [
'no_sl' => $no_sl,
'amount' => $amount,
'token' => $token,
'error' => json_encode($paymentResponse)
]);
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'Gagal proses pembayaran ke PDAM: ' . ($paymentResponse->error ?? 'Unknown error')
], 500);
}
} catch (\Exception $e) {
error_log("Error in processPayment: " . $e->getMessage());
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'Gagal proses pembayaran: ' . $e->getMessage()
], 500);
}
}
/**
* GET /fast/process_payment_get
* Proses pembayaran via GET (temporary workaround)
*/
public function processPaymentGet(Request $request, Response $response): Response
{
// Same logic as processPayment but get data from query params
$query = $request->getQueryParams();
$request = $request->withParsedBody($query);
return $this->processPayment($request, $response);
}
/**
* GET /fast/payment_status
* Cek status pembayaran
*/
public function paymentStatus(Request $request, Response $response): Response
{
try {
// Get API key
$apiKey = $request->getAttribute('api_key');
if (!$apiKey) {
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'Invalid API key'
], 401);
}
// Get transaction_id
$data = $request->getParsedBody() ?? [];
$query = $request->getQueryParams();
$transactionId = $data['transaction_id'] ?? $query['transaction_id'] ?? '';
if (empty($transactionId)) {
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'Transaction ID is required'
], 400);
}
// Check payment status
$payment = $this->db->fetchOne(
"SELECT * FROM pembayaran WHERE id_pembayaran = :id LIMIT 1",
['id' => $transactionId]
);
if ($payment) {
return ResponseHelper::json($response, [
'status' => 'success',
'data' => [
'transaction_id' => $payment->id_pembayaran,
'no_sl' => $payment->no_sl,
'amount' => $payment->jumlah_tagihan,
'status' => $payment->status_bayar,
'created_at' => $payment->tanggal_request
]
], 200);
} else {
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'Transaction not found'
], 404);
}
} catch (\Exception $e) {
error_log("Error in paymentStatus: " . $e->getMessage());
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'Gagal cek status pembayaran: ' . $e->getMessage()
], 500);
}
}
/**
* GET /fast/check_wipay_saldo
* Cek saldo WIPAY
*/
public function checkWipaySaldo(Request $request, Response $response): Response
{
try {
// Get API key
$apiKey = $request->getAttribute('api_key');
if (!$apiKey) {
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'Invalid API key'
], 401);
}
// Get admin user and timo user
$adminUser = $this->db->fetchOne(
"SELECT * FROM admin_users WHERE id = :id LIMIT 1",
['id' => $apiKey->admin_user_id]
);
if (!$adminUser || !$adminUser->timo_user) {
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'Admin user tidak memiliki data TIMO'
], 404);
}
$timoUser = $this->db->fetchOne(
"SELECT * FROM pengguna_timo WHERE id_pengguna_timo = :id LIMIT 1",
['id' => $adminUser->timo_user]
);
if (!$timoUser) {
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'User TIMO tidak ditemukan'
], 404);
}
if (!$timoUser->wipay || $timoUser->wipay <= 0) {
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'User tidak memiliki akun WIPAY'
], 400);
}
$wipayUser = $this->db->fetchOne(
"SELECT * FROM wipay_pengguna WHERE id_wipay = :id LIMIT 1",
['id' => $timoUser->wipay]
);
if (!$wipayUser) {
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'Data WIPAY user tidak ditemukan'
], 400);
}
$saldo = $this->getWipaySaldo($wipayUser->id_wipay);
// Log API usage
$this->apiKeyModel->logApiUsage($apiKey->id, 'check_wipay_saldo', 'success', [
'user_id' => $apiKey->admin_user_id,
'wipay_user_id' => $wipayUser->id_wipay
]);
return ResponseHelper::json($response, [
'status' => 'success',
'message' => 'Saldo WIPAY berhasil dicek',
'data' => [
'user_id' => $apiKey->admin_user_id,
'wipay_user_id' => $wipayUser->id_wipay,
'nama_lengkap' => $wipayUser->nama_lengkap ?? '',
'no_hp' => $wipayUser->no_hp ?? '',
'saldo' => $saldo,
'saldo_formatted' => 'Rp ' . number_format($saldo, 0, ',', '.'),
'biaya_admin' => $timoUser->biaya_admin ?: 0
]
], 200);
} catch (\Exception $e) {
error_log("Error in checkWipaySaldo: " . $e->getMessage());
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'Gagal cek saldo WIPAY: ' . $e->getMessage()
], 500);
}
}
/**
* GET /fast/check_wipay_saldo_get
* Cek saldo WIPAY via GET
*/
public function checkWipaySaldoGet(Request $request, Response $response): Response
{
// Same as checkWipaySaldo
return $this->checkWipaySaldo($request, $response);
}
/**
* GET /fast/mandiri/{tanggal}
* Data Mandiri (mirip dengan /api/mandiri)
*/
public function mandiri(Request $request, Response $response, array $args): Response
{
$tanggal = $args['tanggal'] ?? '';
if (empty($tanggal)) {
$response->getBody()->write('DATE NOT SPECIFIED');
return $response->withStatus(400);
}
// Parse tanggal format ddmmyyyy
$format = "dmY";
$date = \DateTime::createFromFormat($format, $tanggal);
if ($date) {
$tanggal_cari = $date->format('Y-m-d');
} else {
$tanggal_cari = date('Y-m-d');
}
// Get base URL
$baseUrl = $_ENV['BASE_URL'] ??
($request->getUri()->getScheme() . '://' . $request->getUri()->getHost());
// Query data
$sql = "SELECT cm.no_sl, pt.no_hp, cm.tanggal_catat as tanggal_baca,
cm.angka_meter,
CONCAT(:base_url, '/assets/uploads/catat_meter/', cm.photo) as photo
FROM catat_meter cm
LEFT JOIN pengguna_timo pt ON cm.token = pt.id_pengguna_timo
WHERE DATE(cm.tanggal_catat) = :tanggal_cari";
$data = $this->db->fetchAll($sql, [
'base_url' => $baseUrl,
'tanggal_cari' => $tanggal_cari
]);
return ResponseHelper::json($response, [
'status' => 1,
'date' => $tanggal,
'data' => $data
], 200);
}
/**
* Get WIPAY saldo
*/
private function getWipaySaldo($wipayUserId)
{
// Get latest saldo from mutasi or wipay_pengguna
$mutasi = $this->db->fetchOne(
"SELECT saldo_akhir FROM wipay_mutasi
WHERE wipay_user = :id
ORDER BY waktu_transaksi DESC LIMIT 1",
['id' => $wipayUserId]
);
if ($mutasi && isset($mutasi->saldo_akhir)) {
return (float)$mutasi->saldo_akhir;
}
// Fallback to wipay_pengguna saldo
$wipayUser = $this->db->fetchOne(
"SELECT saldo FROM wipay_pengguna WHERE id_wipay = :id LIMIT 1",
['id' => $wipayUserId]
);
return $wipayUser ? (float)($wipayUser->saldo ?? 0) : 0;
}
}

View File

@@ -0,0 +1,88 @@
<?php
namespace App\Controllers;
use App\Config\Database;
use App\Helpers\ResponseHelper;
use App\Models\UserModel;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class LaporanController
{
private $db;
private $userModel;
public function __construct()
{
$this->db = Database::getInstance();
$this->userModel = new UserModel();
}
public function jenisLaporan(Request $request, Response $response): Response
{
// Format response awal sama dengan API lama
$responseData = [
'status' => 404,
'pesan' => 'Error 404'
];
$riwayat = $this->db->fetchAll(
"SELECT * FROM jenis_gangguan ORDER BY tipe",
[]
);
if ($riwayat) {
$responseData = [
'status' => 200,
'pesan' => '',
'data' => $riwayat
];
}
return ResponseHelper::custom($response, $responseData, $responseData['status']);
}
public function historyGangguan(Request $request, Response $response): Response
{
$data = $request->getParsedBody();
$token = $data['token'] ?? '';
// Format response awal sama dengan API lama
$responseData = [
'status' => 404,
'pesan' => 'Gagal mendapatkan detail Tagihan anda, silahkan coba beberapa saat lagi'
];
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. Silahkan Login dan Ulangi transaksi. Terima kasih';
return ResponseHelper::custom($response, $responseData, 404);
}
// API lama menggunakan JOIN dengan jenis_gangguan dan ORDER BY waktu_laporan DESC
$riwayat = $this->db->fetchAll(
"SELECT g.*, jg.* FROM gangguan g
LEFT JOIN jenis_gangguan jg ON g.jenis_gangguan = jg.id_jenis_gangguan
WHERE g.token = :token
ORDER BY g.waktu_laporan DESC
LIMIT 20",
['token' => $token]
);
if ($riwayat) {
$responseData = [
'status' => 200,
'pesan' => '',
'data' => $riwayat
];
}
return ResponseHelper::custom($response, $responseData, $responseData['status']);
}
}

View File

@@ -0,0 +1,194 @@
<?php
namespace App\Controllers;
use App\Config\Database;
use App\Helpers\HttpHelper;
use App\Helpers\ResponseHelper;
use App\Models\UserModel;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class OtherController
{
private $db;
private $userModel;
public function __construct()
{
$this->db = Database::getInstance();
$this->userModel = new UserModel();
}
public function promo(Request $request, Response $response): Response
{
// Format response awal sama dengan API lama
$responseData = [
'status' => 404,
'pesan' => 'Tidak ada Promo'
];
// API lama menggunakan timo_promo dengan status_promo = 'AKTIF' dan berakhir_promo >= sekarang
$promo = $this->db->fetchAll(
"SELECT * FROM timo_promo WHERE status_promo = 'AKTIF' AND berakhir_promo >= NOW() ORDER BY id_promo DESC",
[]
);
if ($promo) {
$responseData = [
'status' => 200,
'pesan' => '',
'data' => $promo
];
}
return ResponseHelper::custom($response, $responseData, $responseData['status']);
}
public function riwayatPasang(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);
}
$riwayat = $this->db->fetchAll(
"SELECT * FROM pasang_baru WHERE token = :token ORDER BY id_pasang_baru DESC",
['token' => $token]
);
$responseData = [
'status' => 200,
'data' => $riwayat
];
return ResponseHelper::custom($response, $responseData, 200);
}
public function jadwalCatatMeter(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. Silahkan Login dan Ulangi transaksi. Terima kasih';
return ResponseHelper::custom($response, $responseData, 404);
}
// Get jadwal dari pengaturan
$jadwal = $this->db->fetchOne(
"SELECT * FROM pengaturan WHERE kata_kunci = 'WAKTU_CATAT_METER' LIMIT 1",
[]
);
if ($jadwal) {
// Get riwayat catat meter
$riwayat = $this->db->fetchAll(
"SELECT * FROM catat_meter WHERE token = :token ORDER BY id_catat_meter DESC LIMIT 50",
['token' => $token]
);
$responseData = [
'status' => 200,
'awal' => (int)$jadwal->val_1,
'akhir' => (int)$jadwal->val_2,
'riwayat' => $riwayat
];
}
return ResponseHelper::custom($response, $responseData, $responseData['status']);
}
public function requestOrderBacaMandiri(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' => '-'
];
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);
}
// Request order ke API eksternal (sesuai API lama: sendBacaMandiriRequest)
$url = 'https://rasamala.tirtaintan.co.id/timo/order-cater/' . $no_sl;
$postData = [
'kar_id' => 'timo'
];
// API lama menggunakan form-urlencoded, bukan JSON
$headers = [
'Content-Type: application/x-www-form-urlencoded',
'Accept: application/json'
];
$apiResponse = HttpHelper::doCurl($url, 'POST', $postData, false, $headers, 30, 10);
// Handle response - bisa array atau object
if ($apiResponse) {
if (is_array($apiResponse) && !empty($apiResponse)) {
$responseData = [
'status' => 200,
'pesan' => 'Order baca mandiri berhasil diambil',
'data' => $apiResponse
];
} elseif (is_object($apiResponse) && isset($apiResponse->data)) {
$responseData = [
'status' => 200,
'pesan' => 'Order baca mandiri berhasil diambil',
'data' => $apiResponse->data
];
} elseif (is_object($apiResponse) && !empty((array)$apiResponse)) {
$responseData = [
'status' => 200,
'pesan' => 'Order baca mandiri berhasil diambil',
'data' => $apiResponse
];
} else {
$responseData['pesan'] = 'Tidak ada order baca mandiri untuk SL ini';
}
} else {
$responseData['pesan'] = 'Tidak ada order baca mandiri untuk SL ini';
}
return ResponseHelper::custom($response, $responseData, $responseData['status']);
}
}

View File

@@ -0,0 +1,644 @@
<?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;
}
}
}

View File

@@ -0,0 +1,179 @@
<?php
namespace App\Controllers;
use App\Config\Database;
use App\Helpers\KodeHelper;
use App\Helpers\ResponseHelper;
use App\Helpers\WhatsAppHelper;
use App\Models\UserModel;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class ResetPasswordController
{
private $db;
private $userModel;
public function __construct()
{
$this->db = Database::getInstance();
$this->userModel = new UserModel();
}
public function buatKode(Request $request, Response $response): Response
{
$data = $request->getParsedBody();
$email = $data['email'] ?? '';
// Format response awal sama dengan API lama
$responseData = [
'status' => 404,
'pesan' => 'Kami sedang melakukan peningkatan sistem, silahkan coba beberapa saat lagi'
];
if (empty($email)) {
$responseData['pesan'] = 'Email/No HP harus diisi';
return ResponseHelper::custom($response, $responseData, 404);
}
// Cari user berdasarkan no_hp (email di API lama sebenarnya no_hp)
$pengguna = $this->db->fetchOne(
"SELECT * FROM pengguna_timo WHERE no_hp = :no_hp LIMIT 1",
['no_hp' => $email]
);
if (!$pengguna) {
$responseData['pesan'] = 'No HP tidak terdaftar. Silahkan buat akun atau masukan kembali email yang terdaftar. Terima kasih';
return ResponseHelper::custom($response, $responseData, 404);
}
// Generate kode verifikasi (6 digit)
$kode_verifikasi = KodeHelper::generateRandomString(6);
// Insert kode verifikasi
$this->db->insert('kode_verifikasi_timo', [
'email' => $pengguna->email,
'kode' => (string)$kode_verifikasi,
'waktu_ver' => date('Y-m-d H:i:s'),
'waktu_exp' => date('Y-m-d H:i:s', strtotime('+10 minute')),
'status_reset' => 'DIBUAT'
]);
// Kirim WhatsApp
if ($pengguna && !empty($pengguna->no_hp)) {
$wa_pesan = "Halo {$pengguna->nama_lengkap},\n\n"
. "Kode verifikasi untuk reset password Anda adalah: *{$kode_verifikasi}*\n\n"
. "Kode ini berlaku selama 10 menit.\n"
. "Jangan berikan kode ini kepada siapapun.\n\n"
. "Terima kasih.";
WhatsAppHelper::sendWa($pengguna->no_hp, $wa_pesan);
}
$responseData = [
'status' => 200,
'pesan' => 'Kode Berhasil'
];
return ResponseHelper::custom($response, $responseData, 200);
}
public function cekKode(Request $request, Response $response): Response
{
$data = $request->getParsedBody();
$kode = $data['kode'] ?? '';
// Format response awal sama dengan API lama
$responseData = [
'status' => 404,
'pesan' => 'Kami sedang melakukan peningkatan sistem, silahkan coba beberapa saat lagi'
];
if (empty($kode)) {
$responseData['pesan'] = 'Kode harus diisi';
return ResponseHelper::custom($response, $responseData, 404);
}
// Cek kode verifikasi
$verifikasi = $this->db->fetchOne(
"SELECT * FROM kode_verifikasi_timo
WHERE kode = :kode
AND waktu_exp >= :waktu_exp
AND status_reset = 'DIBUAT'
LIMIT 1",
[
'kode' => $kode,
'waktu_exp' => date('Y-m-d H:i:s')
]
);
if (!$verifikasi) {
$responseData['pesan'] = 'Kode verifikasi Kadaluarsa, silahkan coba lagi dengan mengekan tombol Lupa Password. Terima Kasih';
return ResponseHelper::custom($response, $responseData, 404);
}
$responseData = [
'status' => 200,
'pesan' => 'Kode Ada'
];
return ResponseHelper::custom($response, $responseData, 200);
}
public function resetKode(Request $request, Response $response): Response
{
$data = $request->getParsedBody();
$kode = $data['kode'] ?? '';
$password = $data['password'] ?? '';
// Format response awal sama dengan API lama
$responseData = [
'status' => 404,
'pesan' => 'Kami sedang melakukan peningkatan sistem, silahkan coba beberapa saat lagi'
];
if (empty($kode) || empty($password)) {
$responseData['pesan'] = 'Kode dan password harus diisi';
return ResponseHelper::custom($response, $responseData, 404);
}
// Cek kode verifikasi
$verifikasi = $this->db->fetchOne(
"SELECT * FROM kode_verifikasi_timo
WHERE kode = :kode
AND waktu_exp >= :waktu_exp
AND status_reset = 'DIBUAT'
LIMIT 1",
[
'kode' => $kode,
'waktu_exp' => date('Y-m-d H:i:s')
]
);
if (!$verifikasi) {
$responseData['pesan'] = 'Kode verifikasi Kadaluarsa, silahkan coba lagi dengan mengekan tombol Lupa Password. Terima Kasih';
return ResponseHelper::custom($response, $responseData, 404);
}
// Update password user
$this->db->update('pengguna_timo',
['password' => md5($password)],
'email = :email',
['email' => $verifikasi->email]
);
// Update status kode verifikasi
$this->db->update('kode_verifikasi_timo',
['status_reset' => 'DIRESET'],
'id_kode_verifikasi_timo = :id',
['id' => $verifikasi->id_kode_verifikasi_timo]
);
$responseData = [
'status' => 200,
'pesan' => 'Berhasil Ada'
];
return ResponseHelper::custom($response, $responseData, 200);
}
}

View File

@@ -0,0 +1,207 @@
<?php
namespace App\Controllers;
use App\Helpers\HttpHelper;
use App\Helpers\ResponseHelper;
use App\Models\SLModel;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class SLController
{
private $slModel;
public function __construct()
{
$this->slModel = new SLModel();
}
public function cekSL(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' => '-'
];
if (empty($token) || empty($no_sl)) {
$responseData['pesan'] = 'Token dan nomor SL harus diisi';
return ResponseHelper::custom($response, $responseData, 404);
}
// Cek apakah SL sudah terdaftar oleh user lain
$sudahAda = $this->slModel->findByNoSL($no_sl);
if ($sudahAda) {
$responseData = [
'status' => 300,
'pesan' => "NO SL \"{$no_sl}\" sudah didaftarkan oleh AKUN Lain"
];
return ResponseHelper::custom($response, $responseData, 300);
}
// Cek apakah SL sudah terdaftar di akun user ini
$cek = $this->slModel->findByTokenAndSL($token, $no_sl);
if ($cek) {
$responseData = [
'status' => 300,
'pesan' => "NO SL \"{$no_sl}\" sudah terdaftar di akun anda. Silahkan cek di daftar SL"
];
return ResponseHelper::custom($response, $responseData, 300);
}
// Cek ke API TIMO
$respon = HttpHelper::doCurl('https://timo.tirtaintan.co.id/enquiry-dil/' . $no_sl);
// Handle response yang bisa berupa array atau object
if (!$respon) {
$responseData['pesan'] = 'Data pelanggan tidak ditemukan';
return ResponseHelper::custom($response, $responseData, 404);
}
// Convert to array untuk konsistensi
if (is_object($respon)) {
$respon = (array)$respon;
}
// Check for error
if (isset($respon['errno']) && $respon['errno'] != 0) {
$responseData['pesan'] = $respon['error'] ?? 'Data pelanggan tidak ditemukan';
return ResponseHelper::custom($response, $responseData, 404);
}
// Check for HTTP error status
if (isset($respon['status']) && $respon['status'] != 200) {
$responseData['pesan'] = $respon['error'] ?? 'Data pelanggan tidak ditemukan';
return ResponseHelper::custom($response, $responseData, 404);
}
// Get data - bisa dari 'data' key atau langsung dari response
$data = $respon['data'] ?? $respon;
// Format response sukses sama dengan API lama: status, pesan, data langsung di root
$responseData = [
'status' => 200,
'data' => $data
];
return ResponseHelper::custom($response, $responseData, 200);
}
public function confirmSL(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' => '-'
];
if (empty($token) || empty($no_sl)) {
$responseData['pesan'] = 'Token dan nomor SL harus diisi';
return ResponseHelper::custom($response, $responseData, 404);
}
// Cek apakah sudah terdaftar
$cek = $this->slModel->findByTokenAndSL($token, $no_sl);
if ($cek) {
$responseData = [
'status' => 300,
'pesan' => "NO SL \"{$no_sl}\" sudah terdaftar di akun anda. Silahkan cek di daftar SL"
];
return ResponseHelper::custom($response, $responseData, 300);
}
// Cek ke API TIMO
$respon = HttpHelper::doCurl('https://timo.tirtaintan.co.id/enquiry-dil/' . $no_sl);
// Handle response yang bisa berupa array atau object
if (!$respon) {
$responseData['pesan'] = 'Data pelanggan tidak ditemukan';
return ResponseHelper::custom($response, $responseData, 404);
}
// Convert to array untuk konsistensi
if (is_object($respon)) {
$respon = (array)$respon;
}
// Check for error
if (isset($respon['errno']) && $respon['errno'] != 0) {
$responseData['pesan'] = $respon['error'] ?? 'Data pelanggan tidak ditemukan';
return ResponseHelper::custom($response, $responseData, 404);
}
// Check for HTTP error status
if (isset($respon['status']) && $respon['status'] != 200) {
$responseData['pesan'] = $respon['error'] ?? 'Data pelanggan tidak ditemukan';
return ResponseHelper::custom($response, $responseData, 404);
}
// Get data - bisa dari 'data' key atau langsung dari response
$data = $respon['data'] ?? $respon;
// Extract data untuk simpan ke database
$pelNama = is_array($data) ? ($data['pel_nama'] ?? '') : ($data->pel_nama ?? '');
$pelAlamat = is_array($data) ? ($data['pel_alamat'] ?? '') : ($data->pel_alamat ?? '');
$dkdKd = is_array($data) ? ($data['dkd_kd'] ?? '') : ($data->dkd_kd ?? '');
$rekGol = is_array($data) ? ($data['rek_gol'] ?? '') : ($data->rek_gol ?? '');
// Simpan ke database
$this->slModel->create([
'token' => $token,
'no_sl' => $no_sl,
'nama' => $pelNama,
'alamat' => $pelAlamat,
'cabang' => $dkdKd,
'golongan' => $rekGol,
]);
// Format response sukses sama dengan API lama: status, data langsung di root
$responseData = [
'status' => 200,
'data' => $data
];
return ResponseHelper::custom($response, $responseData, 200);
}
public function hapusSL(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' => 'Invalid Operation!'
];
if (empty($token) || empty($no_sl)) {
return ResponseHelper::custom($response, $responseData, 404);
}
$cek = $this->slModel->findByTokenAndSL($token, $no_sl);
if (!$cek) {
return ResponseHelper::custom($response, $responseData, 404);
}
$this->slModel->delete($token, $no_sl);
// Format response sukses sama dengan API lama
$responseData = [
'status' => 200,
'pesan' => "NO SL \"{$no_sl}\" berhasil dihapus dari di akun anda."
];
return ResponseHelper::custom($response, $responseData, 200);
}
}

View File

@@ -0,0 +1,370 @@
<?php
namespace App\Controllers;
use App\Config\Database;
use App\Helpers\HttpHelper;
use App\Helpers\ResponseHelper;
use App\Helpers\WhatsAppHelper;
use App\Models\UserModel;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class SiteController
{
private $db;
private $userModel;
public function __construct()
{
$this->db = Database::getInstance();
$this->userModel = new UserModel();
}
/**
* POST /site/verify_bri
* Verifikasi pembayaran BRI
*/
public function verifyBri(Request $request, Response $response): Response
{
try {
// Get BRI token (from config or env)
$briToken = $this->getBriToken();
if (!$briToken) {
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'BRI token tidak tersedia'
], 500);
}
// Get pembayaran yang menunggu verifikasi BRI
$konf = $this->db->fetchOne(
"SELECT p.*, pt.nama_lengkap
FROM pembayaran p
LEFT JOIN pengguna_timo pt ON p.token = pt.id_pengguna_timo
WHERE p.status_bayar = 'MENUNGGU VERIFIKASI'
AND p.nama_bank = 'Bank BRI'
AND p.banyak_cek < 2
ORDER BY p.tanggal_cek_bayar ASC
LIMIT 1"
);
$pesan = "CEK PEMBAYARAN: <br>";
if ($konf) {
$pesan .= "Mengecek:" . $konf->no_trx . ": ";
// Get mutasi from BRI
$dataMutasi = $this->getMutasi($briToken);
if (isset($dataMutasi->data)) {
foreach ($dataMutasi->data as $d) {
$update = [
'tanggal_cek_bayar' => date('Y-m-d H:i:s'),
'banyak_cek' => ($konf->banyak_cek ?? 0) + 1
];
$totalBayar = $konf->jumlah_tagihan + $konf->biaya_admin + $konf->jumlah_unik - $konf->promo;
if ($totalBayar == ($d->creditAmount ?? 0)) {
$update['status_bayar'] = 'DIBAYAR';
$update['jumlah_bayar'] = $d->creditAmount ?? $totalBayar;
$pesan .= " Sudah Dibayar, ";
// Approve pembayaran
$this->approve($konf->id_pembayaran);
} else {
$pesan .= " Belum Dibayar, ";
}
$this->db->update('pembayaran', $update, 'id_pembayaran = :id', [
'id' => $konf->id_pembayaran
]);
}
}
} else {
$pesan .= " Tidak ada pembayaran BRI yang bisa di proses";
}
// Return HTML response (sesuai API lama)
$response->getBody()->write($pesan);
return $response->withHeader('Content-Type', 'text/html');
} catch (\Exception $e) {
error_log("Error in verifyBri: " . $e->getMessage());
$response->getBody()->write("Error: " . $e->getMessage());
return $response->withStatus(500)->withHeader('Content-Type', 'text/html');
}
}
/**
* POST /site/approve/{id_trx}
* Approve transaksi
*/
public function approve(Request $request, Response $response, array $args): Response
{
try {
$idTrx = $args['id_trx'] ?? 0;
if (empty($idTrx)) {
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'ID transaksi tidak valid'
], 400);
}
// Get pembayaran
$cekPembayaran = $this->db->fetchOne(
"SELECT * FROM pembayaran WHERE id_pembayaran = :id AND status_bayar = 'MENUNGGU VERIFIKASI' LIMIT 1",
['id' => $idTrx]
);
if (!$cekPembayaran) {
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'Pembayaran tidak ditemukan atau sudah diproses'
], 404);
}
$token = str_replace('#TIMO', '', $cekPembayaran->no_trx);
$url = "https://timo.tirtaintan.co.id/payment/$token";
// Prepare payment data
$data = [];
$rincian = json_decode($cekPembayaran->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 sesuai API lama (Site.php approve)
$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'
];
// Send payment to PDAM
$paymentResponse = HttpHelper::doCurl($url, 'POST', $post, true, $headers);
// Update database
$this->db->update('pembayaran', [
'raw_bayar' => json_encode($paymentResponse)
], 'id_pembayaran = :id', ['id' => $idTrx]);
if ($paymentResponse && isset($paymentResponse->errno) && $paymentResponse->errno == 0) {
$totalBayar = $cekPembayaran->jumlah_tagihan + $cekPembayaran->biaya_admin +
$cekPembayaran->jumlah_unik - $cekPembayaran->promo;
$this->db->update('pembayaran', [
'status_bayar' => 'DIBAYAR',
'tanggal_bayar' => date('Y-m-d H:i:s'),
'jumlah_bayar' => (string)$totalBayar
], 'id_pembayaran = :id', ['id' => $idTrx]);
// Kirim notifikasi WhatsApp ke user (sesuai backend lama)
$this->sendPaymentNotification($cekPembayaran->token, 'DIBAYAR', [
'no_trx' => $cekPembayaran->no_trx,
'no_sl' => $cekPembayaran->no_sl,
'jumlah_bayar' => $totalBayar,
'tanggal_bayar' => date('Y-m-d H:i:s'),
'id_pembayaran' => $idTrx
]);
return ResponseHelper::json($response, [
'status' => 'success',
'message' => 'Pembayaran berhasil diapprove',
'data' => [
'id_pembayaran' => $idTrx,
'status' => 'DIBAYAR'
]
], 200);
} else {
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'Gagal approve pembayaran ke PDAM'
], 500);
}
} catch (\Exception $e) {
error_log("Error in approve: " . $e->getMessage());
return ResponseHelper::json($response, [
'status' => 'error',
'message' => 'Gagal approve: ' . $e->getMessage()
], 500);
}
}
/**
* Get BRI token
*/
private function getBriToken()
{
$briKey = $_ENV['BRI_KEY'] ?? '';
$briSecret = $_ENV['BRI_SECRET'] ?? '';
$briUrlToken = $_ENV['BRI_URL_TOKEN'] ?? '';
if (empty($briKey) || empty($briSecret) || empty($briUrlToken)) {
return null;
}
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $briUrlToken,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0, // No timeout (sesuai API lama)
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => "client_id={$briKey}&client_secret={$briSecret}",
CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded'],
]);
$response = curl_exec($ch);
curl_close($ch);
if ($response) {
$json = json_decode($response);
return $json->access_token ?? null;
}
return null;
}
/**
* Get mutasi from BRI API
*/
private function getMutasi($token = '')
{
if (empty($token)) {
return (object)['data' => []];
}
$briRekening = $_ENV['BRI_REKENING'] ?? '';
$briSecret = $_ENV['BRI_SECRET'] ?? '';
$briUrlMutasi = $_ENV['BRI_URL_MUTASI'] ?? '';
if (empty($briRekening) || empty($briSecret) || empty($briUrlMutasi)) {
return (object)['data' => []];
}
// Body sesuai API lama: string JSON langsung (bukan dari array)
// Format: {"accountNumber":"...", "startDate":"...", "endDate":"..."}
$body = '{"accountNumber":"' . $briRekening . '", "startDate":"' . date('Y-m-d') . '", "endDate":"' . date('Y-m-d') . '"}';
$verb = 'POST';
$path = '/v2.0/statement';
$timestamp = gmdate('Y-m-d\TH:i:s.000\Z');
$sig = $this->generateSignature($path, $verb, $token, $timestamp, $body, $briSecret);
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $briUrlMutasi,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0, // No timeout (sesuai API lama)
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => [
'BRI-Timestamp: ' . $timestamp,
'BRI-Signature: ' . $sig,
'Content-Type: application/json',
'BRI-External-Id: 1234',
"Authorization: Bearer $token",
],
]);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response) ?: (object)['data' => []];
}
/**
* Generate BRI signature
*/
private function generateSignature($path, $verb, $token, $timestamp, $body, $secret)
{
$payload = "path=$path&verb=$verb&token=Bearer $token&timestamp=$timestamp&body=$body";
$signPayload = hash_hmac('sha256', $payload, $secret, true);
return base64_encode($signPayload);
}
/**
* Send payment notification to user via WhatsApp
* Sesuai dengan backend lama (Site.php sendPaymentNotification)
*/
private function sendPaymentNotification($token = null, $status = null, $data = null)
{
if (empty($token) || empty($status) || empty($data)) {
error_log('SiteController::sendPaymentNotification - Invalid parameters');
return false;
}
$pengguna = $this->userModel->findById($token);
if (!$pengguna || empty($pengguna->no_hp)) {
error_log('SiteController::sendPaymentNotification - User not found or no phone: ' . $token);
return false;
}
$noHp = $pengguna->no_hp;
switch ($status) {
case 'DIBAYAR':
$pesan = "*PEMBAYARAN BERHASIL*\n\n"
. "Pembayaran tagihan Anda telah berhasil diverifikasi.\n\n"
. "*Detail Pembayaran:*\n"
. "No TRX: `" . ($data['no_trx'] ?? '') . "`\n"
. "No SL: `" . ($data['no_sl'] ?? '') . "`\n"
. "Total: Rp " . number_format($data['jumlah_bayar'] ?? 0, 0, ',', '.') . "\n"
. "Tanggal: " . date('d/m/Y H:i', strtotime($data['tanggal_bayar'] ?? 'now')) . "\n\n"
. "Terima kasih telah menggunakan layanan TIMO!";
// Tambahkan link cetak invoice jika ID pembayaran tersedia
if (isset($data['id_pembayaran']) && $data['id_pembayaran']) {
$link_invoice = "https://timo.wipay.id/invoice/download/" . $data['id_pembayaran'];
$pesan .= "\n\n📄 *Struk Pembayaran:*\n"
. "Silakan klik link berikut untuk mengunduh struk pembayaran:\n"
. "$link_invoice";
}
break;
default:
error_log('SiteController::sendPaymentNotification - Invalid status: ' . $status);
return false;
}
error_log('SiteController::sendPaymentNotification - Sending to: ' . $noHp . ' for TRX: ' . ($data['no_trx'] ?? ''));
$result = WhatsAppHelper::sendWa($noHp, $pesan);
if ($result) {
// Update database untuk menandai notifikasi sudah dikirim
if (isset($data['id_pembayaran']) && $data['id_pembayaran']) {
$this->db->update('pembayaran', [
'respon_wa' => 'NOTIFICATION_SENT_' . date('Y-m-d H:i:s')
], 'id_pembayaran = :id', ['id' => $data['id_pembayaran']]);
}
return true;
}
return false;
}
}

View File

@@ -0,0 +1,116 @@
<?php
namespace App\Controllers;
use App\Helpers\HttpHelper;
use App\Helpers\ResponseHelper;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class TagihanController
{
public function history(Request $request, Response $response, array $args): Response
{
$sl = $args['sl'] ?? '';
$periode = $args['periode'] ?? '';
// Format response awal sama dengan API lama
$responseData = [
'status' => 404,
'pesan' => '-'
];
if (empty($sl) || empty($periode)) {
$responseData['pesan'] = 'SL dan periode harus diisi';
return ResponseHelper::custom($response, $responseData, 404);
}
$respon = HttpHelper::doCurl('https://timo.tirtaintan.co.id/enquiry-his/' . $sl . '/' . $periode);
// Handle response yang bisa berupa array atau object
if (!$respon) {
$responseData['pesan'] = 'Data tidak ditemukan';
return ResponseHelper::custom($response, $responseData, 404);
}
// Convert to array untuk konsistensi
if (is_object($respon)) {
$respon = (array)$respon;
}
// Check for error
if (isset($respon['errno']) && $respon['errno'] != 0) {
$responseData['pesan'] = $respon['error'] ?? 'Data tidak ditemukan';
return ResponseHelper::custom($response, $responseData, 404);
}
// Check for HTTP error status
if (isset($respon['status']) && $respon['status'] != 200) {
$responseData['pesan'] = $respon['error'] ?? 'Data tidak ditemukan';
return ResponseHelper::custom($response, $responseData, 404);
}
// Get data - bisa dari 'data' key atau langsung dari response
$data = $respon['data'] ?? $respon;
// Format response sukses sama dengan API lama: status, data langsung di root
$responseData = [
'status' => 200,
'data' => $data
];
return ResponseHelper::custom($response, $responseData, 200);
}
public function tagihan(Request $request, Response $response, array $args): Response
{
$sl = $args['sl'] ?? '';
// Format response awal sama dengan API lama
$responseData = [
'status' => 404,
'pesan' => '-'
];
if (empty($sl)) {
$responseData['pesan'] = 'SL harus diisi';
return ResponseHelper::custom($response, $responseData, 404);
}
$respon = HttpHelper::doCurl('https://timo.tirtaintan.co.id/enquiry/' . $sl);
// Handle response yang bisa berupa array atau object
if (!$respon) {
$responseData['pesan'] = 'Data tidak ditemukan';
return ResponseHelper::custom($response, $responseData, 404);
}
// Convert to array untuk konsistensi
if (is_object($respon)) {
$respon = (array)$respon;
}
// Check for error
if (isset($respon['errno']) && $respon['errno'] != 0) {
$responseData['pesan'] = $respon['error'] ?? 'Data tidak ditemukan';
return ResponseHelper::custom($response, $responseData, 404);
}
// Check for HTTP error status
if (isset($respon['status']) && $respon['status'] != 200) {
$responseData['pesan'] = $respon['error'] ?? 'Data tidak ditemukan';
return ResponseHelper::custom($response, $responseData, 404);
}
// Get data - bisa dari 'data' key atau langsung dari response
$data = $respon['data'] ?? $respon;
// Format response sukses sama dengan API lama: status, data langsung di root
$responseData = [
'status' => 200,
'data' => $data
];
return ResponseHelper::custom($response, $responseData, 200);
}
}

View File

@@ -0,0 +1,684 @@
<?php
namespace App\Controllers;
use App\Config\Database;
use App\Helpers\FileHelper;
use App\Helpers\GeocodingHelper;
use App\Helpers\HttpHelper;
use App\Helpers\ResponseHelper;
use App\Helpers\TelegramHelper;
use App\Models\PembayaranModel;
use App\Models\SLModel;
use App\Models\UserModel;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class UploadController
{
private $db;
private $userModel;
private $slModel;
private $pembayaranModel;
public function __construct()
{
$this->db = Database::getInstance();
$this->userModel = new UserModel();
$this->slModel = new SLModel();
$this->pembayaranModel = new PembayaranModel();
}
public function uploadCatatMeter(Request $request, Response $response): Response
{
$data = $request->getParsedBody();
$token = $data['token'] ?? '';
$no_sl = $data['no_sl'] ?? '';
$nama_photo = $data['nama_photo'] ?? '';
$img = $data['photo'] ?? '';
$angka = $data['angka'] ?? '';
// Format response awal sama dengan API lama
$responseData = [
'status' => 404,
'pesan' => 'Gagal upload catat meter, silahkan coba beberapa saat lagi'
];
// Validasi parameter wajib
if (!$token || !$no_sl || !$nama_photo || !$img || !$angka) {
$responseData['pesan'] = 'Parameter tidak lengkap. Token, no_sl, nama_photo, photo, dan angka wajib 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);
}
// Validasi apakah user sudah pernah melakukan catat meter sebelumnya
$previous_catat = $this->db->fetchOne(
"SELECT * FROM catat_meter WHERE token = :token LIMIT 1",
['token' => $token]
);
if (!$previous_catat) {
// User baru - cek apakah no_sl sudah pernah digunakan oleh user lain
$existing_sl = $this->db->fetchOne(
"SELECT * FROM catat_meter WHERE no_sl = :no_sl LIMIT 1",
['no_sl' => $no_sl]
);
if ($existing_sl) {
$responseData['pesan'] = "Nomor SL {$no_sl} sudah digunakan oleh user lain. Silahkan gunakan nomor SL yang berbeda atau hubungi admin.";
return ResponseHelper::custom($response, $responseData, 404);
}
} else {
// User lama - validasi apakah nomor SL yang dikirim sesuai dengan nomor SL yang pernah digunakan user
if ($previous_catat->no_sl !== $no_sl) {
$responseData['pesan'] = "Nomor SL tidak sesuai dengan data Anda. Nomor SL Anda: {$previous_catat->no_sl}";
return ResponseHelper::custom($response, $responseData, 404);
}
}
// Upload file
$filename = FileHelper::generateFilename($nama_photo);
$uploadPath = __DIR__ . '/../../public/assets/uploads/catat_meter';
if (!FileHelper::saveBase64Image($img, $uploadPath, $filename)) {
$responseData['pesan'] = 'Photo Catat Meter GAGAL upload';
return ResponseHelper::custom($response, $responseData, 404);
}
// Simpan ke database
$this->db->insert('catat_meter', [
'token' => $token,
'no_sl' => $no_sl,
'photo' => $filename,
'angka_meter' => $angka,
'tanggal_catat' => date('Y-m-d H:i:s')
]);
// Kirim ke external API upload-catat-meter
$this->sendCatatMeterToExternalAPI($token, $no_sl, $pengguna, $angka, $filename);
$responseData = [
'status' => 200,
'pesan' => 'Catat meter mandiri berhasil di upload'
];
return ResponseHelper::custom($response, $responseData, 200);
}
/**
* Send catat meter to external API
*/
private function sendCatatMeterToExternalAPI($token, $no_sl, $pengguna, $angka, $filename)
{
try {
// Payload sesuai API lama: photo adalah filename, bukan URL
$payload = [
'token' => $token,
'no_sl' => $no_sl,
'nama_pelanggan' => $pengguna->nama_lengkap ?? '',
'alamat' => ($pengguna->alamat ?? '') ?: 'Tidak diisi',
'angka_meter' => $angka,
'photo' => $filename,
'uploaded_at' => date('Y-m-d H:i:s')
];
$url = 'https://rasamala.tirtaintan.co.id/timo/upload-catat-meter/' . $no_sl;
// Headers sesuai API lama: sendExternalAPIRequest
$headers = [
'Content-Type: application/json',
'Accept: application/json',
'User-Agent: TIMO-External-API/1.0'
];
$response = HttpHelper::doCurl($url, 'POST', $payload, true, $headers, 60, 30);
if ($response && isset($response->status) && $response->status == 'success') {
// Update database
$this->db->update('catat_meter', [
'external_api_sent' => 1,
'external_api_response' => json_encode($response),
'external_api_sent_at' => date('Y-m-d H:i:s')
], 'token = :token AND no_sl = :no_sl AND angka_meter = :angka', [
'token' => $token,
'no_sl' => $no_sl,
'angka' => $angka
]);
} else {
// Update database untuk menandai gagal
$this->db->update('catat_meter', [
'external_api_sent' => 0,
'external_api_response' => json_encode($response),
'external_api_sent_at' => date('Y-m-d H:i:s')
], 'token = :token AND no_sl = :no_sl AND angka_meter = :angka', [
'token' => $token,
'no_sl' => $no_sl,
'angka' => $angka
]);
}
} catch (\Exception $e) {
error_log('External API Catat Meter - Exception: ' . $e->getMessage());
}
}
public function uploadPp(Request $request, Response $response): Response
{
$data = $request->getParsedBody();
$token = $data['token'] ?? '';
$nama_photo = $data['nama_photo'] ?? '';
$img = $data['photo'] ?? '';
// Format response awal sama dengan API lama
$responseData = [
'status' => 404,
'pesan' => 'Gagal upload catat meter, silahkan coba beberapa saat lagi'
];
$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);
}
// Upload file
$filename = FileHelper::generateFilename($nama_photo);
$uploadPath = __DIR__ . '/../../public/assets/uploads/pengguna';
if (!FileHelper::saveBase64Image($img, $uploadPath, $filename)) {
$responseData['pesan'] = 'Photo profil GAGAL upload';
return ResponseHelper::custom($response, $responseData, 404);
}
// Update database
$this->userModel->update($token, ['photo' => $filename]);
// Hapus foto lama jika ada
if ($pengguna->photo != '') {
$oldFile = $uploadPath . '/' . $pengguna->photo;
FileHelper::deleteFile($oldFile);
}
// Get updated user data
$updatedUser = $this->userModel->findById($token);
$responseData = [
'status' => 200,
'pesan' => 'Photo profil berhasil di upload',
'data' => $updatedUser
];
return ResponseHelper::custom($response, $responseData, 200);
}
public function hapusPp(Request $request, Response $response): Response
{
$data = $request->getParsedBody();
$token = $data['token'] ?? '';
// Format response awal sama dengan API lama
$responseData = [
'status' => 404,
'pesan' => 'Gagal upload catat meter, silahkan coba beberapa saat lagi'
];
$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);
}
// Hapus foto jika ada
if ($pengguna->photo != '') {
$oldFile = __DIR__ . '/../../public/assets/uploads/pengguna/' . $pengguna->photo;
FileHelper::deleteFile($oldFile);
}
// Update database
$this->userModel->update($token, ['photo' => '']);
// Get updated user data
$updatedUser = $this->userModel->findById($token);
$responseData = [
'status' => 200,
'pesan' => 'Photo profil berhasil di dihapus',
'data' => $updatedUser
];
return ResponseHelper::custom($response, $responseData, 200);
}
public function uploadGangguan(Request $request, Response $response): Response
{
$data = $request->getParsedBody();
$token = $data['token'] ?? '';
$gangguan = $data['gangguan'] ?? '';
$no_sl = $data['no_sl'] ?? '';
$nama_photo = $data['nama_photo'] ?? '';
$img = $data['photo'] ?? '';
$feedback = $data['feedback'] ?? '';
$lokasi = $data['lokasi'] ?? '';
// Format response awal sama dengan API lama
$responseData = [
'status' => 404,
'pesan' => 'Gagal kirim gangguan, silahkan coba beberapa saat lagi'
];
$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);
}
// Get jenis gangguan
$dt_gangguan = $this->db->fetchOne(
"SELECT * FROM jenis_gangguan WHERE id_jenis_gangguan = :id LIMIT 1",
['id' => $gangguan]
);
// Insert gangguan
$id_gangguan = $this->db->insert('gangguan', [
'token' => $token,
'no_sl' => $no_sl,
'jenis_gangguan' => $gangguan,
'waktu_laporan' => date('Y-m-d H:i:s'),
'photo_gangguan' => '',
'feedback' => $feedback,
'status' => 'DILAPORKAN',
'lokasi' => $lokasi,
'respon' => 'Laporan anda akan segera kami tindaklanjuti oleh bagian terkait. Terima kasih',
]);
// Upload foto jika harus ada foto
if ($dt_gangguan && $dt_gangguan->harus_ada_foto == 'YA' && !empty($img)) {
$filename = FileHelper::generateFilename($nama_photo);
$uploadPath = __DIR__ . '/../../public/assets/uploads/gangguan';
if (FileHelper::saveBase64Image($img, $uploadPath, $filename)) {
$this->db->update('gangguan', ['photo_gangguan' => $filename], 'id_gangguan = :id', ['id' => $id_gangguan]);
} else {
$responseData['pesan'] = 'Photo Gangguan GAGAL upload';
return ResponseHelper::custom($response, $responseData, 404);
}
}
// Kirim ke external API pengaduan
$this->sendGangguanToExternalAPI($id_gangguan, $pengguna, $dt_gangguan, $no_sl, $feedback, $lokasi);
// Kirim notifikasi Telegram ke admin gangguan
$pesan = "🚨 *LAPORAN GANGGUAN BARU*\n\n"
. "No. SL: " . $no_sl . "\n"
. "Jenis: " . ($dt_gangguan->nama_jenis_gangguan ?? '') . "\n"
. "Pelapor: " . ($pengguna->nama_lengkap ?? '') . "\n"
. "Lokasi: " . ($lokasi ?? '-') . "\n"
. "Feedback: " . substr($feedback ?? '', 0, 100) . "\n\n"
. "*Harap segera ditindaklanjuti*\n\n"
. "Hubungi pelapor untuk informasi lebih lanjut";
TelegramHelper::sendToGangguanAdmin($pesan);
$responseData = [
'status' => 200,
'pesan' => 'Laporan anda telah kami terima dan sudah diteruskan kebagian terkait'
];
return ResponseHelper::custom($response, $responseData, 200);
}
/**
* Send gangguan to external API
*/
private function sendGangguanToExternalAPI($id_gangguan, $pengguna, $dt_gangguan, $no_sl, $feedback, $lokasi)
{
try {
// Mapping jenis gangguan
$jenisMapping = $this->getJenisGangguanMapping($dt_gangguan->nama_jenis_gangguan ?? '');
// Format nomor telepon sesuai API lama (formatPhoneNumber)
$telepon = preg_replace('/[^0-9]/', '', $pengguna->no_hp ?? '');
if (substr($telepon, 0, 2) === '08') {
$telepon = '62' . substr($telepon, 1); // '08...' -> '628...'
} elseif (substr($telepon, 0, 1) === '8') {
$telepon = '62' . $telepon; // '8...' -> '628...'
} elseif (substr($telepon, 0, 2) === '62') {
$telepon = $telepon; // Sudah format 62
} elseif (substr($telepon, 0, 1) === '0') {
$telepon = '62' . substr($telepon, 1); // '0...' -> '62...'
}
// Prepare payload
$payload = [
'id' => $id_gangguan,
'nama' => $pengguna->nama_lengkap ?? '',
'alamat' => $pengguna->alamat ?? 'Tidak diisi',
'telepon' => $telepon,
'jenis' => $jenisMapping,
'judul' => 'Laporan Gangguan - ' . ($dt_gangguan->nama_jenis_gangguan ?? ''),
'uraian' => $feedback
];
// Send to external API (sesuai API lama: sendExternalAPIRequest)
$url = 'https://timo.tirtaintan.co.id/pengaduan/' . $no_sl;
$headers = [
'Content-Type: application/json',
'Accept: application/json',
'User-Agent: TIMO-External-API/1.0'
];
$response = HttpHelper::doCurl($url, 'POST', $payload, true, $headers, 60, 30);
// Check response
$isSuccess = false;
$token = null;
if ($response) {
if (isset($response->errno) && $response->errno == '0') {
$isSuccess = true;
$token = $response->token ?? null;
} elseif (isset($response->success) && $response->success === true) {
$isSuccess = true;
$token = $response->token ?? null;
} elseif (isset($response->status) && $response->status == 'success') {
$isSuccess = true;
$token = $response->token ?? null;
} elseif (isset($response->token) && !empty($response->token)) {
$isSuccess = true;
$token = $response->token;
}
}
// Update database
$updateData = [
'external_api_sent' => $isSuccess ? 1 : 2,
'external_api_token' => $token,
'external_api_response' => json_encode($response),
'external_api_sent_at' => date('Y-m-d H:i:s'),
'external_api_error' => $isSuccess ? null : (json_encode($response) ?? 'Unknown error')
];
if ($isSuccess) {
$updateData['status'] = 'DIPROSES';
}
$this->db->update('gangguan', $updateData, 'id_gangguan = :id', ['id' => $id_gangguan]);
} catch (\Exception $e) {
error_log('External API Gangguan - Exception: ' . $e->getMessage());
$this->db->update('gangguan', [
'external_api_sent' => 2,
'external_api_error' => 'Exception: ' . $e->getMessage(),
'external_api_sent_at' => date('Y-m-d H:i:s')
], 'id_gangguan = :id', ['id' => $id_gangguan]);
}
}
/**
* Get jenis gangguan mapping
*/
private function getJenisGangguanMapping($namaJenis)
{
$mapping = [
'Tidak Ada Air' => '1',
'Pengaliran' => '2',
'Tagihan' => '3',
'Tarif' => '4',
'Pelayanan' => '5',
'Kebocoran' => '6',
'Meteran' => '7'
];
return $mapping[$namaJenis] ?? '1';
}
public function uploadPasangBaru(Request $request, Response $response): Response
{
try {
$data = $request->getParsedBody();
$token = $data['token'] ?? '';
$no_sl = $data['no_sl'] ?? '';
$nama_photo = $data['nama_photo'] ?? '';
$img = $data['photo'] ?? '';
$nama = $data['nama'] ?? '';
$email = $data['email'] ?? '';
$telepon = $data['telepon'] ?? '';
$nik = $data['nik'] ?? '';
$alamat = $data['alamat'] ?? '';
// Format response awal sama dengan API lama
$responseData = [
'status' => 404,
'pesan' => 'Gagal upload catat meter, silahkan coba beberapa saat lagi'
];
$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);
}
// Upload file
$filename = FileHelper::generateFilename($nama_photo);
$uploadPath = __DIR__ . '/../../public/assets/uploads/pasang_baru';
if (!FileHelper::saveBase64Image($img, $uploadPath, $filename)) {
$responseData['pesan'] = 'Photo Pasang Baru GAGAL upload';
return ResponseHelper::custom($response, $responseData, 404);
}
// Insert ke database
$id = $this->db->insert('pasang_baru', [
'token' => $token,
'photo' => $filename,
'nama_lengkap' => $nama,
'email' => $email,
'telepon' => $telepon,
'nik' => $nik,
'alamat' => $alamat,
]);
// Kirim ke API eksternal (sesuai API lama: pasangBaruCURl)
$detail = [
'reg_id' => '0',
'reg_unit' => '00',
'reg_name' => $nama,
'reg_address' => $alamat,
'reg_phone' => $telepon,
'reg_email' => $email,
'reg_identity' => $nik,
'reg_tgl' => date('Y-m-d H:i:s'),
];
// Payload sesuai API lama: { data: {...} }
$post = ['data' => $detail];
// Headers sesuai API lama
$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'
];
// Kirim request dengan timeout 15 detik (sesuai API lama)
$respon = HttpHelper::doCurl('https://timo.tirtaintan.co.id/push-registrasi', 'POST', $post, true, $headers, 15, 15);
// API lama mengembalikan string, perlu decode
$responString = is_string($respon) ? $respon : (is_object($respon) && isset($respon->body) ? $respon->body : json_encode($respon));
$hasil = json_decode($responString);
$no_reg = '0';
if ($responString && is_object($hasil) && isset($hasil->errno)) {
if ($hasil->errno == '0' || $hasil->errno == 0) {
$no_reg = isset($hasil->reg_id) ? $hasil->reg_id : '0';
}
}
// Update database (sesuai API lama: simpan string response)
$this->db->update('pasang_baru', [
'no_reg' => $no_reg,
'respon' => $responString
], 'id_pasang_baru = :id', ['id' => $id]);
// Insert ke daftar_sl
if (!empty($no_reg) && $no_reg != '0') {
$this->slModel->create([
'token' => $token,
'no_sl' => $no_reg,
'nama' => $nama,
'alamat' => $alamat,
'cabang' => '-',
'golongan' => '-',
]);
}
$responseData = [
'status' => 200,
'pesan' => 'Data Pasang Baru berhasil di upload'
];
return ResponseHelper::custom($response, $responseData, 200);
} catch (\Exception $e) {
error_log("Error in uploadPasangBaru: " . $e->getMessage());
return ResponseHelper::custom($response, [
'status' => 404,
'pesan' => 'Gagal upload pasang baru: ' . $e->getMessage()
], 404);
}
}
public function uploadBuktiTransfer(Request $request, Response $response): Response
{
$data = $request->getParsedBody();
$token = $data['token'] ?? '';
$no_sl = $data['no_sl'] ?? '';
$nama_photo = $data['nama_photo'] ?? '';
$img = $data['photo'] ?? '';
$pembayaran = $data['pembayaran'] ?? '';
// Format response awal sama dengan API lama
$responseData = [
'status' => 404,
'pesan' => 'Gagal upload catat meter, silahkan coba beberapa saat lagi'
];
$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);
}
// Upload file
$filename = FileHelper::generateFilename($nama_photo);
$uploadPath = __DIR__ . '/../../public/assets/uploads/bukti_transfer';
if (!FileHelper::saveBase64Image($img, $uploadPath, $filename)) {
$responseData['pesan'] = 'Bukti Transfer GAGAL upload';
return ResponseHelper::custom($response, $responseData, 404);
}
// Update pembayaran
$pembayaranData = $this->pembayaranModel->findByNoTrx($token, $pembayaran);
if ($pembayaranData) {
$this->pembayaranModel->update($pembayaranData->id_pembayaran, ['bukti_transfer' => $filename]);
}
$responseData = [
'status' => 200,
'pesan' => 'Bukti Transfer berhasil di upload'
];
return ResponseHelper::custom($response, $responseData, 200);
}
public function uploadBacaMandiri(Request $request, Response $response): Response
{
try {
$data = $request->getParsedBody();
$token = $data['token'] ?? '';
$wrute_id = $data['wrute_id'] ?? '';
$stand_baca = $data['stand_baca'] ?? '';
$abnorm_wm = $data['abnorm_wm'] ?? '';
$abnorm_env = $data['abnorm_env'] ?? '';
$note = $data['note'] ?? '';
$lonkor = $data['lonkor'] ?? '';
$latkor = $data['latkor'] ?? '';
// Format response awal sama dengan API lama
$responseData = [
'status' => 404,
'pesan' => 'Gagal upload baca mandiri, silahkan coba beberapa saat lagi'
];
$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);
}
// Get valid coordinates with priority: GPS > Geocoding > Default
$coordinates = GeocodingHelper::getValidCoordinates($lonkor, $latkor, $pengguna->alamat ?? '');
$lonkor = $coordinates['longitude'];
$latkor = $coordinates['latitude'];
// Kirim ke API eksternal (sesuai API lama: sendBacaMandiriRequest)
$url = 'https://rasamala.tirtaintan.co.id/timo/upload-cater/' . $wrute_id;
$post_data = [
'wmmr_id' => $wrute_id,
'wmmr_standbaca' => $stand_baca,
'wmmr_abnormwm' => $abnorm_wm,
'wmmr_abnormenv' => $abnorm_env,
'wmmr_note' => $note,
'lonkor' => $lonkor,
'latkor' => $latkor
];
// API lama menggunakan form-urlencoded, bukan JSON
$headers = [
'Content-Type: application/x-www-form-urlencoded',
'Accept: application/json'
];
$apiResponse = HttpHelper::doCurl($url, 'POST', $post_data, false, $headers, 30, 10);
if ($apiResponse) {
// Simpan ke database lokal
$this->db->insert('baca_mandiri_log', [
'token' => $token,
'wrute_id' => $wrute_id,
'stand_baca' => $stand_baca,
'abnorm_wm' => $abnorm_wm,
'abnorm_env' => $abnorm_env,
'note' => $note,
'lonkor' => $lonkor,
'latkor' => $latkor,
'response' => json_encode($apiResponse),
'created_at' => date('Y-m-d H:i:s')
]);
$responseData = [
'status' => 200,
'pesan' => 'Data baca mandiri berhasil diupload',
'data' => $apiResponse
];
} else {
$responseData['pesan'] = 'Gagal mengupload data baca mandiri';
}
return ResponseHelper::custom($response, $responseData, $responseData['status']);
} catch (\Exception $e) {
error_log("Error in uploadBacaMandiri: " . $e->getMessage());
return ResponseHelper::custom($response, [
'status' => 404,
'pesan' => 'Gagal upload baca mandiri: ' . $e->getMessage()
], 404);
}
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace App\Controllers;
use App\Config\Database;
use App\Helpers\KodeHelper;
use App\Helpers\ResponseHelper;
use App\Models\PembayaranModel;
use App\Models\UserModel;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class WipayController
{
private $db;
private $userModel;
private $pembayaranModel;
public function __construct()
{
$this->db = Database::getInstance();
$this->userModel = new UserModel();
$this->pembayaranModel = new PembayaranModel();
}
public function cekWipay(Request $request, Response $response): Response
{
$data = $request->getParsedBody();
$token = $data['token'] ?? '';
// Format response awal sama dengan API lama
$responseData = [
'status' => 404,
'wipay' => 0,
'pesan' => 'Gagal kirim gangguan, silahkan coba beberapa saat lagi'
];
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. Silahkan Login dan Ulangi transaksi. Terima kasih';
return ResponseHelper::custom($response, $responseData, 404);
}
// Cek saldo WIPAY - API lama menggunakan pengguna->wipay (bukan wipay_user)
$wipay = $this->db->fetchOne(
"SELECT * FROM wipay_pengguna WHERE id_wipay = :id_wipay",
['id_wipay' => $pengguna->wipay ?? 0]
);
if ($wipay) {
// Format response sama dengan API lama: status tetap 404, wipay = 1, data = wipay object
$responseData['wipay'] = 1;
$responseData['data'] = $wipay;
} else {
$responseData['pesan'] = 'Tidak ada akun wipay yang terkait';
}
return ResponseHelper::custom($response, $responseData, 404);
}
// Note: buat_kode, cek_kode, reset_kode sudah digunakan untuk reset password
// Kode unik pembayaran otomatis di-generate saat request_pembayaran
}