<?php
namespace App\Http\Controllers\Delivery;
use App\Http\Controllers\Controller;
use App\Models\Address;
use App\Models\BusinessWorkingHours;
use App\Models\Cart;
use App\Models\Order;
use App\Models\Product;
use App\Models\ShippingCost;
use App\Models\Shop;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
class PedidosYaController extends Controller
{
private const TOKEN = "7602-061934-71dbfa69-dda9-4b2a-420e-19dbea651888";
private const ESTIMATE_SHIPPING_URI = "https://courier-api.pedidosya.com/v3/shippings/estimates";
private const CONFIRM_SHIPPING_URI = "https://courier-api.pedidosya.com/v3/shippings/estimates/{estimateId}/confirm";
private const HEADERS = [
"Accept: application/json",
'Authorization: 7602-061934-71dbfa69-dda9-4b2a-420e-19dbea651888',
"Content-Type: application/json",
];
public static function make(array $body)
{
return self::buildInformation($body);
}
private static function buildInformation(array $body)
{
$data = [];
if (self::checkAvailability($body)) {
$apisData = self::getEstimatedShipping($body);
foreach ($apisData['response']['deliveryOffers'] as $offer) {
if ($offer['deliveryMode'] == 'EXPRESS') {
$data = [
'available' => self::checkAvailability($body),
'dropOffPoint' => $body['buyerAddress'],
'shippingCostId' => $apisData['response']['shippingCostId'],
'delivery' => [
'estimateId' => $apisData['response']['estimateId'],
'currency' => $offer['pricing']['currency'],
'starter_price' => $offer['pricing']['total'],
'ending_price' => $offer['pricing']['total'] + ($offer['pricing']['total'] * 0.25),
'deliveryOfferId' => $offer['deliveryOfferId'],
'confirmationTimeLimit' => $offer['confirmationTimeLimit']
]
];
}
}
$data['storeAvailability'] = self::calcDeliveryAvailableTime($body);
$data['route'] = $apisData['route'];
} else {
$data['available'] = false;
}
return $data;
}
public static function getEstimatedShipping(array $body)
{
$data = self::prepareShippingData($body);
$cart = Cart::where('user_id', $body['customerId'] ?? Auth::id())
->where('product_id', $body['product']->id)
->first();
if (!$cart->shippingCosts()->where('shipping_company', 'PEDIDOS YA')->exists()) {
$response = self::makeRequest(self::ESTIMATE_SHIPPING_URI, $data);
$price = $response['deliveryOffers'][0]['pricing']['total'];
if (($body['product']->unit_price * $body['quantity']) > 2000.00) {
$secureDifference = ($body['product']->unit_price * $body['quantity']) - 2000.00;
}
$shippingCost = ShippingCost::create([
'starter_price' => $price,
'ending_price' => $price + ($price * 0.25),
'pedidosya_secure_difference' => $secureDifference ?? 0.00,
'shipping_id' => $response['estimateId'],
'delivery_offer_id' => $response['deliveryOffers'][0]['deliveryOfferId'],
'expiration_date' => $response['deliveryOffers'][0]['confirmationTimeLimit'],
'cart_id' => $cart->id,
'estimated_date' => self::calcDeliveryAvailableTime($body)['delivery_available_on'] ?? null
]);
$response['shippingCostId'] = $shippingCost->id;
} else {
$shippingCost = ShippingCost::where('cart_id', $cart->id)->where('shipping_company', 'PEDIDOS YA')->first();
if ($shippingCost->expiration_date < Carbon::now('America/Santo_Domingo')) {
$response = self::makeRequest(self::ESTIMATE_SHIPPING_URI, $data);
$price = $response['deliveryOffers'][0]['pricing']['total'];
$shippingCost->update([
'starter_price' => $price,
'ending_price' => $price + ($price * 0.25),
'pedidosya_secure_difference' => $secureDifference ?? 0.00,
'shipping_id' => $response['estimateId'],
'delivery_offer_id' => $response['deliveryOffers'][0]['deliveryOfferId'],
'expiration_date' => $response['deliveryOffers'][0]['confirmationTimeLimit'],
'estimated_date' => self::calcDeliveryAvailableTime($body)['delivery_available_on'] ?? null,
]);
$response['shippingCostId'] = $shippingCost->id;
} else {
$response = [
'estimateId' => $shippingCost->shipping_id,
'shippingCostId' => $shippingCost->id,
'deliveryOffers' => [
[
'deliveryOfferId' => $shippingCost->delivery_offer_id,
'deliveryMode' => 'EXPRESS',
'pricing' => [
'total' => $shippingCost->starter_price,
'currency' => 'DOP',
],
'confirmationTimeLimit' => $shippingCost->expiration_date
]
]
];
}
}
$route = MapsController::getRouteData($body['sellerAddress'], $body['buyerAddress']);
return [
'response' => $response,
'route' => $route
];
}
private static function prepareShippingData(array $body)
{
$user = User::find($body['customerId'] ?? Auth::id());
$data = [
"referenceId" => "Client Internal Reference",
"isTest" => false,
"notificationMail" => $body['isTransferred'] ? 'pedidosya@lapieza.do' : $user->email,
'items' => self::buildItems($body),
'waypoints' => self::buildWaypoints($body),
];
// dd($data);
return json_encode($data);
}
private static function buildItems(array $body)
{
$isSecure = !(($body['product']->unit_price * $body['quantity']) > 2000.00);
$items = [];
$weight = $body['totalWeight'] / 2.205;
$item = [
"value" => ($isSecure) ? $body['product']->unit_price : 100.00,
"description" => $body['product']->name,
"quantity" => $body['quantity'],
"volume" => $body['totalVolume'],
"weight" => $weight
];
$items[] = $item;
return $items;
}
private static function buildWaypoints(array $body)
{
$waypoints = [];
$pickup_point = [
"addressStreet" => $body['sellerAddress']->address,
"latitude" => $body['sellerAddress']->latitude,
"longitude" => $body['sellerAddress']->longitude,
"phone" => $body['sellerAddress']->phone,
"name" => $body['product']->user->name,
"city" => $body['sellerAddress']->city,
"type" => "PICK_UP"
];
$drop_off_point = [
"addressStreet" => $body['buyerAddress']->address,
"latitude" => $body['buyerAddress']->latitude,
"longitude" => $body['buyerAddress']->longitude,
"phone" => $body['buyerAddress']->phone,
//TODO: Change this to the user name
"name" => Auth::user()->name ?? 'username',
"city" => $body['buyerAddress']->city,
"type" => "DROP_OFF"
];
$waypoints[] = $pickup_point;
$waypoints[] = $drop_off_point;
return $waypoints;
}
private static function makeRequest($uri, $data)
{
$purchase = curl_init($uri);
curl_setopt($purchase, CURLOPT_URL, $uri);
curl_setopt($purchase, CURLOPT_POST, true);
curl_setopt($purchase, CURLOPT_RETURNTRANSFER, true);
curl_setopt($purchase, CURLOPT_HTTPHEADER, self::HEADERS);
curl_setopt($purchase, CURLOPT_POSTFIELDS, $data);
$info = curl_getinfo($purchase);
if (curl_errno($purchase)) {
$error_msg = curl_error($purchase);
Log::error('cURL Error: ' . $error_msg);
}
$resp = curl_exec($purchase);
curl_close($purchase);
Log::warning(json_decode($resp, true));
return json_decode($resp, true);
}
private static function calcDeliveryAvailableTime(array $body)
{
$data = [];
$estimatedDeliveryTime = Carbon::now('America/Santo_Domingo');
$shop = Shop::where('user_id', $body['product']->user_id)->first();
$workingHours = BusinessWorkingHours::where('shop_id', $shop->id)->get()->keyBy('dia_semana');
$data['delivery_now_available'] = false;
$data['delivery_now_not_available_message'] = '';
while (true) {
$dayOfWeek = $estimatedDeliveryTime->englishDayOfWeek;
if (isset($workingHours[$dayOfWeek])) {
$day = $workingHours[$dayOfWeek];
$openTime = Carbon::parse($day->hora_inicio);
$closeTime = Carbon::parse($day->hora_fin);
if ($estimatedDeliveryTime->between($openTime, $closeTime)) {
if ($estimatedDeliveryTime->diffInHours($closeTime) < 1) {
$estimatedDeliveryTime = $estimatedDeliveryTime->addDay();
$data['delivery_now_not_available_message'] = "El negocio está próximo a cerrar, realiza tu pedido el día de mañana";
continue;
} else {
$data['delivery_now_available'] = true;
break;
}
} else {
$estimatedDeliveryTime = $openTime->copy()->setDate($estimatedDeliveryTime->year, $estimatedDeliveryTime->month, $estimatedDeliveryTime->day);
if ($estimatedDeliveryTime->isPast()) {
$estimatedDeliveryTime->addDay();
}
$data['delivery_now_not_available_message'] = "El negocio está cerrado, realiza tu pedido en el horario de apertura";
break;
}
}
$estimatedDeliveryTime = $estimatedDeliveryTime->addDay();
}
$data['delivery_available_on'] = $estimatedDeliveryTime->format('Y-m-d H:i:s');
return $data;
}
public static function checkAvailability($body)
{
//Weight on Kg
$weight = $body['totalWeight'] / 2.205;
// If the product exceeds the weight or volume limits, it is not eligible for delivery.
if ($weight > 20.00 || $body['totalVolume'] > 80840.00) {
return false;
}
// If the address of the seller or the buyer is not in the available areas, it is not eligible for delivery.
if (!in_array($body['sellerAddress']->city, $body['availableAreas']) || !in_array($body['buyerAddress']->city, $body['availableAreas'])) {
return false;
}
if (!self::checkCoverage($body['buyerAddress'], $body['sellerAddress'])) {
return false;
}
return true;
}
public static function checkCoverage(Address $pickup_point, Address $dropoff_point): bool
{
$body = [
"waypoints" => [
[
"addressStreet" => $pickup_point->address,
"city" => $pickup_point->city,
"latitude" => $pickup_point->latitude,
"longitude" => $pickup_point->longitude,
"type" => "PICK_UP"
],
[
"addressStreet" => $dropoff_point->address,
"city" => $dropoff_point->city,
"latitude" => $dropoff_point->latitude,
"longitude" => $dropoff_point->longitude,
"type" => "DROP_OFF"
]
]
];
$response = self::makeRequest('https://courier-api.pedidosya.com/v3/estimates/coverage', json_encode($body));
if (!isset($response['status']) || !is_numeric($response['status'])) {
Log::error("Respuesta inesperada de la API: " . print_r($response, true));
return false;
}
$statusOk = (int)($response['status'] == 200);
if ($statusOk) {
return true;
}
return false;
}
public static function confirmShipping($estimateId, $deliveryOfferId)
{
$uri = str_replace('{estimateId}', $estimateId, self::CONFIRM_SHIPPING_URI);
$body = [
'deliveryOfferId' => $deliveryOfferId
];
return self::makeRequest($uri, json_encode($body));
}
public function makeFromAPI(Request $request)
{
$body = [
'customerId' => $request->customerId,
'product' => Product::find($request->productId),
'quantity' => $request->quantity,
'totalWeight' => $request->totalWeight,
'totalVolume' => $request->totalVolume,
'buyerAddress' => Address::find($request->buyerAddressId),
'sellerAddress' => Address::find($request->sellerAddressId),
'availableAreas' => [
"Santo Domingo",
"Santo Domingo Este",
"Santo Domingo Norte",
"Santo Domingo Oeste",
"Distrito Nacional",
"Pedro Brand",
"Los Alcarrizos",
"San Antonio de Guerra",
"Boca Chica"
],
'isTransferred' => $request->isTransferred
];
return response()->json(self::buildInformation($body));
}
public function webhook(Request $request)
{
$orders = Order::whereHas('shippingCost', function ($query) use ($request) {
$query->where('shipping_id', $request->id);
})->get();
if ($request->data['status'] == 'CONFIRMED') {
$orders->each(function ($order) {
$order->update(['delivery_status' => 'pending']);
$order->orderDetails()->each(function ($orderDetail) {
$orderDetail->update(['delivery_status' => 'pending']);
});
});
}
if ($request->data['status'] == 'IN_PROGRESS') {
$orders->each(function ($order) {
$order->update(['delivery_status' => 'pending']);
$order->orderDetails()->each(function ($orderDetail) {
$orderDetail->update(['delivery_status' => 'pending']);
});
});
}
if ($request->data['status'] == 'NEAR_PICKUP') {
$orders->each(function ($order) {
$order->update(['delivery_status' => 'pending']);
$order->orderDetails()->each(function ($orderDetail) {
$orderDetail->update(['delivery_status' => 'pending']);
});
});
}
if ($request->data['status'] == 'PICKED_UP') {
$orders->each(function ($order) {
$order->update(['delivery_status' => 'picked_up']);
$order->orderDetails()->each(function ($orderDetail) {
$orderDetail->update(['delivery_status' => 'picked_up']);
});
});
}
if ($request->data['status'] == 'NEAR_DROPOFF') {
$orders->each(function ($order) {
$order->update(['delivery_status' => 'on_the_way']);
$order->orderDetails()->each(function ($orderDetail) {
$orderDetail->update(['delivery_status' => 'on_the_way']);
});
});
}
if ($request->data['status'] == 'COMPLETED') {
$orders->each(function ($order) {
$order->update(['delivery_status' => 'delivered']);
$order->orderDetails()->each(function ($orderDetail) {
$orderDetail->update(['delivery_status' => 'delivered']);
});
});
}
return response()->json('ACCEPTED');
}
}
|