TA/web_JMW/app/Services/WhatsAppBusinessService.php

286 lines
8.3 KiB
PHP

<?php
namespace App\Services;
use App\Models\Category;
use App\Models\User;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
class WhatsAppBusinessService
{
/**
* Check whether WhatsApp Business API is configured.
*
* @return bool
*/
public function isConfigured()
{
return filled(config('services.whatsapp.token'))
&& filled(config('services.whatsapp.phone_number_id'))
&& filled($this->adminRecipient());
}
/**
* Send a car notification to the admin via WhatsApp Cloud API.
*
* @param \App\Models\Category $category
* @param \App\Models\User|null $customer
* @return array<string, mixed>
*/
public function sendCarNotificationToAdmin(Category $category, ?User $customer = null)
{
if (!$this->isConfigured()) {
return [
'success' => false,
'message' => 'WhatsApp Business API belum dikonfigurasi.',
];
}
$recipient = $this->formatPhoneNumber($this->adminRecipient());
$endpoint = $this->graphEndpoint('messages');
$imageUrl = $this->getCarImageUrl($category);
$mediaId = $this->uploadCarImage($category);
$canSendImage = filled($mediaId) || $this->isPublicMediaUrl($imageUrl);
$caption = $this->buildCaption($category, $customer, $canSendImage);
$payload = [
'messaging_product' => 'whatsapp',
'recipient_type' => 'individual',
'to' => $recipient,
'type' => $canSendImage ? 'image' : 'text',
];
if ($mediaId) {
$payload['image'] = [
'id' => $mediaId,
'caption' => $caption,
];
} elseif ($canSendImage) {
$payload['image'] = [
'link' => $imageUrl,
'caption' => $caption,
];
} else {
$payload['text'] = [
'preview_url' => false,
'body' => $caption,
];
}
$response = Http::withToken(config('services.whatsapp.token'))
->acceptJson()
->post($endpoint, $payload);
if ($response->successful()) {
return [
'success' => true,
'message' => $canSendImage
? 'Notifikasi gambar katalog mobil berhasil dikirim ke admin.'
: 'Notifikasi teks berhasil dikirim ke admin.',
'data' => $response->json(),
];
}
Log::warning('Gagal mengirim WhatsApp Business notification.', [
'category_id' => $category->id,
'status' => $response->status(),
'response' => $response->json() ?: $response->body(),
]);
return [
'success' => false,
'message' => 'Gagal mengirim notifikasi WhatsApp Business ke admin.',
'error' => $response->json() ?: $response->body(),
];
}
/**
* Build message caption sent to admin.
*
* @param \App\Models\Category $category
* @param \App\Models\User|null $customer
* @param bool $withImage
* @return string
*/
private function buildCaption(Category $category, ?User $customer = null, $withImage = true)
{
$lines = [
'Customer tertarik dengan mobil katalog ini.',
'Mobil: ' . $category->name,
'Harga: Rp ' . number_format((float) $category->price, 0, ',', '.'),
'Tahun: ' . ($category->year ?: '-'),
];
if ($customer) {
$lines[] = 'Username customer: ' . $customer->username;
$lines[] = 'Email customer: ' . $customer->email;
if ($customer->phone_number) {
$lines[] = 'No. HP customer: ' . $customer->phone_number;
}
} else {
$lines[] = 'Customer: pengunjung belum login';
}
$detailUrl = route('mobil-bekas.detail', ['category' => $category->id]);
$lines[] = 'Link detail: ' . $detailUrl;
if (!$withImage) {
$lines[] = 'Link foto katalog: ' . ($this->getCarImageUrl($category) ?: '-');
$lines[] = 'Catatan: gambar dikirim sebagai teks karena file katalog belum bisa dikirim sebagai media.';
}
return implode("\n", $lines);
}
/**
* Upload the local catalog image to WhatsApp and return its media ID.
*
* @param \App\Models\Category $category
* @return string|null
*/
private function uploadCarImage(Category $category)
{
if (!$category->image || !Storage::disk('public')->exists($category->image)) {
return null;
}
$path = Storage::disk('public')->path($category->image);
$mimeType = Storage::disk('public')->mimeType($category->image) ?: 'image/jpeg';
if (!str_starts_with($mimeType, 'image/')) {
return null;
}
$handle = fopen($path, 'r');
if (!$handle) {
return null;
}
try {
$response = Http::withToken(config('services.whatsapp.token'))
->acceptJson()
->attach('file', $handle, basename($path), ['Content-Type' => $mimeType])
->post($this->graphEndpoint('media'), [
'messaging_product' => 'whatsapp',
'type' => $mimeType,
]);
} finally {
fclose($handle);
}
if ($response->successful()) {
return $response->json('id');
}
Log::warning('Gagal upload media WhatsApp Business.', [
'category_id' => $category->id,
'status' => $response->status(),
'response' => $response->json() ?: $response->body(),
]);
return null;
}
/**
* Get admin WhatsApp recipient from the dedicated WhatsApp setting,
* with fallback to the existing direct chat number.
*
* @return string|null
*/
private function adminRecipient()
{
return config('services.whatsapp.admin_recipient');
}
/**
* Get a public absolute image URL if the car has an image.
*
* @param \App\Models\Category $category
* @return string|null
*/
private function getCarImageUrl(Category $category)
{
if (!$category->image) {
return null;
}
return $this->publicUrl(Storage::disk('public')->url($category->image));
}
/**
* Check whether the media URL is public enough for WhatsApp to fetch.
*
* @param string|null $url
* @return bool
*/
private function isPublicMediaUrl($url)
{
if (!$url) {
return false;
}
return !str_contains($url, 'localhost')
&& !str_contains($url, '127.0.0.1');
}
/**
* Build an absolute URL from APP_URL so the Cloud API can fetch media.
*
* @param string $path
* @return string
*/
private function publicUrl($path)
{
if (filter_var($path, FILTER_VALIDATE_URL)) {
return $path;
}
return rtrim(config('app.url'), '/') . '/' . ltrim($path, '/');
}
/**
* Build WhatsApp Graph API endpoint for the configured phone number.
*
* @param string $path
* @return string
*/
private function graphEndpoint($path)
{
return rtrim(config('services.whatsapp.base_url'), '/')
. '/'
. config('services.whatsapp.api_version')
. '/'
. config('services.whatsapp.phone_number_id')
. '/'
. ltrim($path, '/');
}
/**
* Normalize phone number to WhatsApp format.
*
* @param string $phoneNumber
* @return string
*/
private function formatPhoneNumber($phoneNumber)
{
$phoneNumber = preg_replace('/[^0-9]/', '', $phoneNumber);
if (strlen($phoneNumber) === 10 && substr($phoneNumber, 0, 1) === '8') {
return '62' . $phoneNumber;
}
if (strlen($phoneNumber) >= 11 && substr($phoneNumber, 0, 2) === '08') {
return '62' . substr($phoneNumber, 1);
}
if (substr($phoneNumber, 0, 2) !== '62') {
return '62' . $phoneNumber;
}
return $phoneNumber;
}
}