prepare( "SELECT id, min_order_amount, is_open_override, working_hours FROM food_merchants WHERE id=? AND status='active' LIMIT 1" ); $merchantSt->execute([$merchantId]); $merchant = $merchantSt->fetch(); if (!$merchant) jsonError('Merchant not found', 404); if (!foodIsMerchantOpen($merchant)) jsonError('Merchant is currently closed', 409); $lines = []; $itemsTotal = 0; foreach ($itemsRaw as $line) { $itemId = (int)($line['item_id'] ?? 0); $quantity = max(1, (int)($line['quantity'] ?? 1)); if (!$itemId) jsonError('Invalid item in cart'); $itemSt = $food_con->prepare( "SELECT id, merchant_id, name_ar, price, is_available FROM food_menu_items WHERE id=? LIMIT 1" ); $itemSt->execute([$itemId]); $item = $itemSt->fetch(); if (!$item || (int)$item['merchant_id'] !== $merchantId) jsonError("Item $itemId does not belong to this merchant", 400); if (!$item['is_available']) jsonError("{$item['name_ar']} is currently unavailable", 409); $optionsSt = $food_con->prepare("SELECT id, choices, is_required, max_select FROM food_item_options WHERE item_id=?"); $optionsSt->execute([$itemId]); $optionGroups = $optionsSt->fetchAll(); $chosenOptionsOut = []; $optionsTotalPerUnit = 0; $requestedGroups = $line['options'] ?? []; foreach ($optionGroups as $group) { $choices = json_decode($group['choices'], true) ?: []; $choiceById = array_column($choices, null, 'id'); $requested = null; foreach ($requestedGroups as $rg) { if ((int)($rg['option_group_id'] ?? 0) === (int)$group['id']) { $requested = $rg; break; } } $choiceIds = $requested['choice_ids'] ?? []; if ($group['is_required'] && empty($choiceIds)) { jsonError("Required option group missing for item {$item['name_ar']}", 400); } if (count($choiceIds) > (int)$group['max_select']) { jsonError("Too many choices selected for item {$item['name_ar']}", 400); } foreach ($choiceIds as $cid) { if (!isset($choiceById[$cid])) jsonError("Invalid option choice for item {$item['name_ar']}", 400); $optionsTotalPerUnit += (int)$choiceById[$cid]['price']; $chosenOptionsOut[] = ['group_id' => (int)$group['id'], 'choice_id' => $cid, 'price' => (int)$choiceById[$cid]['price']]; } } $unitPrice = (int)$item['price'] + $optionsTotalPerUnit; $lineTotal = $unitPrice * $quantity; $itemsTotal += $lineTotal; $lines[] = [ 'item_id' => $itemId, 'name_ar_snapshot' => $item['name_ar'], 'unit_price' => $unitPrice, 'quantity' => $quantity, 'options' => $chosenOptionsOut, 'line_total' => $lineTotal, ]; } if ($itemsTotal < (int)$merchant['min_order_amount']) { jsonError('Order does not meet minimum order amount', 409, ['min_order_amount' => (int)$merchant['min_order_amount']]); } // رسم التوصيل — ثابت من env حتى يُربط بمحرك التسعير القائم في مرحلة لاحقة $deliveryFee = (int)(getenv('FOOD_DELIVERY_BASE_FEE') ?: 1000); $serviceFee = 0; $grandTotal = $itemsTotal + $deliveryFee + $serviceFee; $quote = [ 'merchant_id' => $merchantId, 'passenger_id' => $food_passenger_id, 'lines' => $lines, 'items_total' => $itemsTotal, 'delivery_fee' => $deliveryFee, 'service_fee' => $serviceFee, 'grand_total' => $grandTotal, ]; $token = foodSignQuote($quote); jsonSuccess([ 'quote_token' => $token, 'items_total' => $itemsTotal, 'delivery_fee' => $deliveryFee, 'service_fee' => $serviceFee, 'grand_total' => $grandTotal, 'lines' => $lines, 'expires_in' => 600, ]);