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); } // Convert to array untuk konsistensi (sama seperti TagihanController) if (is_object($timoResponse)) { $timoResponse = (array)$timoResponse; } // Check for HTTP error status if (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']); } // Get data - bisa dari 'data' key atau langsung dari response (sama seperti TagihanController) $data = $timoResponse['data'] ?? $timoResponse; // Format response untuk Fast API (sesuai format API lama) // Response TIMO API langsung berupa array data, perlu di-wrap dengan errno, error, recordsTotal, data if (is_array($data) && isset($data[0])) { // Data adalah array tagihan $responseData = [ 'errno' => 0, 'error' => '', 'recordsTotal' => count($data), 'data' => $data ]; } else { // Data mungkin sudah dalam format yang benar atau single object $responseData = [ 'errno' => isset($timoResponse['errno']) ? $timoResponse['errno'] : 0, 'error' => $timoResponse['error'] ?? '', 'recordsTotal' => isset($timoResponse['recordsTotal']) ? $timoResponse['recordsTotal'] : (is_array($data) ? count($data) : 1), 'data' => $data ]; } // Log API usage $this->apiKeyModel->logApiUsage($apiKey->id, 'check_bill', $responseData['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' => $responseData, 'message' => 'Tagihan berhasil dicek' ], 200); } 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; } }