523 lines
20 KiB
PHP
523 lines
20 KiB
PHP
<?php
|
|
|
|
namespace App\Libraries;
|
|
|
|
use App\Models\Outlet;
|
|
use App\Models\Orders;
|
|
use App\Models\LogGrab;
|
|
use App\Models\OrderDeliveries;
|
|
|
|
class Grab {
|
|
|
|
protected $api_url;
|
|
protected $client_id;
|
|
protected $client_secret;
|
|
protected $access_token;
|
|
protected $outlet;
|
|
|
|
public function __construct() {
|
|
$this->api_url = defined('GRAB_API_URL') ? GRAB_API_URL : '';
|
|
$this->client_id = defined('GRAB_CLIENT_ID') ? GRAB_CLIENT_ID : '';
|
|
$this->client_secret = defined('GRAB_CLIENT_SECRET') ? GRAB_CLIENT_SECRET : '';
|
|
|
|
helper("general");
|
|
|
|
$this->outlet = new Outlet();
|
|
|
|
// Get access token
|
|
$this->getAccessToken();
|
|
}
|
|
|
|
/**
|
|
* Get OAuth 2.0 access token for GrabExpress API
|
|
*/
|
|
private function getAccessToken() {
|
|
$token_url = $this->api_url . '/grabid/v1/oauth2/token';
|
|
|
|
$request_data = [
|
|
'client_id' => $this->client_id,
|
|
'client_secret' => $this->client_secret,
|
|
'grant_type' => 'client_credentials',
|
|
'scope' => 'grab_express.partner_deliveries'
|
|
];
|
|
|
|
$headers = [
|
|
'Cache-Control: no-cache',
|
|
'Content-Type: application/json'
|
|
];
|
|
|
|
$response = send_api_request('POST', $token_url, $headers, json_encode($request_data));
|
|
|
|
if (isset($response['access_token'])) {
|
|
$this->access_token = $response['access_token'];
|
|
} else {
|
|
log_message('error', 'Failed to get Grab access token: ' . json_encode($response));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Request delivery quotation from GrabExpress
|
|
*/
|
|
public function requestQuotation($selected_date = null, $selected_time = null, $total_amount = null, $outlet_id = null, $latitude = null, $longitude = null, $address = null) {
|
|
// Determine service type based on amount
|
|
if ($total_amount > 150) {
|
|
$service_type = 'CAR';
|
|
} else {
|
|
$service_type = 'MOTORCYCLE';
|
|
}
|
|
|
|
// Validate service type
|
|
if (!in_array($service_type, ['MOTORCYCLE', 'CAR'])) {
|
|
$service_type = 'MOTORCYCLE'; // Default to motorcycle
|
|
}
|
|
|
|
// Get outlet details
|
|
$outlet_data = $this->outlet->where('id', $outlet_id)->first();
|
|
$outlet_address = $outlet_data['address'];
|
|
$outlet_latitude = $outlet_data['latitude'];
|
|
$outlet_longitude = $outlet_data['longitude'];
|
|
|
|
// Validate coordinates
|
|
if (empty($outlet_latitude) || empty($outlet_longitude) || empty($latitude) || empty($longitude)) {
|
|
return [
|
|
'error' => 'Invalid coordinates provided',
|
|
'outlet_coords' => ['lat' => $outlet_latitude, 'lng' => $outlet_longitude],
|
|
'customer_coords' => ['lat' => $latitude, 'lng' => $longitude]
|
|
];
|
|
}
|
|
|
|
// Prepare request data following GrabExpress API structure
|
|
$request_data = [
|
|
'serviceType' => 'INSTANT',
|
|
'vehicleType' => $service_type === 'CAR' ? 'CAR' : 'BIKE',
|
|
'codType' => 'REGULAR',
|
|
'packages' => [
|
|
[
|
|
'name' => 'Food Package',
|
|
'description' => 'Food delivery package',
|
|
'quantity' => 1,
|
|
'price' => $total_amount,
|
|
'dimensions' => [
|
|
'height' => 0,
|
|
'width' => 0,
|
|
'depth' => 0,
|
|
'weight' => 0
|
|
]
|
|
]
|
|
],
|
|
'origin' => [
|
|
'address' => $outlet_address,
|
|
'keywords' => 'US Pizza Outlet',
|
|
'cityCode' => 'KUL', // Kuala Lumpur, Malaysia
|
|
'coordinates' => [
|
|
'latitude' => (float) $outlet_latitude,
|
|
'longitude' => (float) $outlet_longitude
|
|
]
|
|
],
|
|
'destination' => [
|
|
'address' => $address,
|
|
'keywords' => 'Customer Address',
|
|
'cityCode' => 'KUL', // Kuala Lumpur, Malaysia
|
|
'coordinates' => [
|
|
'latitude' => (float) $latitude,
|
|
'longitude' => (float) $longitude
|
|
]
|
|
]
|
|
];
|
|
|
|
// Handle preorder/scheduled delivery
|
|
if ($selected_date && $selected_time) {
|
|
$local_datetime = $selected_date . ' ' . $selected_time;
|
|
$dt = new \DateTime($local_datetime, new \DateTimeZone('Asia/Kuala_Lumpur'));
|
|
$request_data['schedule'] = [
|
|
'pickupTimeFrom' => $dt->format('Y-m-d\TH:i:s+08:00'),
|
|
'pickupTimeTo' => $dt->add(new \DateInterval('PT1H'))->format('Y-m-d\TH:i:s+08:00')
|
|
];
|
|
}
|
|
|
|
$headers = [
|
|
'Authorization: Bearer ' . $this->access_token,
|
|
'Content-Type: application/json',
|
|
'cache-control: no-cache'
|
|
];
|
|
|
|
$response = send_api_request('POST', $this->api_url . '/grab-express-sandbox/v1/deliveries/quotes', $headers, json_encode($request_data));
|
|
|
|
// Debug: Log the response
|
|
log_message('info', 'Grab Quotation Response: ' . json_encode($response));
|
|
|
|
// Log the request
|
|
$log_grab = new LogGrab();
|
|
$log_grab->insert([
|
|
'url' => $this->api_url . '/grab-express-sandbox/v1/deliveries/quotes',
|
|
'request' => json_encode($request_data),
|
|
'respond' => json_encode($response),
|
|
'quotation_id' => $response['quotes'][0]['quoteId'] ?? null,
|
|
'created_at' => date('Y-m-d H:i:s'),
|
|
'updated_at' => date('Y-m-d H:i:s')
|
|
]);
|
|
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Submit delivery order to GrabExpress
|
|
*/
|
|
public function submitOrder($order_id) {
|
|
$orders = new Orders();
|
|
$order = $orders->select('orders.*, customer_addresses.address, customer_addresses.latitude, customer_addresses.longitude, customer_addresses.name as recipient_name, customer_addresses.phone as recipient_phone, customer_addresses.note as recipient_note, outlets.pic_name, outlets.pic_phone')
|
|
->join('outlets', 'outlets.id = orders.outlet_id')
|
|
->join('customer_addresses', 'customer_addresses.id = orders.customer_address_id AND customer_addresses.deleted_at IS NULL', 'left')
|
|
->where('orders.id', $order_id)
|
|
->where('orders.deleted_at', null)
|
|
->first();
|
|
|
|
$quotation_id = $order['grab_quot_id'];
|
|
|
|
// Get outlet data for origin coordinates
|
|
$outlet_data = $this->outlet->where('id', $order['outlet_id'])->first();
|
|
if (!$outlet_data) {
|
|
return ['error' => 'Outlet not found'];
|
|
}
|
|
$request_data = [
|
|
'merchantOrderID' => 'USPIZZA_' . $order_id,
|
|
'serviceType' => 'INSTANT',
|
|
'vehicleType' => 'BIKE',
|
|
'codType' => 'REGULAR',
|
|
'paymentMethod' => 'CASHLESS',
|
|
'packages' => [
|
|
[
|
|
'name' => 'Food Package',
|
|
'description' => 'Food delivery package',
|
|
'quantity' => 1,
|
|
'price' => (int) (($order['grand_total'] ?? 0)),
|
|
'dimensions' => [
|
|
'height' => 1,
|
|
'width' => 1,
|
|
'depth' => 1,
|
|
'weight' => 1
|
|
]
|
|
]
|
|
],
|
|
'origin' => [
|
|
'address' => $outlet_data['address'],
|
|
'cityCode' => 'KUL',
|
|
'coordinates' => [
|
|
'latitude' => round((float) $outlet_data['latitude'], 6),
|
|
'longitude' => round((float) $outlet_data['longitude'], 6)
|
|
]
|
|
],
|
|
'destination' => [
|
|
'address' => $order['address'],
|
|
'cityCode' => 'KUL',
|
|
'coordinates' => [
|
|
'latitude' => round((float) $order['latitude'], 6),
|
|
'longitude' => round((float) $order['longitude'], 6)
|
|
]
|
|
],
|
|
'recipient' => [
|
|
'firstName' => $order['recipient_name'] ?? 'Customer',
|
|
'lastName' => '',
|
|
'email' => 'customer@example.com',
|
|
'phone' => $order['recipient_phone'] ? $order['recipient_phone'] : '0123456789',
|
|
'smsEnabled' => true
|
|
],
|
|
'sender' => [
|
|
'firstName' => $outlet_data['pic_name'] ?? 'US Pizza',
|
|
'companyName' => 'US Pizza',
|
|
'email' => 'delivery@uspizza.com',
|
|
'phone' => $outlet_data['pic_phone'] ? $outlet_data['pic_phone'] : '0123456789',
|
|
'smsEnabled' => true
|
|
]
|
|
];
|
|
|
|
// Add schedule if it's a preorder
|
|
if ($order['selected_date'] && $order['selected_time']) {
|
|
$local_datetime = $order['selected_date'] . ' ' . $order['selected_time'];
|
|
$dt = new \DateTime($local_datetime, new \DateTimeZone('Asia/Kuala_Lumpur'));
|
|
$request_data['schedule'] = [
|
|
'pickupTimeFrom' => $dt->format('Y-m-d\TH:i:s+08:00'),
|
|
'pickupTimeTo' => $dt->add(new \DateInterval('PT1H'))->format('Y-m-d\TH:i:s+08:00')
|
|
];
|
|
}
|
|
|
|
// Remove schedule temporarily to test if that's causing the issue
|
|
// unset($request_data['schedule']);
|
|
|
|
// Validate required fields
|
|
if (empty($request_data['origin']['address']) || empty($request_data['destination']['address'])) {
|
|
return ['error' => 'Missing address information'];
|
|
}
|
|
|
|
if (empty($request_data['origin']['coordinates']['latitude']) || empty($request_data['origin']['coordinates']['longitude']) ||
|
|
empty($request_data['destination']['coordinates']['latitude']) || empty($request_data['destination']['coordinates']['longitude'])) {
|
|
return ['error' => 'Missing coordinate information'];
|
|
}
|
|
|
|
$headers = [
|
|
'Authorization: Bearer ' . $this->access_token,
|
|
'Content-Type: application/json; charset=utf-8',
|
|
'cache-control: no-cache'
|
|
];
|
|
|
|
log_message('info', 'Grab Submit Order Headers: ' . json_encode($headers));
|
|
log_message('info', 'Grab Submit Order Request: ' . json_encode($request_data));
|
|
|
|
// Debug: Check JSON encoding
|
|
$json_request = json_encode($request_data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
log_message('error', 'JSON encoding error: ' . json_last_error_msg());
|
|
return ['error' => 'JSON encoding failed: ' . json_last_error_msg()];
|
|
}
|
|
|
|
log_message('info', 'Grab Submit Order JSON: ' . $json_request);
|
|
$response = send_api_request('POST', $this->api_url . '/grab-express-sandbox/v1/deliveries', $headers, $json_request);
|
|
|
|
// Debug: Log the response
|
|
log_message('info', 'Grab Submit Order Response: ' . json_encode($response));
|
|
|
|
// Log the request
|
|
$log_grab = new LogGrab();
|
|
$log_grab->insert([
|
|
'url' => $this->api_url . '/grab-express-sandbox/v1/deliveries',
|
|
'request' => json_encode($request_data),
|
|
'respond' => json_encode($response),
|
|
'quotation_id' => $quotation_id,
|
|
'created_at' => date('Y-m-d H:i:s'),
|
|
'updated_at' => date('Y-m-d H:i:s')
|
|
]);
|
|
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Handle webhook notifications from GrabExpress
|
|
*/
|
|
public function handleWebhook($data) {
|
|
// Log the webhook
|
|
$log_grab = new LogGrab();
|
|
$log_grab->insert([
|
|
'url' => $this->api_url . '/webhook',
|
|
'request' => json_encode($data),
|
|
'respond' => 'Status: ' . ($data['status'] ?? 'Unknown'),
|
|
'quotation_id' => null,
|
|
'created_at' => date('Y-m-d H:i:s'),
|
|
'updated_at' => date('Y-m-d H:i:s')
|
|
]);
|
|
|
|
// Handle delivery status updates
|
|
if (isset($data['deliveryID']) && isset($data['status'])) {
|
|
$delivery_id = $data['deliveryID'];
|
|
$status = $data['status'];
|
|
$merchant_order_id = $data['merchantOrderID'] ?? null;
|
|
$tracking_url = $data['trackURL'] ?? null;
|
|
$pod_url = $data['dropoffProofURL'] ?? null;
|
|
|
|
// Update order status
|
|
$this->updateOrderStatus($delivery_id, $status, $merchant_order_id, $tracking_url, $pod_url);
|
|
|
|
// Log additional webhook data
|
|
log_message('info', 'Grab Webhook - Delivery ID: ' . $delivery_id . ', Status: ' . $status . ', Order: ' . $merchant_order_id);
|
|
|
|
// Log driver information if available
|
|
if (isset($data['driver'])) {
|
|
log_message('info', 'Grab Webhook - Driver: ' . $data['driver']['name'] . ', Phone: ' . $data['driver']['phone'] . ', License: ' . $data['driver']['licensePlate']);
|
|
}
|
|
|
|
// Log pickup pin if available
|
|
if (isset($data['pickupPin'])) {
|
|
log_message('info', 'Grab Webhook - Pickup Pin: ' . $data['pickupPin']);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Update order status based on delivery status
|
|
*/
|
|
private function updateOrderStatus($delivery_id, $status, $merchant_order_id = null, $tracking_url = null, $pod_url = null) {
|
|
$orders = new Orders();
|
|
|
|
// Try to find order by delivery ID first, then by merchant order ID
|
|
$order = null;
|
|
if ($delivery_id) {
|
|
$order = $orders->select('orders.*, customer_addresses.phone as recipient_phone')
|
|
->join('order_deliveries', 'order_deliveries.order_id = orders.id')
|
|
->join('customer_addresses', 'customer_addresses.id = orders.customer_address_id AND customer_addresses.deleted_at IS NULL', 'left')
|
|
->where('order_deliveries.provider_name', 'Grab')
|
|
->where('order_deliveries.provider_order_id', $delivery_id)
|
|
->first();
|
|
}
|
|
|
|
// If not found by delivery ID, try by merchant order ID
|
|
if (!$order && $merchant_order_id) {
|
|
$order_id = str_replace('USPIZZA_', '', $merchant_order_id);
|
|
$order = $orders->where('id', $order_id)->first();
|
|
}
|
|
// echo(123);exit;
|
|
if ($order) {
|
|
$order_id = $order['id'];
|
|
// echo($order_id);
|
|
// exit;
|
|
$order_status = 'pending';
|
|
|
|
// Map GrabExpress status to internal status
|
|
switch ($status) {
|
|
case 'PICKING_UP':
|
|
$order_status = 'picked_up';
|
|
break;
|
|
case 'IN_DELIVERY':
|
|
$order_status = 'on_the_way';
|
|
break;
|
|
case 'COMPLETED':
|
|
$order_status = 'completed';
|
|
break;
|
|
case 'CANCELLED':
|
|
$order_status = 'cancelled';
|
|
break;
|
|
default:
|
|
log_message('info', 'Grab Webhook - Unknown status: ' . $status);
|
|
$order_status = 'pending';
|
|
break;
|
|
}
|
|
|
|
// Update order status
|
|
$orders->update($order_id, ['status' => $order_status]);
|
|
|
|
//send notification to customer
|
|
$message = '';
|
|
switch($order_status){
|
|
case 'on_the_way':
|
|
$message = "🚚 Your order [**".$order['order_so']."**] is on the way!\nThe driver is delivering your food now.\nPlease get ready to receive it.\nHere is the tracking link: \n\n" . $tracking_url;
|
|
break;
|
|
case 'completed':
|
|
$message = "🎉 Your order [**".$order['order_so']."**] has been completed.\nWe hope you enjoy your meal!\nThank you for ordering with us.";
|
|
break;
|
|
}
|
|
|
|
if($message){
|
|
$wato = new Wato();
|
|
$wato->pushNotification($order['recipient_phone'], $message);
|
|
}
|
|
|
|
|
|
log_message('info', 'Grab Webhook - Updated order ' . $order_id . ' status to: ' . $order_status);
|
|
|
|
// Update delivery record with additional information
|
|
$order_deliveries = new OrderDeliveries();
|
|
$delivery_update_data = [
|
|
'status' => $order_status,
|
|
'updated_at' => date('Y-m-d H:i:s'),
|
|
'tracking_link' => $tracking_url,
|
|
'POD_url' => $pod_url
|
|
];
|
|
|
|
// Add delivery completion timestamp if completed
|
|
if ($order_status === 'completed') {
|
|
$delivery_update_data['delivered_at'] = date('Y-m-d H:i:s');
|
|
}
|
|
|
|
// Update delivery record
|
|
$order_deliveries->where('order_id', $order_id)
|
|
->where('provider_name', 'Grab')
|
|
->where('provider_order_id', $delivery_id)
|
|
->set($delivery_update_data)
|
|
->update();
|
|
} else {
|
|
log_message('warning', 'Grab Webhook - Order not found for delivery ID: ' . $delivery_id . ' or merchant order ID: ' . $merchant_order_id);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get delivery details from GrabExpress
|
|
*/
|
|
public function getDeliveryDetails($delivery_id) {
|
|
$headers = [
|
|
'Authorization: Bearer ' . $this->access_token,
|
|
'Content-Type: application/json',
|
|
'cache-control: no-cache'
|
|
];
|
|
|
|
$response = send_api_request('GET', $this->api_url . '/grab-express-sandbox/v1/deliveries/' . $delivery_id, $headers);
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Cancel delivery request
|
|
*/
|
|
public function cancelDelivery($delivery_id) {
|
|
$headers = [
|
|
'Authorization: Bearer ' . $this->access_token,
|
|
'Content-Type: application/json',
|
|
'cache-control: no-cache'
|
|
];
|
|
|
|
$response = send_api_request('DELETE', $this->api_url . '/grab-express-sandbox/v1/deliveries/' . $delivery_id, $headers);
|
|
|
|
// Log the cancellation
|
|
$log_grab = new LogGrab();
|
|
$log_grab->insert([
|
|
'url' => $this->api_url . '/grab-express-sandbox/v1/deliveries/' . $delivery_id,
|
|
'request' => 'DELETE request',
|
|
'respond' => json_encode($response),
|
|
'quotation_id' => null,
|
|
'created_at' => date('Y-m-d H:i:s'),
|
|
'updated_at' => date('Y-m-d H:i:s')
|
|
]);
|
|
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Store additional webhook information like driver details, pickup pin, etc.
|
|
*/
|
|
private function storeWebhookInfo($delivery_id, $webhook_data) {
|
|
$order_deliveries = new OrderDeliveries();
|
|
|
|
// Find the delivery record
|
|
$delivery_record = $order_deliveries->where('provider_name', 'Grab')
|
|
->where('provider_order_id', $delivery_id)
|
|
->first();
|
|
|
|
if ($delivery_record) {
|
|
$update_data = [];
|
|
|
|
// Store driver information if available
|
|
if (isset($webhook_data['driver'])) {
|
|
$update_data['driver_name'] = $webhook_data['driver']['name'] ?? '';
|
|
$update_data['driver_phone'] = $webhook_data['driver']['phone'] ?? '';
|
|
$update_data['driver_license'] = $webhook_data['driver']['licensePlate'] ?? '';
|
|
}
|
|
|
|
// Store pickup pin if available
|
|
if (isset($webhook_data['pickupPin'])) {
|
|
$update_data['pickup_pin'] = $webhook_data['pickupPin'];
|
|
}
|
|
|
|
// Store tracking URL if available
|
|
if (isset($webhook_data['trackURL'])) {
|
|
$update_data['tracking_link'] = $webhook_data['trackURL'];
|
|
}
|
|
|
|
// Store distance if available
|
|
if (isset($webhook_data['distance'])) {
|
|
$update_data['distance_meters'] = $webhook_data['distance'];
|
|
}
|
|
|
|
// Store country if available
|
|
if (isset($webhook_data['country'])) {
|
|
$update_data['country'] = $webhook_data['country'];
|
|
}
|
|
|
|
// Update the delivery record with additional information
|
|
if (!empty($update_data)) {
|
|
$update_data['updated_at'] = date('Y-m-d H:i:s');
|
|
$order_deliveries->where('id', $delivery_record['id'])
|
|
->set($update_data)
|
|
->update();
|
|
}
|
|
}
|
|
}
|
|
}
|