initial project
This commit is contained in:
@@ -0,0 +1,238 @@
|
||||
<?php
|
||||
|
||||
namespace App\Libraries;
|
||||
|
||||
use App\Models\LogPaymentTransactions;
|
||||
use App\Models\Orders;
|
||||
use App\Models\TopupModel;
|
||||
|
||||
class Fiuu
|
||||
{
|
||||
protected $endpoint;
|
||||
protected $merchantId;
|
||||
protected $verifyKey;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->endpoint = FIUU_SANDBOX_ENDPOINT;
|
||||
$this->merchantId = FIUU_MERCHANT_ID;
|
||||
$this->verifyKey = FIUU_VERIFY_KEY;
|
||||
helper('general');
|
||||
helper('order');
|
||||
}
|
||||
// public function createPayment($orderId, $amount)
|
||||
// {
|
||||
// $vcode = md5($amount . $this->merchantId . $orderId . $this->verifyKey);
|
||||
|
||||
// $query = http_build_query([
|
||||
// 'merchantid' => $this->merchantId,
|
||||
// 'orderid' => $orderId,
|
||||
// 'amount' => $amount,
|
||||
// 'vcode' => $vcode,
|
||||
// ]);
|
||||
|
||||
// $redirectUrl = 'https://sandbox-payment.fiuu.com/RMS/pay/' . $this->merchantId . '/?' . $query;
|
||||
|
||||
// return [
|
||||
// 'status' => 'success',
|
||||
// 'vcode' => $vcode,
|
||||
// 'redirect_url' => $redirectUrl,
|
||||
// ];
|
||||
// }
|
||||
|
||||
public function createPayment($order_so, $amount, $customer = [])
|
||||
{
|
||||
$vcode = md5($amount . $this->merchantId . $order_so . $this->verifyKey);
|
||||
|
||||
$fields = [
|
||||
'orderid' => $order_so,
|
||||
'currency' => 'MYR',
|
||||
'amount' => $amount,
|
||||
'vcode' => $vcode,
|
||||
'bill_name' => 'John Doe',
|
||||
'bill_email' => 'johndoe@example.com',
|
||||
'bill_mobile' => '60123456789',
|
||||
'bill_desc' => 'Test Payment',
|
||||
'returnurl' => 'https://uspizza.ipsgroup.com.my/screens/payment/loading_payment?type=order',
|
||||
'callbackurl' => 'https://icom.ipsgroup.com.my/api/payment/fiuu/notification'
|
||||
];
|
||||
|
||||
|
||||
$query = http_build_query($fields);
|
||||
|
||||
$redirectUrl = $this->endpoint . '?' . $query;
|
||||
|
||||
return [
|
||||
'status' => 'success',
|
||||
'vcode' => $vcode,
|
||||
'redirect_url' => $redirectUrl,
|
||||
];
|
||||
}
|
||||
|
||||
public function createTopup($order_so, $amount, $customer = [])
|
||||
{
|
||||
$vcode = md5($amount . $this->merchantId . $order_so . $this->verifyKey);
|
||||
|
||||
$fields = [
|
||||
'orderid' => $order_so,
|
||||
'currency' => 'MYR',
|
||||
'amount' => $amount,
|
||||
'vcode' => $vcode,
|
||||
'bill_name' => 'John Doe',
|
||||
'bill_email' => 'johndoe@example.com',
|
||||
'bill_mobile' => '60123456789',
|
||||
'bill_desc' => 'Test Payment',
|
||||
'returnurl' => 'https://uspizza.ipsgroup.com.my/screens/payment/loading_payment?type=topup',
|
||||
'callbackurl' => 'https://icom.ipsgroup.com.my/api/topup/fiuu/notification'
|
||||
];
|
||||
|
||||
|
||||
$query = http_build_query($fields);
|
||||
|
||||
$redirectUrl = $this->endpoint . '?' . $query;
|
||||
|
||||
return [
|
||||
'status' => 'success',
|
||||
'vcode' => $vcode,
|
||||
'redirect_url' => $redirectUrl,
|
||||
];
|
||||
}
|
||||
|
||||
public function validateToken($data){
|
||||
$tranID = $data['tranID'];
|
||||
$order_so = $data['orderid'];
|
||||
$status = $data['status'];
|
||||
$domain = $data['domain'];
|
||||
$amount = $data['amount'];
|
||||
$currency = $data['currency'];
|
||||
$appcode = $data['appcode'];
|
||||
$paydate = $data['paydate'];
|
||||
$skey = $data['skey'];
|
||||
|
||||
$pre_skey = md5($tranID.$order_so.$status.$domain.$amount.$currency);
|
||||
$myskey = md5($paydate.$domain.$pre_skey.$appcode.$this->verifyKey);
|
||||
return $skey == $myskey;
|
||||
}
|
||||
|
||||
public function paymentNotification($data){
|
||||
$tranID = $data['tranID'];
|
||||
$order_so = $data['orderid'];
|
||||
$status = $data['status'];
|
||||
$domain = $data['domain'];
|
||||
$amount = $data['amount'];
|
||||
$currency = $data['currency'];
|
||||
$appcode = $data['appcode'];
|
||||
$paydate = $data['paydate'];
|
||||
$skey = $data['skey'];
|
||||
|
||||
$log_payment_transactions = new LogPaymentTransactions();
|
||||
$log_payment_transactions->insert([
|
||||
'order_id' => 0,
|
||||
'order_type' => 'order',
|
||||
'url' => 'notification',
|
||||
'request' => json_encode($data),
|
||||
'respond' => '',
|
||||
'result' => $skey.'-check-token',
|
||||
'status' => $status,
|
||||
]);
|
||||
|
||||
$is_valid = $this->validateToken($data);
|
||||
|
||||
if (!$is_valid) {
|
||||
$result = 'invalid-token'; // Invalid SKEY, ignore or log as fraud attempt
|
||||
}
|
||||
|
||||
$orders = new Orders();
|
||||
$order = $orders->where('order_so', $order_so)->first();
|
||||
$order_id = $order['id'];
|
||||
|
||||
if($status == '00' || $status == '0'){
|
||||
if(completeOrder($order_id)){
|
||||
$result = 'success-payment';
|
||||
}else{
|
||||
$result = 'failed-payment';
|
||||
}
|
||||
}else{
|
||||
$explode_order_so = explode('-', $order_so);
|
||||
$order_so_number = $explode_order_so[1] ?? 0;
|
||||
$order_so_number++;
|
||||
$order_so = $explode_order_so[0].'-'.$order_so_number;
|
||||
$orders->update($order_id, ['order_so' => $order_so]);
|
||||
$result = 'failed-payment';
|
||||
}
|
||||
|
||||
$log_payment_transactions = new LogPaymentTransactions();
|
||||
$log_payment_transactions->insert([
|
||||
'order_id' => $order_id,
|
||||
'order_type' => 'order',
|
||||
'url' => 'notification',
|
||||
'request' => json_encode($data),
|
||||
'respond' => json_encode($result),
|
||||
'result' => $skey.'-'.$is_valid,
|
||||
'status' => $status,
|
||||
]);
|
||||
|
||||
return $result;
|
||||
// if($status == '00'){
|
||||
// //send notification to fiuu server
|
||||
// send_api_request('POST', );
|
||||
// }
|
||||
}
|
||||
|
||||
public function topupNotification($data)
|
||||
{
|
||||
$tranID = $data['tranID'];
|
||||
$topup_number = $data['orderid'];
|
||||
$status = $data['status'];
|
||||
$domain = $data['domain'];
|
||||
$amount = $data['amount'];
|
||||
$currency = $data['currency'];
|
||||
$appcode = $data['appcode'];
|
||||
$paydate = $data['paydate'];
|
||||
$skey = $data['skey'];
|
||||
|
||||
$log_payment_transactions = new LogPaymentTransactions();
|
||||
$log_payment_transactions->insert([
|
||||
'order_id' => 0,
|
||||
'order_type' => 'topup',
|
||||
'url' => 'topup-notification',
|
||||
'request' => json_encode($data),
|
||||
'respond' => '',
|
||||
'result' => $skey.'-check-token',
|
||||
'status' => $status,
|
||||
]);
|
||||
|
||||
$is_valid = $this->validateToken($data);
|
||||
|
||||
if (!$is_valid) {
|
||||
$result = 'invalid-token';
|
||||
}
|
||||
|
||||
$topups = new TopupModel();
|
||||
$topup = $topups->where('topup_number', $topup_number)->first();
|
||||
|
||||
if (!$topup) {
|
||||
$result = 'topup-not-found';
|
||||
} else {
|
||||
if ($status == '00' || $status == '0') {
|
||||
$topups->update($topup['id'], ['status' => 'Success']);
|
||||
$result = 'success-topup';
|
||||
} else {
|
||||
$topups->update($topup['id'], ['status' => 'Failed']);
|
||||
$result = 'failed-topup';
|
||||
}
|
||||
}
|
||||
|
||||
$log_payment_transactions->insert([
|
||||
'order_id' => $topup['id'] ?? 0,
|
||||
'order_type' => 'topup',
|
||||
'url' => 'topup-notification',
|
||||
'request' => json_encode($data),
|
||||
'respond' => json_encode($result),
|
||||
'result' => $skey.'-'.$is_valid,
|
||||
'status' => $status,
|
||||
]);
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,522 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
<?php
|
||||
|
||||
namespace App\Libraries;
|
||||
|
||||
use App\Models\Outlet;
|
||||
use App\Models\Orders;
|
||||
use App\Models\LogLalamove;
|
||||
use App\Models\OrderDeliveries;
|
||||
|
||||
class Lalamove {
|
||||
|
||||
protected $api_url;
|
||||
protected $api_secret;
|
||||
protected $api_key;
|
||||
protected $outlet;
|
||||
|
||||
public function __construct() {
|
||||
$this->api_url = defined('LALAMOVELINK') ? LALAMOVELINK : '';
|
||||
$this->api_secret = defined('LALAMOVESECRET') ? LALAMOVESECRET : '';
|
||||
$this->api_key = defined('LALAMOVEAPIKEY') ? LALAMOVEAPIKEY : '';
|
||||
|
||||
helper("general");
|
||||
|
||||
$this->outlet = new Outlet();
|
||||
}
|
||||
|
||||
public function requestQuotation($selected_date = null, $selected_time = null, $total_amount = null, $outlet_id = null, $latitude = null, $longitude = null, $address = null) {
|
||||
//check service type
|
||||
if($total_amount > 150){
|
||||
$service_type = 'CAR';
|
||||
}else{
|
||||
$service_type = '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'];
|
||||
// echo($outlet_latitude);exit;
|
||||
$stops = [
|
||||
[
|
||||
"coordinates" => [
|
||||
"lat" => $outlet_latitude,
|
||||
"lng" => $outlet_longitude
|
||||
],
|
||||
"address" => $outlet_address
|
||||
],
|
||||
[
|
||||
"coordinates" => [
|
||||
"lat" => $latitude,
|
||||
"lng" => $longitude
|
||||
],
|
||||
"address" => $address
|
||||
]
|
||||
];
|
||||
|
||||
$request_data = [
|
||||
"data" => [
|
||||
'serviceType' => $service_type,
|
||||
'stops' => $stops,
|
||||
'language' => 'en_MY'
|
||||
]
|
||||
];
|
||||
|
||||
//handling preorder
|
||||
if($selected_date && $selected_time){
|
||||
// Convert local Y-m-d H:i:s to UTC ISO 8601 format
|
||||
$local_datetime = $selected_date . ' ' . $selected_time;
|
||||
$dt = new \DateTime($local_datetime, new \DateTimeZone('Asia/Kuala_Lumpur'));
|
||||
$request_data['data']['scheduleAt'] = $dt->format('Y-m-d\TH:i:s\Z');
|
||||
}
|
||||
|
||||
$request_body = json_encode($request_data);
|
||||
|
||||
$timestamp = time() * 1000;
|
||||
$raw_signature = $timestamp . "\r\nPOST\r\n/v3/quotations\r\n\r\n" . $request_body;
|
||||
$signature = hash_hmac('sha256', $raw_signature, $this->api_secret);
|
||||
$token = $this->api_key.':'.$timestamp.':'.$signature;
|
||||
|
||||
$headers = [
|
||||
'Authorization: hmac ' . $token,
|
||||
'Content-Type: application/json',
|
||||
'Market: MY',
|
||||
'Request-ID: 211b9d85-ips-476f-8675-b61ec923cc27'
|
||||
];
|
||||
|
||||
$response = send_api_request('POST', $this->api_url.'/quotations', $headers, $request_body);
|
||||
|
||||
$log_lalamove = new LogLalamove();
|
||||
$log_lalamove->insert([
|
||||
'url' => $this->api_url.'/quotations',
|
||||
'request' => $request_body,
|
||||
'respond' => json_encode($response),
|
||||
'quotation_id' => $response['data']['quotationId'] ?? null,
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
'updated_at' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
|
||||
return $response;
|
||||
|
||||
}
|
||||
|
||||
public function submitOrder($order_id){
|
||||
|
||||
$orders = new Orders();
|
||||
$order = $orders->select('orders.*, customer_addresses.name as recipient_name, customer_addresses.phone as recipient_phone, customer_addresses.note as recipient_note, log_lalamove.quotation_id, log_lalamove.respond, outlets.pic_name, outlets.pic_phone')
|
||||
->join('log_lalamove', 'log_lalamove.quotation_id = orders.lalamove_quot_id')
|
||||
->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['lalamove_quot_id'];
|
||||
$quotation_data = json_decode($order['respond'], true);
|
||||
|
||||
$request_data = [
|
||||
"data" => [
|
||||
'quotationId' => $quotation_id,
|
||||
"sender" => [
|
||||
"stopId" => $quotation_data['data']['stops'][0]['stopId'],
|
||||
"name" => $order['pic_name'] ?? 'US Pizza',
|
||||
"phone" => $order['pic_phone'] ?? '+60123456789'
|
||||
],
|
||||
"recipients" => [
|
||||
[
|
||||
"stopId" => $quotation_data['data']['stops'][1]['stopId'],
|
||||
"name" => $order['recipient_name'],
|
||||
"phone" => $order['recipient_phone'] ? '+6'.$order['recipient_phone'] : '+60123456789',
|
||||
"remarks" => $order['recipient_note']
|
||||
]
|
||||
],
|
||||
"isPODEnabled" => true,
|
||||
"partner" => "USPizza Delivery",
|
||||
"metadata" => [
|
||||
"order_id" => $order['id'],
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$request_body = json_encode($request_data);
|
||||
|
||||
$timestamp = time() * 1000;
|
||||
$raw_signature = $timestamp . "\r\nPOST\r\n/v3/orders\r\n\r\n" . $request_body;
|
||||
$signature = hash_hmac('sha256', $raw_signature, $this->api_secret);
|
||||
$token = $this->api_key.':'.$timestamp.':'.$signature;
|
||||
|
||||
$headers = [
|
||||
'Authorization: hmac ' . $token,
|
||||
'Content-Type: application/json',
|
||||
'Market: MY',
|
||||
'Request-ID: 211b9d85-ips-476f-8675-b61ec923cc27'
|
||||
];
|
||||
|
||||
$response = send_api_request('POST', $this->api_url.'/orders', $headers, $request_body);
|
||||
|
||||
$log_lalamove = new LogLalamove();
|
||||
$log_lalamove->insert([
|
||||
'url' => $this->api_url.'/orders',
|
||||
'request' => $request_body,
|
||||
'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;
|
||||
}
|
||||
|
||||
public function handleWebhook($data){
|
||||
//compare signature
|
||||
$signature = $data['signature'];
|
||||
$timestamp = $data['timestamp'];
|
||||
$http_verb = 'POST';
|
||||
$path = '/api/lalamove/webhook';
|
||||
$json_body = json_encode($data['data'], JSON_UNESCAPED_SLASHES);
|
||||
|
||||
$raw_signature = $timestamp . "\r\n" . $http_verb . "\r\n" . $path . "\r\n\r\n" . $json_body;
|
||||
$mysignature = hash_hmac('sha256', $raw_signature, $this->api_secret);
|
||||
|
||||
if($signature == $mysignature){
|
||||
$log_lalamove = new LogLalamove();
|
||||
$log_lalamove->insert([
|
||||
'url' => $this->api_url.'/webhook',
|
||||
'request' => json_encode($data),
|
||||
'respond' => $data['eventType'] == 'ORDER_STATUS_CHANGED' ? 'ORDER_STATUS_CHANGED ('.$data['data']['order']['orderId'].'): '.$data['data']['order']['status'] : $data['eventType'],
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
'updated_at' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
|
||||
if($data['eventType'] == 'ORDER_STATUS_CHANGED'){
|
||||
$orders = new Orders();
|
||||
$order = $orders->select('orders.*, customer_addresses.phone as recipient_phone, order_deliveries.tracking_link')
|
||||
->join('customer_addresses', 'customer_addresses.id = orders.customer_address_id AND customer_addresses.deleted_at IS NULL', 'left')
|
||||
->join('order_deliveries', 'order_deliveries.order_id = orders.id')
|
||||
->where('order_deliveries.provider_name', 'Lalamove')
|
||||
->where('order_deliveries.provider_order_id', $data['data']['order']['orderId'])
|
||||
->first();
|
||||
|
||||
if($order){
|
||||
$order_id = $order['id'];
|
||||
$lalamove_order_id = $data['data']['order']['orderId'];
|
||||
|
||||
$order_status = 'pending';
|
||||
|
||||
switch($data['data']['order']['status']){
|
||||
case 'ON_GOING':
|
||||
$order_status = 'on_the_way';
|
||||
break;
|
||||
case 'PICKED_UP':
|
||||
$order_status = 'picked_up';
|
||||
break;
|
||||
case 'COMPLETED':
|
||||
$order_status = 'completed';
|
||||
break;
|
||||
case 'CANCELLED':
|
||||
$order_status = 'cancelled';
|
||||
break;
|
||||
}
|
||||
|
||||
$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" . $order['tracking_link'];
|
||||
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);
|
||||
}
|
||||
|
||||
if($order_status == 'completed'){
|
||||
//get order details
|
||||
$order_details = $this->getOrderDetails($lalamove_order_id);
|
||||
|
||||
$pod_details = $order_details['data']['stops'][1]['POD'];
|
||||
if($pod_details['status'] == 'DELIVERED' || $pod_details['status'] == 'SIGNED'){
|
||||
$order_deliveries = new OrderDeliveries();
|
||||
$delivery_records = $order_deliveries->where('order_id', $order_id)
|
||||
->where('provider_name', 'Lalamove')
|
||||
->where('provider_order_id', $lalamove_order_id)
|
||||
->get()->getResultArray();
|
||||
|
||||
if (!empty($delivery_records)) {
|
||||
// Update delivery status to completed
|
||||
$order_deliveries->where('order_id', $order_id)
|
||||
->where('provider_name', 'Lalamove')
|
||||
->where('provider_order_id', $lalamove_order_id)
|
||||
->set(['POD_url' => $pod_details['image'], 'delivered_at' => $pod_details['deliveredAt']])
|
||||
->update();
|
||||
}
|
||||
}
|
||||
} elseif($order_status == 'cancelled'){
|
||||
// Update delivery status to cancelled
|
||||
// $order_deliveries = new OrderDeliveries();
|
||||
// $order_deliveries->where('order_id', $order_id)
|
||||
// ->where('provider_name', 'Lalamove')
|
||||
// ->where('provider_order_id', $lalamove_order_id)
|
||||
// ->set(['status' => 'cancelled', 'cancelled_at' => date('Y-m-d H:i:s')])
|
||||
// ->update();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getOrderDetails($lalamove_order_id){
|
||||
$timestamp = time() * 1000;
|
||||
$raw_signature = $timestamp . "\r\nGET\r\n/v3/orders/" . $lalamove_order_id . "\r\n\r\n";
|
||||
$signature = hash_hmac('sha256', $raw_signature, $this->api_secret);
|
||||
$token = $this->api_key.':'.$timestamp.':'.$signature;
|
||||
|
||||
$headers = [
|
||||
'Authorization: hmac ' . $token,
|
||||
'Content-Type: application/json',
|
||||
'Market: MY',
|
||||
'Request-ID: 211b9d85-ips-476f-8675-b61ec923cc27'
|
||||
];
|
||||
|
||||
$response = send_api_request('GET', $this->api_url.'/orders/'.$lalamove_order_id, $headers);
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Libraries;
|
||||
|
||||
class Wato {
|
||||
|
||||
protected $api_url;
|
||||
protected $api_key;
|
||||
protected $company;
|
||||
|
||||
public function __construct() {
|
||||
$this->api_url = defined('WATOAPILINK') ? WATOAPILINK : '';
|
||||
$this->api_key = defined('WATOAPIKEY') ? WATOAPIKEY : '';
|
||||
$this->company = defined('COMPANY') ? COMPANY : 'Company';
|
||||
helper("general");
|
||||
}
|
||||
|
||||
public function pushNotification($mobile, $message) {
|
||||
$mobile = str_replace('+', '', $mobile);
|
||||
$postFields = [
|
||||
'token' => $this->api_key,
|
||||
'to' => $mobile,
|
||||
'message' => $message
|
||||
];
|
||||
|
||||
return send_api_request('POST', $this->api_url, [], $postFields);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user