Files
API-KTA/app/Libraries/MekariWhatsAppService.php
2026-04-25 04:41:23 +07:00

461 lines
18 KiB
PHP

<?php
namespace App\Libraries;
use App\Config\ExternalApi;
class MekariWhatsAppService
{
private $hmacUsername;
private $hmacSecret;
private $apiUrl;
private $channelIntegrationId;
private $messageTemplateId;
private $messageTemplateIdPelanggaranID;
private $messageTemplateIdPelanggaranEN;
private $uploadApiUrl;
public function __construct()
{
$config = config('ExternalApi');
$this->hmacUsername = $config->hmacUsername;
$this->hmacSecret = $config->hmacSecret;
$this->channelIntegrationId = $config->channelIntegrationId;
$this->messageTemplateId = $config->messageTemplateId;
$this->messageTemplateIdPelanggaranID = $config->messageTemplateIdPelanggaranID;
$this->messageTemplateIdPelanggaranEN = $config->messageTemplateIdPelanggaranEN;
$this->apiUrl = 'https://api.mekari.com/qontak/chat/v1/broadcasts/whatsapp/direct';
$this->uploadApiUrl = 'https://api.mekari.com/qontak/chat/v1/file_uploader';
}
/**
* Upload foto ke Mekari untuk digunakan di WhatsApp template
* Jika file WebP, akan dikonversi ke JPEG dulu
*/
public function uploadImage($filePath)
{
$tempJpegPath = null;
try {
if (!file_exists($filePath)) {
throw new \Exception("File tidak ditemukan: {$filePath}");
}
$fileExtension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
$uploadFilePath = $filePath;
$uploadFileName = basename($filePath);
// Konversi WebP ke JPEG
if ($fileExtension === 'webp') {
log_message('debug', "Converting WebP to JPEG: {$filePath}");
$tempJpegPath = sys_get_temp_dir() . '/' . uniqid('wa_upload_') . '.jpg';
$image = imagecreatefromwebp($filePath);
if (!$image) {
throw new \Exception("Gagal membaca file WebP");
}
$converted = imagejpeg($image, $tempJpegPath, 90);
imagedestroy($image);
if (!$converted) {
throw new \Exception("Gagal konversi WebP ke JPEG");
}
$uploadFilePath = $tempJpegPath;
$uploadFileName = pathinfo($filePath, PATHINFO_FILENAME) . '.jpg';
log_message('debug', "WebP converted to JPEG: {$tempJpegPath}");
}
$urlParts = parse_url($this->uploadApiUrl);
$path = $urlParts['path'];
$dateString = gmdate('D, d M Y H:i:s') . ' GMT';
$hmacAuth = $this->generateHmacAuth('POST', $path, $dateString);
$cFile = new \CURLFile($uploadFilePath, 'image/jpeg', $uploadFileName);
$ch = curl_init($this->uploadApiUrl);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: ' . $hmacAuth,
'Date: ' . $dateString
],
CURLOPT_POSTFIELDS => ['file' => $cFile],
CURLOPT_TIMEOUT => 60,
CURLOPT_SSL_VERIFYPEER => true
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
// Cleanup temporary file
if ($tempJpegPath && file_exists($tempJpegPath)) {
unlink($tempJpegPath);
log_message('debug', "Temporary JPEG file deleted: {$tempJpegPath}");
}
if ($error) {
log_message('error', "Mekari Upload cURL Error: {$error}");
return [
'success' => false,
'message' => 'Gagal upload gambar ke server'
];
}
log_message('debug', "Mekari Upload Response [{$httpCode}]: {$response}");
$result = json_decode($response, true);
if ($httpCode >= 200 && $httpCode < 300 && isset($result['data']['url'])) {
return [
'success' => true,
'message' => 'Gambar berhasil diupload',
'url' => $result['data']['url'],
'filename' => $result['data']['filename']
];
}
$errorMessage = 'Gagal upload gambar';
if (isset($result['error']['message'])) {
$errorMessage = $result['error']['message'];
} elseif (isset($result['message'])) {
$errorMessage = $result['message'];
}
return [
'success' => false,
'message' => $errorMessage
];
} catch (\Exception $e) {
if ($tempJpegPath && file_exists($tempJpegPath)) {
unlink($tempJpegPath);
}
log_message('error', "Mekari Upload Exception: " . $e->getMessage());
return [
'success' => false,
'message' => 'Terjadi kesalahan saat upload gambar: ' . $e->getMessage()
];
}
}
public function sendOtp($phoneNumber, $otpCode, $userName = '', $subject = 'VERIFIKASI AKUN')
{
try {
$phoneNumber = $this->formatPhoneNumber($phoneNumber);
$urlParts = parse_url($this->apiUrl);
$path = $urlParts['path'];
$dateString = gmdate('D, d M Y H:i:s') . ' GMT';
$hmacAuth = $this->generateHmacAuth('POST', $path, $dateString);
$payload = [
'to_number' => $phoneNumber,
'to_name' => $userName ?: 'User',
'message_template_id' => $this->messageTemplateId,
'channel_integration_id' => $this->channelIntegrationId,
'language' => [
'code' => 'id'
],
'parameters' => [
'body' => [
[
'key' => '1',
'value' => 'code',
'value_text' => $otpCode
],
],
"buttons" => [
[
"index" => "0",
"type" => "url",
"value" => "$otpCode"
]
]
]
];
log_message('debug', "Mekari WA OTP Request - Date: {$dateString}");
log_message('debug', "Mekari WA OTP Payload: " . json_encode($payload));
return $this->sendRequest($payload, $hmacAuth, $dateString);
} catch (\Exception $e) {
log_message('error', "Mekari WA OTP Exception: " . $e->getMessage());
return [
'success' => false,
'message' => 'Terjadi kesalahan saat mengirim OTP'
];
}
}
/**
* Kirim notifikasi pelanggaran via WhatsApp dengan header image
*
* @param string $phoneNumber Nomor WhatsApp tujuan
* @param array $data Data pelanggaran dengan keys:
* - nama, jenis_pelanggaran, tanggal_formatted, waktu_formatted
* - lokasi_kejadian, dasar_hukum, pasal, hukuman, urutan_pelanggaran
* - pelanggaran_id, is_luar_negeri, image_url (optional), image_filename (optional)
*/
public function sendNotifikasiPelanggaran($phoneNumber, $data)
{
try {
$phoneNumber = $this->formatPhoneNumber($phoneNumber);
$urlParts = parse_url($this->apiUrl);
$path = $urlParts['path'];
$dateString = gmdate('D, d M Y H:i:s') . ' GMT';
$hmacAuth = $this->generateHmacAuth('POST', $path, $dateString);
$isLuarNegeri = $data['is_luar_negeri'] ?? false;
// $templateId = $isLuarNegeri ?
// $this->messageTemplateIdPelanggaranEN :
// $this->messageTemplateIdPelanggaranID;
$templateId = $this->messageTemplateIdPelanggaranID;
// Validasi required fields
$requiredFields = [
'nama', 'jenis_pelanggaran', 'tanggal_formatted',
'waktu_formatted', 'lokasi_kejadian', 'dasar_hukum',
'pasal', 'hukuman', 'urutan_pelanggaran', 'pelanggaran_id'
];
foreach ($requiredFields as $field) {
if (empty($data[$field])) {
throw new \Exception("Field '{$field}' is required");
}
}
// Kalau template inggris sudah ada bisa di uncomment di bawah ini
// Build body parameters
// if ($isLuarNegeri) {
// $bodyParams = [
// ['key' => '1', 'value' => 'p1', 'value_text' => $data['urutan_pelanggaran']],
// ['key' => '2', 'value' => 'p2', 'value_text' => $data['title'] ?? 'Mr./Ms.'],
// ['key' => '3', 'value' => 'p3', 'value_text' => $data['nama']],
// ['key' => '4', 'value' => 'p4', 'value_text' => $data['jenis_pelanggaran']],
// ['key' => '5', 'value' => 'p5', 'value_text' => $data['tanggal_formatted']],
// ['key' => '6', 'value' => 'p6', 'value_text' => $data['waktu_formatted']],
// ['key' => '7', 'value' => 'p7', 'value_text' => $data['lokasi_kejadian']],
// ['key' => '8', 'value' => 'p8', 'value_text' => $data['dasar_hukum']],
// ['key' => '9', 'value' => 'p9', 'value_text' => $data['pasal']],
// ['key' => '10', 'value' => 'p10', 'value_text' => $data['hukuman']]
// ];
// } else {
// $bodyParams = [
// ['key' => '1', 'value' => 'p1', 'value_text' => $data['urutan_pelanggaran']],
// ['key' => '2', 'value' => 'p2', 'value_text' => $data['nama']],
// ['key' => '3', 'value' => 'p3', 'value_text' => $data['jenis_pelanggaran']],
// ['key' => '4', 'value' => 'p4', 'value_text' => $data['tanggal_formatted']],
// ['key' => '5', 'value' => 'p5', 'value_text' => $data['waktu_formatted']],
// ['key' => '6', 'value' => 'p6', 'value_text' => $data['lokasi_kejadian']],
// ['key' => '7', 'value' => 'p7', 'value_text' => $data['dasar_hukum']],
// ['key' => '8', 'value' => 'p8', 'value_text' => $data['pasal']],
// ['key' => '9', 'value' => 'p9', 'value_text' => $data['hukuman']]
// ];
// }
// Selalu pakai format Indonesia dulu (9 parameter)
$bodyParams = [
['key' => '1', 'value' => 'p1', 'value_text' => $data['urutan_pelanggaran']],
['key' => '2', 'value' => 'p2', 'value_text' => $data['nama']],
['key' => '3', 'value' => 'p3', 'value_text' => $data['jenis_pelanggaran']],
['key' => '4', 'value' => 'p4', 'value_text' => $data['tanggal_formatted']],
['key' => '5', 'value' => 'p5', 'value_text' => $data['waktu_formatted']],
['key' => '6', 'value' => 'p6', 'value_text' => $data['lokasi_kejadian']],
['key' => '7', 'value' => 'p7', 'value_text' => $data['dasar_hukum']],
['key' => '8', 'value' => 'p8', 'value_text' => $data['pasal']],
['key' => '9', 'value' => 'p9', 'value_text' => $data['hukuman']]
];
// Build payload parameters
$parameters = [
'body' => $bodyParams,
'buttons' => [
[
'index' => '0',
'type' => 'url',
'value' => $data['pelanggaran_id']
]
]
];
// Tambahkan header
if (!empty($data['image_url']) && !empty($data['image_filename'])) {
$parameters['header'] = [
'format' => 'IMAGE',
'params' => [
[
'key' => 'url',
'value' => $data['image_url']
],
[
'key' => 'filename',
'value' => $data['image_filename']
]
]
];
} else {
$parameters['header'] = [
'format' => 'IMAGE',
'params' => [
[
'key' => 'url',
'value' => "https://cdn.qontak.com/uploads/direct/images/1ed10e3c-461f-477f-a74a-ee6553f23f4d/photo_2025-11-10_15-32-37.jpg"
],
[
'key' => 'filename',
'value' => "photo_2025-11-10_15-32-37.jpg"
]
]
];
}
$payload = [
'to_number' => $phoneNumber,
'to_name' => $data['nama'],
'message_template_id' => $templateId,
'channel_integration_id' => $this->channelIntegrationId,
'language' => [
'code' => $isLuarNegeri ? 'en' : 'id'
],
'parameters' => $parameters
];
log_message('debug', "Mekari WA Pelanggaran Request - Date: {$dateString}");
log_message('debug', "Mekari WA Pelanggaran Payload: " . json_encode($payload));
$result = $this->sendRequest($payload, $hmacAuth, $dateString);
if ($result['success']) {
log_message('info', "WhatsApp notification sent to {$phoneNumber} for pelanggaran {$data['pelanggaran_id']}");
}
return $result;
} catch (\Exception $e) {
log_message('error', "Mekari WA Pelanggaran Exception: " . $e->getMessage());
return [
'success' => false,
'message' => 'Terjadi kesalahan saat mengirim notifikasi pelanggaran: ' . $e->getMessage()
];
}
}
private function sendRequest($payload, $hmacAuth, $dateString)
{
$ch = curl_init($this->apiUrl);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: ' . $hmacAuth,
'Date: ' . $dateString,
'Content-Type: application/json'
],
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_TIMEOUT => 30,
CURLOPT_SSL_VERIFYPEER => true
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($error) {
log_message('error', "Mekari WA cURL Error: {$error}");
return [
'success' => false,
'message' => 'Gagal terhubung ke server WhatsApp'
];
}
log_message('debug', "Mekari WA Response [{$httpCode}]: {$response}");
$result = json_decode($response, true);
if ($httpCode >= 200 && $httpCode < 300) {
return [
'success' => true,
'message' => 'Pesan berhasil dikirim',
'data' => $result
];
}
$errorMessage = 'Gagal mengirim pesan';
if (isset($result['error']['message'])) {
$errorMessage = $result['error']['message'];
} elseif (isset($result['message'])) {
$errorMessage = $result['message'];
}
log_message('error', "Mekari WA Error [{$httpCode}]: " . $response);
return [
'success' => false,
'message' => $errorMessage,
'error_code' => $result['error']['code'] ?? $httpCode,
// 'raw_response' => $result // Untuk debugging
];
}
private function generateHmacAuth($method, $path, $dateString)
{
$requestLine = strtoupper($method) . ' ' . $path . ' HTTP/1.1';
$stringToSign = 'date: ' . $dateString . "\n" . $requestLine;
$signature = base64_encode(hash_hmac('sha256', $stringToSign, $this->hmacSecret, true));
$hmacHeader = sprintf(
'hmac username="%s", algorithm="hmac-sha256", headers="date request-line", signature="%s"',
$this->hmacUsername,
$signature
);
return $hmacHeader;
}
private function formatPhoneNumber($phone)
{
$phone = preg_replace('/[^0-9]/', '', $phone);
if (substr($phone, 0, 2) === '62') {
return $phone;
}
if (substr($phone, 0, 1) === '0') {
return '62' . substr($phone, 1);
}
return '62' . $phone;
}
public function verifyWebhookSignature($authHeader, $dateHeader, $method, $path)
{
try {
preg_match('/signature="([^"]+)"/', $authHeader, $matches);
$receivedSignature = $matches[1] ?? '';
if (empty($receivedSignature)) {
return false;
}
$requestLine = strtoupper($method) . ' ' . $path . ' HTTP/1.1';
$stringToSign = 'date: ' . $dateHeader . "\n" . $requestLine;
$expectedSignature = base64_encode(hash_hmac('sha256', $stringToSign, $this->hmacSecret, true));
return hash_equals($expectedSignature, $receivedSignature);
} catch (\Exception $e) {
log_message('error', "HMAC Verification Error: " . $e->getMessage());
return false;
}
}
}