is_token_empty($token_user) || !$this->is_valid_fcm_token($token_user)) { log_message('debug', 'notif_cancel_user: skip, no/invalid Firebase token for user'); return true; } // Use FCM HTTP v1 via service account, not legacy keyfcm. $payload = array( 'id_driver' => $id_driver, 'id_transaksi' => $id_transaksi, 'response' => '5', 'type' => 1, ); $this->send_generic_to_token($token_user, $payload, array()); return true; } public function notif_cancel_driver($id_transaksi, $token_driver) { if ($this->is_token_empty($token_driver) || !$this->is_valid_fcm_token($token_driver)) { log_message('debug', 'notif_cancel_driver: skip, no/invalid Firebase token for driver'); return true; } $payload = array( 'id_transaksi' => $id_transaksi, 'response' => '0', 'type' => 1, ); $this->send_generic_to_token($token_driver, $payload, array()); return true; } /** * Check if token/target is empty (null, empty string, or whitespace). * Use before sending FCM to avoid sending when device has no token. */ private function is_token_empty($token) { return $token === null || trim((string) $token) === ''; } /** * Check if string looks like a valid FCM v1 device token (not a placeholder). * Uses shared fcm_v1_is_valid_device_token helper. */ private function is_valid_fcm_token($token) { if (!function_exists('fcm_v1_is_valid_device_token')) { get_instance()->load->helper('fcm_v1_helper'); } return fcm_v1_is_valid_device_token($token); } /** * Send notification via FCM HTTP v1 to a device token (reg_id, token, token_merchant). * Uses token API so reg_id can be used as FCM v1 target. Skips invalid/placeholder tokens. * @param string $target FCM device token (from driver.reg_id, pelanggan.token, etc.) * @return bool True on success or skip, false on failure */ public function send_notif($title, $message, $target) { if ($this->is_token_empty($target)) { log_message('debug', 'send_notif: skip, no token'); return true; } if (!$this->is_valid_fcm_token($target)) { log_message('debug', 'send_notif: skip, invalid/placeholder FCM token'); return true; } if (!function_exists('fcm_v1_validate_token')) { get_instance()->load->helper('fcm_v1_helper'); } if (!fcm_v1_validate_token()) { log_message('error', 'send_notif: Firebase token not ready, skip send'); return false; } $data = array( 'title' => $title, 'message' => $message, 'type' => 4, ); $options = array( 'title' => $title, 'body' => $message, ); return $this->send_generic_to_token($target, $data, $options); } public function send_notif_topup($title, $id, $message, $method, $token) { if ($this->is_token_empty($token)) { log_message('debug', 'send_notif_topup: skip, no Firebase token for user id=' . $id); return; } if (!function_exists('fcm_v1_validate_token')) { get_instance()->load->helper('fcm_v1_helper'); } if (!fcm_v1_validate_token()) { log_message('error', 'send_notif_topup: Firebase token not ready, skip send for user id=' . $id); return; } // Direct token notification for topup via HTTP v1. $data = array( 'title' => $title, 'id' => $id, 'message'=> $message, 'method' => $method, 'type' => 3, ); $options = array( 'title' => $title, 'body' => $message, ); $this->send_generic_to_token($token, $data, $options); } /** * Send FCM via HTTP v1 API to a single device token. * Used by User and Driver apps (notification/send_generic). * $data must be flat key-value; values are stringified for FCM. * * @param string $target FCM device token * @param array $data Data payload (keys preserved; values cast to string) * @param array $options Optional 'title', 'body' for notification * @return bool True on success, false on failure */ public function send_generic_to_token($target, $data, $options = array()) { if ($this->is_token_empty($target)) { log_message('debug', 'FCM v1 send_generic_to_token: skip, target token empty'); return false; } if (!$this->is_valid_fcm_token($target)) { log_message('debug', 'FCM v1 send_generic_to_token: skip, invalid/placeholder token'); return false; } if (!function_exists('fcm_v1_send')) { get_instance()->load->helper('fcm_v1_helper'); } if (!fcm_v1_validate_token()) { log_message('error', 'FCM v1 send_generic_to_token: Firebase token not ready, skip send'); return false; } $flat = array(); foreach ($data as $k => $v) { if (is_array($v) || is_object($v)) { $flat[$k] = json_encode($v); } else { $flat[$k] = (string) $v; } } log_message('debug', 'FCM v1 send_generic_to_token target=' . substr((string)$target, 0, 20) . '... payload=' . json_encode($flat)); $result = fcm_v1_send($target, $flat, false, $options); if ($result === false) { log_message('error', 'FCM v1 send_generic_to_token failed for target=' . substr((string)$target, 0, 20) . '...'); } return $result !== false; } /** * Send FCM via HTTP v1 API to a topic. * * @param string $target Topic name (without /topics/ prefix) * @param array $data Data payload * @param array $options Optional 'title', 'body' * @return bool True on success, false on failure */ public function send_generic_to_topic($target, $data, $options = array()) { if (!function_exists('fcm_v1_send')) { get_instance()->load->helper('fcm_v1_helper'); } if (!fcm_v1_validate_token()) { log_message('error', 'FCM v1 send_generic_to_topic: Firebase token not ready, skip send'); return false; } $flat = array(); foreach ($data as $k => $v) { if (is_array($v) || is_object($v)) { $flat[$k] = json_encode($v); } else { $flat[$k] = (string) $v; } } log_message('debug', 'FCM v1 send_generic_to_topic topic=' . (string)$target . ' payload=' . json_encode($flat)); $result = fcm_v1_send($target, $flat, true, $options); if ($result === false) { log_message('error', 'FCM v1 send_generic_to_topic failed for topic=' . (string)$target); } return $result !== false; } }