286 lines
8.3 KiB
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;
|
|
}
|
|
}
|