prepare("SELECT id FROM food_orders WHERE client_order_uuid=? LIMIT 1"); $dupSt->execute([$clientUuid]); if ($existing = $dupSt->fetch()) { jsonSuccess(['order_id' => (int)$existing['id'], 'idempotent_replay' => true], 'Order already exists'); } $maxActive = (int)(getenv('FOOD_MAX_ACTIVE_ORDERS_PER_USER') ?: 3); $activeSt = $food_con->prepare( "SELECT COUNT(*) c FROM food_orders WHERE passenger_id=? AND status NOT IN ('delivered','rejected','cancelled_by_customer','cancelled_by_merchant','cancelled_system')" ); $activeSt->execute([$food_passenger_id]); if ((int)$activeSt->fetch()['c'] >= $maxActive) { jsonError("You already have $maxActive active orders — finish or cancel one first", 409); } $merchantSt = $food_con->prepare("SELECT id, status, commission_percent FROM food_merchants WHERE id=? LIMIT 1"); $merchantSt->execute([$quote['merchant_id']]); $merchant = $merchantSt->fetch(); if (!$merchant || $merchant['status'] !== 'active') jsonError('Merchant is no longer available', 409); $itemsTotal = (int)$quote['items_total']; $deliveryFee = (int)$quote['delivery_fee']; $serviceFee = (int)$quote['service_fee']; $grandTotal = (int)$quote['grand_total']; $commission = foodComputeCommission($itemsTotal, (float)$merchant['commission_percent']); if ($paymentMethod === 'wallet') { $balance = foodWalletGetBalance($food_passenger_id); if ($balance === null) jsonError('Unable to verify wallet balance. Please try again.', 503); if ($balance < foodSmallestUnitToDecimal($grandTotal)) { jsonError('Insufficient wallet balance', 402, ['current_balance' => $balance]); } } $food_con->beginTransaction(); try { $food_con->prepare( "INSERT INTO food_orders (client_order_uuid, passenger_id, merchant_id, status, items_total, delivery_fee, service_fee, discount, grand_total, commission_amount, payment_method, delivery_address, delivery_lat, delivery_lng, customer_note) VALUES (?,?,?,'pending',?,?,?,0,?,?,?,?,?,?,?)" )->execute([ $clientUuid, $food_passenger_id, $quote['merchant_id'], $itemsTotal, $deliveryFee, $serviceFee, $grandTotal, $commission, $paymentMethod, $address, $lat, $lng, $note, ]); $orderId = (int)$food_con->lastInsertId(); $insertLine = $food_con->prepare( "INSERT INTO food_order_items (order_id, item_id, name_ar_snapshot, unit_price, quantity, option_price_json, line_total) VALUES (?,?,?,?,?,?,?)" ); foreach ($quote['lines'] as $line) { $insertLine->execute([ $orderId, $line['item_id'], $line['name_ar_snapshot'], $line['unit_price'], $line['quantity'], json_encode($line['options']), $line['line_total'], ]); } $food_con->prepare( "INSERT INTO food_order_status_log (order_id, from_status, to_status, actor_type, actor_id) VALUES (?,NULL,'pending','customer',?)" )->execute([$orderId, $food_passenger_id]); $food_con->commit(); } catch (Throwable $e) { $food_con->rollBack(); appLog('[FOOD][ORDER][create] ' . $e->getMessage(), 'ERROR'); jsonError('Failed to create order', 500); } if ($paymentMethod === 'wallet') { $debited = foodWalletMove($food_passenger_id, $grandTotal, 'subtract', "food-order-{$orderId}", "Food order #{$orderId}"); if (!$debited) { // فشل الخصم بعد إنشاء السجل — نُلغي الطلب فوراً بدل ترك طلب بلا دفع food_transition_status($orderId, 'cancelled_system', 'system', 'wallet', 'Wallet debit failed'); jsonError('Wallet payment failed — order cancelled', 402); } $food_con->prepare( "INSERT INTO food_order_payments (order_id, type, amount, status) VALUES (?,'hold',?,'success')" )->execute([$orderId, $grandTotal]); } foodPushToSocket('order_status_update', [ 'order_id' => $orderId, 'status' => 'pending', 'passenger_id' => $food_passenger_id, 'merchant_id' => (int)$quote['merchant_id'], 'courier_id' => null, ]); jsonSuccess(['order_id' => $orderId, 'status' => 'pending', 'grand_total' => $grandTotal], 'Order placed');