File: /var/www/javago-portal-updates/app/Http/Controllers/API/CafeOrdersController.php
<?php
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Models\GroupCoffeeRun;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use App\Models\Cafe;
use App\Models\CafeMenu;
use App\Models\CafeMenuItem;
use App\Models\CafeMenuItemSize;
use App\Models\Order;
use App\Models\User;
use App\Models\SuggestedItem;
use App\Services\CafeOrderHistoryService;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use Illuminate\Support\Facades\Hash;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Notifications\Notification;
use Kreait\Firebase\Factory;
use Kreait\Firebase\Messaging\CloudMessage;
use Kreait\Firebase\Messaging\Notification as FirebaseNotification;
use App\Models\Notification as NotificationModel;
use Illuminate\Support\Str;
class CafeOrdersController extends Controller
{
//get orders
public function getOrders(Request $request)
{
try {
$cafe = Cafe::find($request->user->id);
if (!$cafe) {
return response()->json([
'status' => 'error',
'message' => 'Cafe not found.',
], 404);
}
$orderFrom = $request->orderFrom ? Carbon::createFromFormat('Y-m-d', $request->orderFrom)->startOfDay()->timestamp : null;
$orderTo = $request->orderTo ? Carbon::createFromFormat('Y-m-d', $request->orderTo)->endOfDay()->timestamp : null;
$userId = $request->user_id ?? null;
$today = $request->today ? true : false;
$cafeId = $cafe->id;
// -----------------------------
// Fetch INDIVIDUAL Orders
// -----------------------------
$individualOrders = Order::join('users', 'users.id', 'orders.user_id')
->select('orders.id', 'orders.order_number', 'orders.status', 'orders.user_id', 'users.name', 'orders.order_placed_at', 'orders.total_amount', 'orders.order_item_array', 'orders.order_completed', 'orders.is_individual_order', 'orders.order_completed', 'orders.refunded_order_items_array', 'orders.refunded_amount', 'orders.is_full_order_cancelled', 'orders.refund_status')
->where('orders.cafe_id', $cafeId)
->where('orders.is_individual_order', 1)
->when($orderFrom, fn($q) => $q->where('orders.order_placed_at', '>=', $orderFrom))
->when($orderTo, fn($q) => $q->where('orders.order_placed_at', '<=', $orderTo))
->when($userId, fn($q) => $q->where('orders.user_id', $userId))
->when($today, fn($q) => $q->whereBetween('orders.order_placed_at', [Carbon::today()->startOfDay()->timestamp, Carbon::today()->endOfDay()->timestamp]))
->orderBy('orders.order_placed_at', 'desc')
->get();
foreach ($individualOrders as $order) {
$order->order_item_array = $this->formatOrderItems($order->order_item_array);
$order->order_type = 'individual';
}
// -----------------------------
// Fetch GROUP Coffee Run Orders
// -----------------------------
$groupCoffeeRunOrders = DB::table('group_coffee_runs')
->leftJoin('orders', 'orders.group_coffee_run_id', '=', 'group_coffee_runs.request_unique_id')
->leftJoin('users', 'users.id', '=', 'orders.user_id')
->leftJoin('users as creator_users', 'creator_users.id', '=', 'group_coffee_runs.request_created_by') // for request creator
->select(
'group_coffee_runs.request_unique_id',
'group_coffee_runs.created_at as run_created_at',
'group_coffee_runs.request_created_by',
'creator_users.name as request_created_by_name',
'orders.id',
'orders.order_number',
'orders.status',
'orders.user_id',
'users.name',
'orders.order_placed_at',
'orders.total_amount',
'orders.order_item_array',
'orders.order_completed',
'orders.is_individual_order',
'orders.order_completed',
'orders.refunded_order_items_array',
'orders.refunded_amount',
'orders.is_full_order_cancelled',
'orders.refund_status'
)
->where('group_coffee_runs.cafe_id', $cafeId)
->where('group_coffee_runs.type', 1)
->where('orders.is_timer_completed', 1)
->orderBy('orders.id', 'desc')
->get();
// Group orders by group_coffee_run_id
$groupedOrders = [];
foreach ($groupCoffeeRunOrders as $order) {
// Skip if no valid order ID (i.e., no match from LEFT JOIN)
if (!$order->id)
continue;
// Format items (avoid null item array)
$order->order_item_array = $this->formatOrderItems($order->order_item_array ?? '[]');
$order->order_type = 'group';
// Group by request_unique_id
$groupedOrders[$order->request_unique_id][$order->id] = $order;
}
// Now reset indexes so the output is clean arrays (not associative)
foreach ($groupedOrders as &$group) {
$group = array_values($group);
}
return response()->json([
'status' => 'success',
'message' => 'Orders retrieved successfully.',
'individualOrders' => $individualOrders,
'groupCoffeeRunOrders' => $groupedOrders,
], 200);
} catch (ValidationException $validationException) {
return response()->json([
'status' => 'error',
'message' => 'Validation failed.',
'errors' => $validationException->errors(),
], 422);
} catch (\Exception $e) {
return response()->json([
'status' => 'error',
'message' => 'An error occurred while retrieving orders.',
'error' => $e->getMessage(),
], 500);
}
}
private function formatOrderItems($orderItemArray)
{
if (empty($orderItemArray))
return [];
$items = is_string($orderItemArray) ? json_decode($orderItemArray, true) : $orderItemArray;
if (!is_array($items))
return [];
return array_map(function ($item) {
return [
'item_id' => $item['item_id'] ?? null,
'item_name' => $item['item_name'] ?? '',
'item_size' => $item['item_size'] ?? '',
'item_image' => $item['item_image'] ?? '',
'item_amount' => number_format((float) ($item['item_amount'] ?? 0), 2),
'item_quantity' => $item['item_quantity'] ?? 1,
'item_description' => $item['item_description'] ?? '',
'addon_sizes' => !empty($item['addon_sizes']) ? array_map(function ($addon) {
return [
'addon_name' => $addon['addon_name'] ?? '',
'addon_size_name' => $addon['addon_size_name'] ?? '',
'addon_size_price' => number_format((float) ($addon['addon_size_price'] ?? 0), 2),
];
}, $item['addon_sizes']) : [],
];
}, $items);
}
//get today orders
public function getTodayOrders(Request $request)
{
try {
$cafe = Cafe::find($request->user->id);
if (!$cafe) {
return response()->json([
'status' => 'error',
'message' => 'Cafe not found.',
], 404);
}
$userId = $request->user_id ?? null;
$today = true;
$cafeId = $cafe->id;
// -----------------------------
// Fetch INDIVIDUAL Orders
// -----------------------------
$individualOrders = Order::join('users', 'users.id', 'orders.user_id')
->select('orders.id', 'orders.order_number', 'orders.status', 'orders.user_id', 'users.name', 'orders.order_placed_at', 'orders.total_amount', 'orders.order_item_array', 'orders.order_completed', 'orders.is_individual_order', 'orders.order_completed', 'orders.estimated_arival_time', 'orders.refunded_order_items_array', 'orders.refunded_amount', 'orders.is_full_order_cancelled', 'orders.refund_status')
->where('orders.cafe_id', $cafeId)
->where('orders.is_individual_order', 1)
->when($userId, fn($q) => $q->where('orders.user_id', $userId))
->when($today, fn($q) => $q->whereBetween('orders.order_placed_at', [Carbon::today()->startOfDay()->timestamp, Carbon::today()->endOfDay()->timestamp]))
->orderBy('orders.order_placed_at', 'desc')
->get();
foreach ($individualOrders as $order) {
$order->order_item_array = $this->formatOrderItems($order->order_item_array);
$order->order_type = 'individual';
}
// -----------------------------
// Fetch GROUP Coffee Run Orders
// -----------------------------
$groupCoffeeRunOrders = DB::table('group_coffee_runs')
->leftJoin('orders', 'orders.group_coffee_run_id', '=', 'group_coffee_runs.request_unique_id')
->leftJoin('users', 'users.id', '=', 'orders.user_id')
->leftJoin('users as creator_users', 'creator_users.id', '=', 'group_coffee_runs.request_created_by') // for request creator
->leftJoin(DB::raw('(SELECT group_coffee_run_id, COUNT(*) as order_count FROM orders GROUP BY group_coffee_run_id) as order_counts'), function ($join) {
$join->on('group_coffee_runs.request_unique_id', '=', 'order_counts.group_coffee_run_id');
})
->select(
'group_coffee_runs.request_unique_id',
'group_coffee_runs.created_at as run_created_at',
'group_coffee_runs.request_created_by',
'creator_users.name as request_created_by_name',
'orders.id',
'orders.order_number',
'orders.status',
'orders.user_id',
'users.name',
'orders.order_placed_at',
'orders.total_amount',
'orders.order_item_array',
'orders.order_completed',
'orders.is_individual_order',
'orders.order_completed',
'orders.estimated_arival_time',
'orders.refunded_order_items_array',
'orders.refunded_amount',
'orders.is_full_order_cancelled',
'orders.refund_status',
'order_counts.order_count'
)
->when($today, fn($q) => $q->whereBetween('orders.order_placed_at', [Carbon::today()->startOfDay()->timestamp, Carbon::today()->endOfDay()->timestamp]))
->where('group_coffee_runs.cafe_id', $cafeId)
->where('group_coffee_runs.type', 1)
->where('orders.is_timer_completed', 1)
->orderBy('orders.id', 'desc')
->get();
// Group orders by group_coffee_run_id
$groupedOrders = [];
if ($groupCoffeeRunOrders->isEmpty()) {
$groupedOrders = new \stdClass();
}
foreach ($groupCoffeeRunOrders as $order) {
// Skip if no valid order ID (i.e., no match from LEFT JOIN)
if (!$order->id)
continue;
// Format items (avoid null item array)
$order->order_item_array = $this->formatOrderItems($order->order_item_array ?? '[]');
$order->order_type = 'group';
// Group by request_unique_id
$groupedOrders[$order->request_unique_id][$order->id] = $order;
}
// Now reset indexes so the output is clean arrays (not associative)
// foreach ($groupedOrders as &$group) {
// $group = array_values($group);
// }
foreach ($groupedOrders as $runId => &$group) {
$group = collect($group)->map(function ($order) {
return (object) [
'id' => $order->id,
'order_number' => $order->order_number,
'request_unique_id' => $order->request_unique_id,
'request_created_by_name' => $order->request_created_by_name,
'status' => $order->status,
'user_id' => $order->user_id,
'name' => $order->name,
'order_placed_at' => $order->order_placed_at,
'total_amount' => $order->total_amount,
'order_item_array' => $order->order_item_array,
'order_completed' => $order->order_completed,
'is_individual_order' => $order->is_individual_order,
'order_type' => 'group', // unified format
'estimated_arival_time' => $order->estimated_arival_time,
'refunded_order_items_array' => $order->refunded_order_items_array,
'refunded_amount' => $order->refunded_amount,
'is_full_order_cancelled' => $order->is_full_order_cancelled,
'refund_status' => $order->refund_status,
'orders' => $order->order_count,
];
})->values()->all(); // Ensures indexed array
}
return response()->json([
'status' => 'success',
'message' => 'Orders retrieved successfully.',
'individualOrders' => $individualOrders,
'groupCoffeeRunOrders' => $groupedOrders,
], 200);
} catch (ValidationException $validationException) {
return response()->json([
'status' => 'error',
'message' => 'Validation failed.',
'errors' => $validationException->errors(),
], 422);
} catch (\Exception $e) {
return response()->json([
'status' => 'error',
'message' => 'An error occurred while retrieving orders.',
'error' => $e->getMessage(),
], 500);
}
}
//get order details
public function getOrderDetailsold(Request $request)
{
try {
$validator = Validator::make($request->all(), [
'id' => 'required',
'is_individual_order' => 'required|boolean|in:0,1',
]);
if ($validator->fails()) {
throw new ValidationException($validator);
}
$cafe = Cafe::find($request->user->id);
if (!$cafe) {
return response()->json([
'status' => 'error',
'message' => 'Cafe not found.',
], 404);
}
// INDIVIDUAL ORDER
if ($request->is_individual_order == 1) {
$order = Order::with('user:id,name')->where('id', $request->id)->first();
if (!$order) {
return response()->json(['error' => 'Order not found'], 404);
}
$orderItems = json_decode($order->order_item_array, true) ?? [];
foreach ($orderItems as &$item) {
$item['addon_sizes'] = !empty($item['addon_sizes']) ? array_map(function ($addon) {
return [
'addon_name' => $addon['addon_name'],
'addon_size_name' => $addon['addon_size_name'],
'addon_size_price' => number_format((float) $addon['addon_size_price'], 2),
];
}, $item['addon_sizes']) : [];
$item['customer_name'] = $order->user->name ?? 'Unknown';
$item['order_number'] = $order->order_number ?? null;
}
return response()->json([
'status' => 'success',
'message' => 'Order details found successfully!',
'orders' => $orderItems
], 200);
}
// GROUP ORDER
else {
$orders = Order::with('user:id,name')
->rightJoin('group_coffee_runs', 'group_coffee_runs.request_unique_id', '=', 'orders.group_coffee_run_id')
->leftJoin('users as creator_users', 'creator_users.id', '=', 'group_coffee_runs.request_created_by')
->where('orders.group_coffee_run_id', $request->id)
->groupBy('orders.id')
->get([
'orders.*',
'group_coffee_runs.request_unique_id',
'creator_users.name as creator_name'
]);
if ($orders->isEmpty()) {
return response()->json(['error' => 'Group order not found'], 404);
}
$orderItemsList = [];
foreach ($orders as $order) {
$items = json_decode($order->order_item_array, true) ?? [];
foreach ($items as &$item) {
$item['addon_sizes'] = !empty($item['addon_sizes']) ? array_map(function ($addon) {
return [
'addon_name' => $addon['addon_name'],
'addon_size_name' => $addon['addon_size_name'],
'addon_size_price' => number_format((float) $addon['addon_size_price'], 2),
];
}, $item['addon_sizes']) : [];
$item['customer_name'] = $order->user->name ?? 'Unknown';
$item['creator_name'] = $order->creator_name ?? 'Unknown';
$item['request_unique_id'] = $order->request_unique_id ?? null;
}
$orderItemsList[] = $items;
}
$flattenedItems = collect($orderItemsList)->collapse()->all();
return response()->json([
'status' => 'success',
'message' => 'Group order details found successfully!',
'orders' => $flattenedItems
], 200);
}
} catch (ValidationException $validationException) {
return response()->json([
'status' => 'error',
'message' => 'Validation failed.',
'errors' => $validationException->errors(),
], 422);
} catch (\Exception $e) {
return response()->json([
'status' => 'error',
'message' => 'An error occurred while getting order details!',
'error' => $e->getMessage(),
], 500);
}
}
public function getOrderDetails(Request $request)
{
try {
$validator = Validator::make($request->all(), [
'id' => 'required',
'is_individual_order' => 'required|boolean|in:0,1',
]);
if ($validator->fails()) {
throw new ValidationException($validator);
}
$cafe = Cafe::find($request->user->id);
if (!$cafe) {
return response()->json([
'status' => 'error',
'message' => 'Cafe not found.',
], 404);
}
// INDIVIDUAL ORDER
if ($request->is_individual_order == 1) {
$order = Order::with('user:id,name')->where('id', $request->id)->first();
if (!$order) {
return response()->json(['error' => 'Order not found'], 404);
}
$processedItems = json_decode($order->processed_order_items_array, true) ?? [];
$refundedItems = json_decode($order->refunded_order_items_array, true) ?? [];
$items = [];
// if (empty($processedItems) && empty($refundedItems)) {
// $baseItems = json_decode($order->order_item_array, true) ?? [];
// foreach ($baseItems as &$item) {
// $item['status_tag'] = 'processed'; // default fallback tag
// }
// $items = $baseItems;
// } else {
// foreach ($processedItems as &$item) {
// $item['status_tag'] = 'processed';
// }
// foreach ($refundedItems as &$item) {
// $item['status_tag'] = 'refunded';
// }
// $items = array_merge($processedItems, $refundedItems);
// }
if (empty($processedItems) && empty($refundedItems)) {
$baseItems = json_decode($order->order_item_array, true) ?? [];
foreach ($baseItems as &$item) {
$item['status_tag'] = 'processed'; // default fallback tag
}
$items = $baseItems;
} else {
foreach ($processedItems as &$item) {
$item['status_tag'] = 'processed';
}
foreach ($refundedItems as &$item) {
$item = [
'item_name' => $item['name'] ?? '',
'item_amount' => $item['amount'] ?? 0,
'item_quantity' => $item['quantity'] ?? 1,
'fulfillment_uid' => $order->uid ?? '',
'state' => $order->state ?? 'COMPLETED',
'status_tag' => 'refunded',
];
}
$items = array_merge($processedItems, $refundedItems);
}
foreach ($items as &$item) {
$item['addon_sizes'] = !empty($item['addon_sizes']) ? array_map(function ($addon) {
return [
'addon_name' => $addon['addon_name'],
'addon_size_name' => $addon['addon_size_name'],
'addon_size_price' => number_format((float) $addon['addon_size_price'], 2),
];
}, $item['addon_sizes']) : [];
$item['customer_name'] = $order->user->name ?? 'Unknown';
$item['order_number'] = $order->order_number ?? null;
}
return response()->json([
'status' => 'success',
'message' => 'Order details found successfully!',
'orders' => $items
], 200);
}
// GROUP ORDER
else {
$orders = Order::with('user:id,name')
->rightJoin('group_coffee_runs', 'group_coffee_runs.request_unique_id', '=', 'orders.group_coffee_run_id')
->leftJoin('users as creator_users', 'creator_users.id', '=', 'group_coffee_runs.request_created_by')
->where('orders.group_coffee_run_id', $request->id)
->groupBy('orders.id')
->get([
'orders.*',
'group_coffee_runs.request_unique_id',
'creator_users.name as creator_name'
]);
if ($orders->isEmpty()) {
return response()->json(['error' => 'Group order not found'], 404);
}
$orderItemsList = [];
foreach ($orders as $order) {
$processedItems = json_decode($order->processed_order_items_array, true) ?? [];
$refundedItems = json_decode($order->refunded_order_items_array, true) ?? [];
$items = [];
// if (empty($processedItems) && empty($refundedItems)) {
// $baseItems = json_decode($order->order_item_array, true) ?? [];
// foreach ($baseItems as &$item) {
// $item['status_tag'] = 'processed'; // fallback default
// }
// $items = $baseItems;
// } else {
// foreach ($processedItems as &$item) {
// $item['status_tag'] = 'processed';
// }
// foreach ($refundedItems as &$item) {
// $item['status_tag'] = 'refunded';
// }
// $items = array_merge($processedItems, $refundedItems);
// }
if (empty($processedItems) && empty($refundedItems)) {
$baseItems = json_decode($order->order_item_array, true) ?? [];
foreach ($baseItems as &$item) {
$item['status_tag'] = 'processed'; // default fallback tag
}
$items = $baseItems;
} else {
foreach ($processedItems as &$item) {
$item['status_tag'] = 'processed';
}
foreach ($refundedItems as &$item) {
$item = [
'item_name' => $item['name'] ?? '',
'item_amount' => $item['amount'] ?? 0,
'item_quantity' => $item['quantity'] ?? 1,
'fulfillment_uid' => $order->uid ?? '',
'state' => $order->state ?? 'COMPLETED',
'status_tag' => 'refunded',
];
}
$items = array_merge($processedItems, $refundedItems);
}
foreach ($items as &$item) {
$item['addon_sizes'] = !empty($item['addon_sizes']) ? array_map(function ($addon) {
return [
'addon_name' => $addon['addon_name'],
'addon_size_name' => $addon['addon_size_name'],
'addon_size_price' => number_format((float) $addon['addon_size_price'], 2),
];
}, $item['addon_sizes']) : [];
$item['customer_name'] = $order->user->name ?? 'Unknown';
$item['creator_name'] = $order->creator_name ?? 'Unknown';
$item['request_unique_id'] = $order->request_unique_id ?? null;
$item['order_number'] = $order->order_number ?? null;
}
$orderItemsList[] = $items;
}
$flattenedItems = collect($orderItemsList)->collapse()->all();
return response()->json([
'status' => 'success',
'message' => 'Group order details found successfully!',
'orders' => $flattenedItems
], 200);
}
} catch (ValidationException $validationException) {
return response()->json([
'status' => 'error',
'message' => 'Validation failed.',
'errors' => $validationException->errors(),
], 422);
} catch (\Exception $e) {
return response()->json([
'status' => 'error',
'message' => 'An error occurred while getting order details!',
'error' => $e->getMessage(),
], 500);
}
}
//mark order completed
public function markOrderCompleted(Request $request)
{
try {
$validator = Validator::make($request->all(), [
'id' => 'required',
'is_individual_order' => 'required|boolean|in:0,1',
]);
if ($validator->fails()) {
throw new ValidationException($validator);
}
$messageText = 'Your order has been completed! Please collect it.';
if ($request->is_individual_order == 1) {
$order = Order::find($request->id);
if (!$order) {
return response()->json(['error' => 'Order not found'], 404);
}
$order->order_completed = 1;
// Example: Save processed items (just for example; adjust as needed)
$processedItems = $order->items ? json_decode($order->items, true) : [];
$order->processed_order_items_array = json_encode($processedItems);
$order->save();
$user = $order->user;
// if ($user) {
// $this->sendOrderCompleteNotification($order);
// }
// Mark pickup on Square
$squareResult = $this->pickupItemsOnSquare($order);
if (!$squareResult['success']) {
\Log::error("Square pickup update failed for Order ID {$order->id}", ['error' => $squareResult['message']]);
}
// Check if first order for awarding stamps
$existingOrders = Order::where('user_id', $user->id)->where('id', '!=', $order->id)->count();
// if ($existingOrders == 0) {
// $user->remaining_awarded_stamps = 2;
// $user->save();
// if (!empty($user->referral_code_used)) {
// $referrer = User::where('referral_code', $user->referral_code_used)->first();
// if ($referrer) {
// $referrer->remaining_awarded_stamps += 2;
// $referrer->save();
// }
// }
// }
} else {
$orders = Order::where('group_coffee_run_id', $request->id)->get();
if ($orders->isEmpty()) {
return response()->json(['error' => 'Group orders not found'], 404);
}
foreach ($orders as $order) {
$order->order_completed = 1;
$order->save();
$user = $order->user;
$groupCoffeeRun = GroupCoffeeRun::where('request_unique_id', $order->group_coffee_run_id)
->where('request_created_by', $user->id)
->first();
// if ($groupCoffeeRun) {
// \Log::info("Sending group coffee run completion notification", [
// 'group_coffee_run_id' => $order->group_coffee_run_id,
// 'user_id' => $user->id,
// 'order_id' => $order->id
// ]);
// $this->sendOrderCompleteNotification($order);
// }
// Mark pickup on Square
$squareResult = $this->pickupItemsOnSquare($order);
if (!$squareResult['success']) {
\Log::error("Square pickup update failed for Order ID {$order->id}", ['error' => $squareResult['message']]);
}
$existingOrders = Order::where('user_id', $user->id)->where('id', '!=', $order->id)->count();
// if ($existingOrders == 0) {
// $user->remaining_awarded_stamps = 2;
// $user->save();
// if (!empty($user->referral_code_used)) {
// $referrer = User::where('referral_code', $user->referral_code_used)->first();
// if ($referrer) {
// $referrer->remaining_awarded_stamps += 2;
// $referrer->save();
// }
// }
// }
}
// Group orders — implement similar logic as above for group if needed
// return response()->json(['error' => 'Group orders not implemented yet'], 501);
}
return response()->json([
'status' => 'success',
'message' => 'Order marked completed successfully!',
], 200);
} catch (ValidationException $validationException) {
return response()->json([
'status' => 'error',
'message' => 'Validation failed.',
'errors' => $validationException->errors(),
], 422);
} catch (\Exception $e) {
return response()->json([
'status' => 'error',
'message' => 'An error occurred while completing the order!',
'error' => $e->getMessage(),
], 500);
}
}
private function pickupItemsOnSquare($order)
{
try {
$cafe = Cafe::find($order->cafe_id);
if (!$cafe || !$cafe->square_access_token || !$order->square_order_id) {
return ['success' => false, 'message' => 'Cafe or access token or Square order ID missing'];
}
$orderResponse = Http::withToken($cafe->square_access_token)
->get("https://connect.squareup.com/v2/orders/{$order->square_order_id}");
if (!$orderResponse->successful()) {
return ['success' => false, 'message' => 'Failed to fetch Square order'];
}
$squareOrder = $orderResponse->json('order');
$version = $squareOrder['version'];
$locationId = $squareOrder['location_id'];
if (!isset($squareOrder['fulfillments'])) {
return ['success' => false, 'message' => 'No fulfillments found'];
}
foreach ($squareOrder['fulfillments'] as &$fulfillment) {
if ($fulfillment['type'] === 'PICKUP') {
// $fulfillment['state'] = "COMPLETED";
$fulfillment['state'] = "PREPARED";
}
}
$updatePayload = [
"order" => [
"version" => $version,
"location_id" => $locationId,
"fulfillments" => $squareOrder['fulfillments']
]
];
$updateResponse = Http::withToken($cafe->square_access_token)
->put("https://connect.squareup.com/v2/orders/{$order->square_order_id}", $updatePayload);
if (!$updateResponse->successful()) {
return ['success' => false, 'message' => 'Failed to update Square order state', 'errors' => $updateResponse->json()];
}
// return ['success' => true, 'message' => 'Square order marked as picked up'];
return ['success' => true, 'message' => 'Square order marked as Ready for Pickup'];
} catch (\Exception $e) {
return ['success' => false, 'message' => 'Exception: ' . $e->getMessage()];
}
}
//mark order completed
public function markOrderProcessed(Request $request)
{
try {
// Validate the incoming request
$validator = Validator::make($request->all(), [
'id' => 'required',
'is_individual_order' => 'required|boolean|in:0,1',
]);
if ($validator->fails()) {
throw new ValidationException($validator);
}
if ($request->is_individual_order == 1) {
// Individual order processing
$order = Order::find($request->id);
if (!$order) {
return response()->json(['error' => 'Order not found'], 404);
}
//from cafeMenuItems check item status where item id is in order item array
$orderItemArray = json_decode($order->order_item_array, true);
$itemIds = array_column($orderItemArray, 'item_id');
$cafeMenuItems = CafeMenuItem::whereIn('id', $itemIds)->get();
$isItemAvailable = true;
$notAvailableItems = [];
//check if any item is not available
foreach ($cafeMenuItems as $cafeMenuItem) {
if ($cafeMenuItem->status != 1) {
$isItemAvailable = false;
$notAvailableItems[] = $cafeMenuItem;
}
}
//if any item is not available then set order status to 2
if (!$isItemAvailable) {
return response()->json([
'status' => 'success',
'unavailable_items' => $notAvailableItems,
'message' => 'Order cannot be processed as some items are not available!',
], 200);
}
$order->status = 3;
$order->save();
return response()->json([
'status' => 'success',
'message' => 'Order marked processed successfully!',
], 200);
} else {
// Group coffee run orders processing
$orders = Order::where('group_coffee_run_id', $request->id)->get();
if ($orders->isEmpty()) {
return response()->json(['error' => 'Group orders not found'], 404);
}
foreach ($orders as $order) {
//from cafeMenuItems check item status where item id is in order item array
$orderItemArray = json_decode($order->order_item_array, true);
$itemIds = array_column($orderItemArray, 'item_id');
$cafeMenuItems = CafeMenuItem::whereIn('id', $itemIds)->get();
$isItemAvailable = true;
$notAvailableItems = [];
//check if any item is not available
foreach ($cafeMenuItems as $cafeMenuItem) {
if ($cafeMenuItem->status != 1) {
$isItemAvailable = false;
$notAvailableItems[] = $cafeMenuItem;
}
}
//if any item is not available then set order status to 2
if (!$isItemAvailable) {
return response()->json([
'status' => 'success',
'unavailable_items' => $notAvailableItems,
'message' => 'Order cannot be processed as some items are not available!',
], 200);
}
$order->status = 3;
$order->save();
}
return response()->json([
'status' => 'success',
'message' => 'Order marked processed successfully!',
], 200);
}
} catch (ValidationException $validationException) {
return response()->json([
'status' => 'error',
'message' => 'Validation failed.',
'errors' => $validationException->errors(),
], 422);
} catch (\Exception $e) {
return response()->json([
'status' => 'error',
'message' => 'An error occurred while processing the order!',
'error' => $e->getMessage(),
], 500);
}
}
//send order refund
public function oldrefundOrder(Request $request)
{
try {
$validator = Validator::make($request->all(), [
'id' => 'required',
'is_individual_order' => 'required|boolean|in:0,1',
'item_ids' => 'required|string',
]);
if ($validator->fails()) {
throw new ValidationException($validator);
}
$itemIds = array_map('intval', explode(',', $request->item_ids));
$orders = collect();
$initiator = null;
if ($request->is_individual_order == 1) {
$order = Order::find($request->id);
if (!$order) {
return response()->json(['error' => 'Order not found'], 404);
}
$orders->push($order);
} else {
$orders = Order::where('group_coffee_run_id', $request->id)->get();
if ($orders->isEmpty()) {
return response()->json(['error' => 'Group orders not found'], 404);
}
$group = GroupCoffeeRun::find($request->id);
$initiator = $group ? $group->initiator : null;
}
$cafeMenuItems = CafeMenuItem::whereIn('id', $itemIds)->get()->keyBy('id');
$refundAmount = 0;
$refundedItems = [];
$processedItems = [];
foreach ($orders as $order) {
$unavailableItemMessages = [];
$orderItems = json_decode($order->order_item_array, true);
foreach ($orderItems as $item) {
$itemId = (int) $item['item_id'];
if (in_array($itemId, $itemIds)) {
$amount = $item['item_amount'] * $item['item_quantity'];
$refundAmount += $amount;
$refundedItems[] = $item;
$itemName = $cafeMenuItems[$itemId]->item_name ?? 'Unknown Item';
$unavailableItemMessages[] = "Sorry, {$itemName} is unavailable. You’ve been refunded £" . number_format($amount, 2) . ". For questions, please contact the cafe directly.";
} else {
$processedItems[] = $item;
}
}
if ($refundAmount > 0) {
$user = $order->user;
if ($user && $user->fcm_token) {
$refundMessage = implode(' ', $unavailableItemMessages);
NotificationModel::create([
'sender_id' => $order->cafe_id,
'receiver_id' => $user->id,
'reference_id' => $order->id,
'notification_type' => 9,
'is_read' => 0,
'message' => $refundMessage,
'created_at' => now()->timestamp,
'updated_at' => now()->timestamp
]);
try {
$firebase = (new Factory)->withServiceAccount(
storage_path('app/java-go-380509-firebase-adminsdk-236oo-44f6190a2d.json')
);
$messaging = $firebase->createMessaging();
$message = CloudMessage::withTarget('token', $user->fcm_token)
->withNotification(FirebaseNotification::create('Refund Processed', $refundMessage))
->withData([
'order_id' => (string) $order->id,
'type' => 'refund'
]);
$messaging->send($message);
\Log::info("Refund notification sent to User ID: {$user->id}");
} catch (\Exception $e) {
\Log::error("FCM notification failed for User ID: {$user->id} - " . $e->getMessage());
}
}
if ($initiator && $initiator->id !== $user->id && $initiator->fcm_token) {
$refundMessage = implode(' ', $unavailableItemMessages);
NotificationModel::create([
'sender_id' => $order->cafe_id,
'receiver_id' => $initiator->id,
'reference_id' => $order->id,
'notification_type' => 9,
'is_read' => 0,
'message' => $refundMessage,
'created_at' => now()->timestamp,
'updated_at' => now()->timestamp
]);
try {
$firebase = (new Factory)->withServiceAccount(
storage_path('app/java-go-380509-firebase-adminsdk-236oo-44f6190a2d.json')
);
$messaging = $firebase->createMessaging();
$message = CloudMessage::withTarget('token', $initiator->fcm_token)
->withNotification(FirebaseNotification::create('Refund Processed (Your Group)', $refundMessage))
->withData([
'order_id' => (string) $order->id,
'type' => 'refund'
]);
$messaging->send($message);
\Log::info("Refund notification sent to Initiator ID: {$initiator->id}");
} catch (\Exception $e) {
\Log::error("FCM to initiator failed: " . $e->getMessage());
}
}
}
}
$order->processed_order_items_array = json_encode($processedItems);
$order->refunded_order_items_array = json_encode($refundedItems);
$order->refunded_amount = $refundAmount;
$order->save();
return response()->json([
'status' => 'success',
'message' => 'Refund processed and notifications sent.',
], 200);
} catch (ValidationException $validationException) {
return response()->json([
'status' => 'error',
'message' => 'Validation failed.',
'errors' => $validationException->errors(),
], 422);
} catch (\Exception $e) {
return response()->json([
'status' => 'error',
'message' => 'An error occurred while processing the refund!',
'error' => $e->getMessage(),
], 500);
}
}
//send order refund
public function refundOrder(Request $request)
{
try {
$validator = Validator::make($request->all(), [
'id' => 'required',
'is_individual_order' => 'required|boolean|in:0,1',
'items' => 'required|string'
]);
if ($validator->fails()) {
throw new ValidationException($validator);
}
$itemsString = $request->items; // e.g., "9823742-[12,13],928374-[15]"
$parsedItems = [];
$pattern = '/(\d+)-\[(.*?)\]/';
preg_match_all($pattern, $itemsString, $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
$orderNumber = $match[1];
$itemIdsString = $match[2];
$itemIds = array_map('intval', explode(',', $itemIdsString));
$parsedItems[] = [
'order_number' => $orderNumber,
'item_ids' => $itemIds
];
}
$squareCancellationResults = [];
foreach ($parsedItems as $itemGroup) {
$orderNumber = $itemGroup['order_number'];
$itemIds = $itemGroup['item_ids'];
\Log::info("Processing refund for order number: {$orderNumber} with item IDs: " . implode(',', $itemIds));
$order = Order::where('order_number', $orderNumber)->first();
if (!$order) {
\Log::warning("Order number {$orderNumber} not found for refund");
continue;
}
$cafeMenuItems = CafeMenuItem::whereIn('id', $itemIds)->get()->keyBy('id');
$orderItems = json_decode($order->order_item_array, true);
$remainingItems = [];
$refundedItemsThisOrder = [];
$refundAmountThisOrder = 0;
$itemsToCancel = [];
$unavailableItemMessages = [];
foreach ($orderItems as $item) {
$itemId = (int) $item['item_id'];
if (in_array($itemId, $itemIds)) {
$amount = $item['item_amount'] * $item['item_quantity'];
$refundAmountThisOrder += $amount;
$refundedItemsThisOrder[] = $item;
$itemsToCancel[] = $item;
$itemName = $cafeMenuItems[$itemId]->item_name ?? 'Unknown Item';
$unavailableItemMessages[] = "Sorry, {$itemName} is unavailable. You've been refunded £" . number_format($amount, 2) . ".";
} else {
$remainingItems[] = $item;
}
}
\Log::info("Refunding order number {$orderNumber} for items: " . implode(', ', array_column($itemsToCancel, 'item_name')));
if (!empty($itemsToCancel) && !empty($order->square_order_id)) {
$squareResult = $this->cancelSquareOrderItems($order, array_column($itemsToCancel, 'item_name'));
\Log::info("Square cancellation result for order number {$orderNumber}: " . json_encode($squareResult));
$squareCancellationResults[] = $squareResult;
}
// $order->processed_order_items_array = json_encode($remainingItems);
// $order->refunded_order_items_array = json_encode($refundedItemsThisOrder);
// $order->refunded_amount = $refundAmountThisOrder;
// if (count($remainingItems) === 0) {
// $order->refund_status = 'full_refund';
// $order->is_full_order_cancelled = 1;
// } else {
// $order->refund_status = 'partial_refund';
// $order->is_full_order_cancelled = 0;
// }
// $order->save();
}
return response()->json([
'status' => 'success',
'message' => 'Refund(s) processed successfully.',
'square_cancellation_results' => $squareCancellationResults
], 200);
} catch (ValidationException $validationException) {
return response()->json([
'status' => 'error',
'message' => 'Validation failed.',
'errors' => $validationException->errors(),
], 422);
} catch (\Exception $e) {
return response()->json([
'status' => 'error',
'message' => 'An error occurred while processing the refund!',
'error' => $e->getMessage(),
], 500);
}
}
public function prevrefundOrder(Request $request)
{
try {
$validator = Validator::make($request->all(), [
'id' => 'required',
'is_individual_order' => 'required|boolean|in:0,1',
'item_ids' => 'required|string',
]);
if ($validator->fails()) {
throw new ValidationException($validator);
}
$itemIds = array_map('intval', explode(',', $request->item_ids));
$orders = collect();
$initiator = null;
if ($request->is_individual_order == 1) {
$order = Order::find($request->id);
if (!$order) {
return response()->json(['error' => 'Order not found'], 404);
}
$orders->push($order);
} else {
$orders = Order::where('group_coffee_run_id', $request->id)->get();
if ($orders->isEmpty()) {
return response()->json(['error' => 'Group orders not found'], 404);
}
$group = GroupCoffeeRun::find($request->id);
$initiator = $group ? $group->initiator : null;
}
$cafeMenuItems = CafeMenuItem::whereIn('id', $itemIds)->get()->keyBy('id');
$refundAmount = 0;
$refundedItems = [];
$processedItems = [];
$squareCancellationResults = [];
foreach ($orders as $order) {
$unavailableItemMessages = [];
$orderItems = json_decode($order->order_item_array, true);
$itemsToCancel = [];
foreach ($orderItems as $item) {
$itemId = (int) $item['item_id'];
if (in_array($itemId, $itemIds)) {
$amount = $item['item_amount'] * $item['item_quantity'];
$refundAmount += $amount;
$refundedItems[] = $item;
$itemsToCancel[] = $item;
$itemName = $cafeMenuItems[$itemId]->item_name ?? 'Unknown Item';
$unavailableItemMessages[] = "Sorry, {$itemName} is unavailable. You've been refunded £" . number_format($amount, 2) . ". For questions, please contact the cafe directly.";
} else {
$processedItems[] = $item;
}
}
$itemIdsOnly = array_column($itemsToCancel, 'item_name');
\Log::info("Refunding order ID: {$order->id} for items: " . implode(', ', $itemIdsOnly));
// Cancel items on Square if order has square_order_id
if (!empty($order->square_order_id) && !empty($itemsToCancel)) {
$squareResult = $this->cancelSquareOrderItems($order, $itemIdsOnly);
\Log::info("Square cancellation result for order ID {$order->id}: " . json_encode($squareResult));
$squareCancellationResults[] = $squareResult;
}
// if ($refundAmount > 0) {
// $user = $order->user;
// if ($user && $user->fcm_token) {
// $refundMessage = implode(' ', $unavailableItemMessages);
// NotificationModel::create([
// 'sender_id' => $order->cafe_id,
// 'receiver_id' => $user->id,
// 'reference_id' => $order->id,
// 'notification_type' => 9,
// 'is_read' => 0,
// 'message' => $refundMessage,
// 'created_at' => now()->timestamp,
// 'updated_at' => now()->timestamp
// ]);
// try {
// $firebase = (new Factory)->withServiceAccount(
// storage_path('app/java-go-380509-firebase-adminsdk-236oo-44f6190a2d.json')
// );
// $messaging = $firebase->createMessaging();
// $message = CloudMessage::withTarget('token', $user->fcm_token)
// ->withNotification(FirebaseNotification::create('Refund Processed', $refundMessage))
// ->withData([
// 'order_id' => (string) $order->id,
// 'type' => 'refund'
// ]);
// $messaging->send($message);
// \Log::info("Refund notification sent to User ID: {$user->id}");
// } catch (\Exception $e) {
// \Log::error("FCM notification failed for User ID: {$user->id} - " . $e->getMessage());
// }
// }
// if ($initiator && $initiator->id !== $user->id && $initiator->fcm_token) {
// $refundMessage = implode(' ', $unavailableItemMessages);
// NotificationModel::create([
// 'sender_id' => $order->cafe_id,
// 'receiver_id' => $initiator->id,
// 'reference_id' => $order->id,
// 'notification_type' => 9,
// 'is_read' => 0,
// 'message' => $refundMessage,
// 'created_at' => now()->timestamp,
// 'updated_at' => now()->timestamp
// ]);
// try {
// $firebase = (new Factory)->withServiceAccount(
// storage_path('app/java-go-380509-firebase-adminsdk-236oo-44f6190a2d.json')
// );
// $messaging = $firebase->createMessaging();
// $message = CloudMessage::withTarget('token', $initiator->fcm_token)
// ->withNotification(FirebaseNotification::create('Refund Processed (Your Group)', $refundMessage))
// ->withData([
// 'order_id' => (string) $order->id,
// 'type' => 'refund'
// ]);
// $messaging->send($message);
// \Log::info("Refund notification sent to Initiator ID: {$initiator->id}");
// } catch (\Exception $e) {
// \Log::error("FCM to initiator failed: " . $e->getMessage());
// }
// }
// }
// Update order with refund information
// $order->processed_order_items_array = json_encode($processedItems);
// $order->refunded_order_items_array = json_encode($refundedItems);
// $order->refunded_amount = $refundAmount;
// // Check if all items are refunded for full cancellation
// $totalItems = count($orderItems);
// $refundedItemsCount = count($refundedItems);
// if ($refundedItemsCount === $totalItems) {
// $order->refund_status = 'full_refund';
// $order->is_full_order_cancelled = 1;
// \Log::info("Full order cancellation detected", [
// 'order_id' => $order->id,
// 'total_items' => $totalItems,
// 'refunded_items' => $refundedItemsCount
// ]);
// } else {
// $order->refund_status = 'partial_refund';
// \Log::info("Partial order cancellation detected", [
// 'order_id' => $order->id,
// 'total_items' => $totalItems,
// 'refunded_items' => $refundedItemsCount
// ]);
// }
// $order->save();
}
return response()->json([
'status' => 'success',
'message' => 'Refund processed and notifications sent.',
'square_cancellation_results' => $squareCancellationResults
], 200);
} catch (ValidationException $validationException) {
return response()->json([
'status' => 'error',
'message' => 'Validation failed.',
'errors' => $validationException->errors(),
], 422);
} catch (\Exception $e) {
return response()->json([
'status' => 'error',
'message' => 'An error occurred while processing the refund!',
'error' => $e->getMessage(),
], 500);
}
}
/**
* Cancel specific items on Square
*/
private function cancelSquareOrderItems($order, array $itemsToCancel)
{
try {
\Log::info('Starting Square order cancellation', [
'order_id' => $order->id,
'square_order_id' => $order->square_order_id,
'items_to_cancel' => $itemsToCancel
]);
$cafe = Cafe::find($order->cafe_id);
if (!$cafe || !$cafe->square_access_token) {
return ['success' => false, 'message' => 'Cafe or access token not found'];
}
// ✅ Fetch latest order
$orderResponse = Http::withToken($cafe->square_access_token)
->get("https://connect.squareup.com/v2/orders/{$order->square_order_id}");
if (!$orderResponse->successful()) {
return ['success' => false, 'message' => 'Failed to fetch Square order'];
}
$squareOrder = $orderResponse->json('order');
$lineItems = collect($squareOrder['line_items'] ?? []);
$version = $squareOrder['version'];
$totalAmountToRefund = 0;
$itemsRefunded = [];
foreach ($lineItems as $item) {
$itemName = strtolower(trim($item['name']));
if (in_array($itemName, array_map('strtolower', $itemsToCancel))) {
$amount = (int) $item['base_price_money']['amount'];
$quantity = (int) $item['quantity'];
$lineAmount = $amount * $quantity;
$totalAmountToRefund += $lineAmount;
$itemsRefunded[] = [
'name' => $item['name'],
'amount' => $lineAmount / 100,
'quantity' => $quantity,
'uid' => $item['uid'] ?? null
];
}
}
if (empty($itemsRefunded)) {
return ['success' => false, 'message' => 'No matching items found to cancel'];
}
// ✅ Refund logic
$paymentId = $squareOrder['tenders'][0]['payment_id'] ?? $order->square_payment_id ?? null;
if (!$paymentId) {
return ['success' => false, 'message' => 'Payment ID not found'];
}
$refundPayload = [
"idempotency_key" => "refund-{$order->id}-" . time(),
"amount_money" => [
"amount" => $totalAmountToRefund,
"currency" => "GBP"
],
"payment_id" => $paymentId,
"reason" => "Partial item cancel: " . implode(', ', array_column($itemsRefunded, 'name'))
];
$refundResponse = Http::withToken($cafe->square_access_token)
->post("https://connect.squareup.com/v2/refunds", $refundPayload);
if (!$refundResponse->successful()) {
return ['success' => false, 'message' => 'Failed to process refund', 'errors' => $refundResponse->json()];
}
\Log::info("Partial refund completed", [
'order_id' => $order->id,
'refund_response' => $refundResponse->json()
]);
// ✅ Check if all items were refunded
$remainingItems = $lineItems->reject(function ($item) use ($itemsToCancel) {
return in_array(strtolower(trim($item['name'])), array_map('strtolower', $itemsToCancel));
})->values();
$isFullCancel = $remainingItems->isEmpty();
if ($isFullCancel && isset($squareOrder['fulfillments'])) {
// ✅ Refetch latest order to get latest version
$latestOrderResponse = Http::withToken($cafe->square_access_token)
->get("https://connect.squareup.com/v2/orders/{$order->square_order_id}");
if ($latestOrderResponse->successful()) {
$latestSquareOrder = $latestOrderResponse->json('order');
$newVersion = $latestSquareOrder['version'];
// Build updated fulfillments with CANCELED state
$updatedFulfillments = collect($latestSquareOrder['fulfillments'])
->map(function ($fulfillment) {
return [
'uid' => $fulfillment['uid'],
'state' => 'CANCELED',
];
})->toArray();
$updatePayload = [
"order" => [
"version" => $newVersion,
"fulfillments" => $updatedFulfillments,
],
"idempotency_key" => "cancel-{$order->id}-" . time(),
];
$updateResponse = Http::withToken($cafe->square_access_token)
->put("https://connect.squareup.com/v2/orders/{$order->square_order_id}", $updatePayload);
if (!$updateResponse->successful()) {
\Log::error("Failed to update fulfillments to CANCELED", [
'order_id' => $order->id,
'errors' => $updateResponse->json(),
]);
return [
'success' => true,
'message' => 'Items refunded, but failed to update fulfillments to canceled',
'errors' => $updateResponse->json(),
];
}
} else {
\Log::error("Failed to refetch order for fulfillment cancellation", [
'order_id' => $order->id,
'errors' => $latestOrderResponse->json(),
]);
return [
'success' => true,
'message' => 'Items refunded, but failed to update fulfillments (could not refetch order)',
];
}
}
// ✅ Update local DB
$order->refunded_order_items_array = json_encode($itemsRefunded);
$order->refunded_amount = $totalAmountToRefund / 100;
$order->refund_status = $isFullCancel ? 'full_refund' : 'partial_refund';
$order->is_full_order_cancelled = $isFullCancel ? 1 : 0;
$order->save();
$this->sendFullRefundNotification($order, $order->refunded_amount);
return [
'success' => true,
'message' => $isFullCancel ? 'Full order canceled and refunded' : 'Partial refund completed',
'canceled_items' => array_column($itemsRefunded, 'name'),
'refund_amount' => $totalAmountToRefund / 100,
'refund_status' => $order->refund_status,
'is_full_cancellation' => $isFullCancel
];
} catch (\Exception $e) {
\Log::error("Exception during Square cancel", [
'order_id' => $order->id ?? 'unknown',
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return ['success' => false, 'message' => 'Exception: ' . $e->getMessage()];
}
}
private function sendFullRefundNotification($order, $totalRefund)
{
try {
$user = $order->user;
if (!$user) {
\Log::warning("User not found for full refund notification", ['order_id' => $order->id]);
return;
}
$title = 'Order Cancelled & Refunded';
$body = "Your order has been cancelled and a refund of £" . number_format($totalRefund, 2) . " has been processed.";
// Create notification in database
NotificationModel::create([
'sender_id' => $order->cafe_id,
'receiver_id' => $user->id,
'reference_id' => $order->id,
'notification_type' => 9, // refund
'is_read' => 0,
'message' => $body,
'created_at' => now()->timestamp,
'updated_at' => now()->timestamp
]);
// Send FCM notification
$this->sendFCMNotification($user, $title, $body, [
'order_id' => (string) $order->id,
'type' => 'full_refund',
'refund_amount' => $totalRefund
]);
\Log::info("📱 Full refund notification sent", [
'user_id' => $user->id,
'order_id' => $order->id,
'refund_amount' => $totalRefund
]);
} catch (\Exception $e) {
\Log::error("Full refund notification failed", [
'order_id' => $order->id,
'error' => $e->getMessage()
]);
}
}
private function sendOrderCompleteNotification($order)
{
try {
$user = $order->user;
if (!$user) {
\Log::warning("User not found for order complete notification", ['order_id' => $order->id]);
return;
}
$title = 'Order Completed';
$body = 'Your order has been completed! Please collect it.';
// Create notification in database
NotificationModel::create([
'sender_id' => $order->cafe_id,
'receiver_id' => $user->id,
'reference_id' => $order->id,
'notification_type' => 10, // example type: order completed
'is_read' => 0,
'message' => $body,
'created_at' => now()->timestamp,
'updated_at' => now()->timestamp
]);
// Send FCM notification
$this->sendFCMNotification($user, $title, $body, [
'order_id' => (string) $order->id,
'type' => 'order_completed'
]);
\Log::info("📱 Order completed notification sent", [
'user_id' => $user->id,
'order_id' => $order->id
]);
} catch (\Exception $e) {
\Log::error("Order complete notification failed", [
'order_id' => $order->id,
'error' => $e->getMessage()
]);
}
}
private function sendFCMNotification($user, $title, $body, $data = [])
{
try {
if (!$user->fcm_token) {
\Log::info("No FCM token for user", ['user_id' => $user->id]);
return;
}
$firebaseConfig = config('firebase.credentials.file') ?: storage_path('app/java-go-380509-firebase-adminsdk-236oo-44f6190a2d.json');
$messaging = (new Factory)->withServiceAccount($firebaseConfig)->createMessaging();
$messageBuilder = CloudMessage::withTarget('token', $user->fcm_token)
->withNotification(FirebaseNotification::create($title, $body));
if (!empty($data)) {
$messageBuilder = $messageBuilder->withData($data);
}
$repsomes = $messaging->send($messageBuilder);
\log::info("FCM RESPONSE", [$repsomes]);
\Log::info("FCM notification sent successfully", [
'title' => $title,
'user_id' => $user->id,
'data' => $data
]);
} catch (\Throwable $e) {
\Log::error("FCM notification failed", [
'title' => $title,
'user_id' => $user->id ?? 'unknown',
'error' => $e->getMessage()
]);
}
}
public function getCafeStats(Request $request)
{
try {
$cafe = Cafe::find($request->user->id);
if (!$cafe) {
return response()->json([
'status' => 'error',
'message' => 'Cafe not found.',
], 404);
}
$todayStart = Carbon::today()->startOfDay()->timestamp;
$todayEnd = Carbon::today()->endOfDay()->timestamp;
// All orders
$allOrders = Order::where('cafe_id', $cafe->id)->get();
$allOrderCount = $allOrders->count();
$allRevenue = $allOrders->sum(function ($order) {
return $order->total_amount - ($order->refunded_amount ?? 0);
});
// Today's orders
$todayOrders = $allOrders->filter(function ($order) use ($todayStart, $todayEnd) {
return $order->created_at >= $todayStart && $order->created_at <= $todayEnd;
});
$todayOrderCount = $todayOrders->count();
$todayRevenue = $todayOrders->sum(function ($order) {
return $order->total_amount - ($order->refunded_amount ?? 0);
});
return response()->json([
'status' => 'success',
'message' => 'Cafe stats found successfully!',
'all_order' => $allOrderCount,
'today_order' => $todayOrderCount,
'all_revenue' => number_format((float) $allRevenue, 2),
'today_revenue' => number_format((float) $todayRevenue, 2),
], 200);
} catch (\Exception $e) {
return response()->json([
'status' => 'error',
'message' => 'An error occurred while getting cafe stats!',
'error' => $e->getMessage(),
], 500);
}
}
public function markOrderPickup(Request $request)
{
try {
$validator = Validator::make($request->all(), [
'id' => 'required',
'is_individual_order' => 'required|boolean|in:0,1',
]);
if ($validator->fails()) {
throw new ValidationException($validator);
}
if ($request->is_individual_order == 1) {
$order = Order::find($request->id);
if (!$order) {
return response()->json(['error' => 'Order not found'], 404);
}
$order->order_completed = 2;
$order->save();
$user = $order->user;
// Mark pickup on Square
$squareResult = $this->markpickupItemsOnSquare($order);
if (!$squareResult['success']) {
\Log::error("Square pickup update failed for Order ID {$order->id}", ['error' => $squareResult['message']]);
}
return response()->json([
'status' => 'success',
'message' => 'Order marked Picked up successfully!',
], 200);
} else {
$orders = Order::where('group_coffee_run_id', $request->id)->get();
if ($orders->isEmpty()) {
return response()->json(['error' => 'Group orders not found'], 404);
}
foreach ($orders as $order) {
$order->order_completed = 2;
$order->save();
$user = $order->user;
// Mark pickup on Square
$squareResult = $this->markpickupItemsOnSquare($order);
if (!$squareResult['success']) {
\Log::error("Square pickup update failed for Order ID {$order->id}", ['error' => $squareResult['message']]);
}
}
return response()->json([
'status' => 'success',
'message' => 'Order marked Picked up successfully!',
], 200);
}
} catch (ValidationException $validationException) {
return response()->json([
'status' => 'error',
'message' => 'Validation failed.',
'errors' => $validationException->errors(),
], 422);
} catch (\Exception $e) {
return response()->json([
'status' => 'error',
'message' => 'An error occurred while completing the order!',
'error' => $e->getMessage(),
], 500);
}
}
private function markpickupItemsOnSquare($order)
{
try {
$cafe = Cafe::find($order->cafe_id);
if (!$cafe || !$cafe->square_access_token || !$order->square_order_id) {
return ['success' => false, 'message' => 'Cafe or access token or Square order ID missing'];
}
$orderResponse = Http::withToken($cafe->square_access_token)
->get("https://connect.squareup.com/v2/orders/{$order->square_order_id}");
if (!$orderResponse->successful()) {
return ['success' => false, 'message' => 'Failed to fetch Square order'];
}
$squareOrder = $orderResponse->json('order');
$version = $squareOrder['version'];
$locationId = $squareOrder['location_id'];
if (!isset($squareOrder['fulfillments'])) {
return ['success' => false, 'message' => 'No fulfillments found'];
}
foreach ($squareOrder['fulfillments'] as &$fulfillment) {
if ($fulfillment['type'] === 'PICKUP') {
$fulfillment['state'] = "COMPLETED";
}
}
$updatePayload = [
"order" => [
"version" => $version,
"location_id" => $locationId,
"fulfillments" => $squareOrder['fulfillments']
]
];
$updateResponse = Http::withToken($cafe->square_access_token)
->put("https://connect.squareup.com/v2/orders/{$order->square_order_id}", $updatePayload);
if (!$updateResponse->successful()) {
return ['success' => false, 'message' => 'Failed to update Square order state', 'errors' => $updateResponse->json()];
}
// return ['success' => true, 'message' => 'Square order marked as picked up'];
return ['success' => true, 'message' => 'Square order marked as Pickup'];
} catch (\Exception $e) {
return ['success' => false, 'message' => 'Exception: ' . $e->getMessage()];
}
}
}