Files
2026-08-09 16:56:13 +03:00

114 lines
5.3 KiB
PHP

<?php
// food/order/create.php — إنشاء الطلب من عرض سعر موقّع + خصم فوري من المحفظة
// body: quote_token, client_order_uuid, delivery_address, delivery_lat, delivery_lng,
// payment_method (wallet|cash), customer_note
require_once __DIR__ . '/../connect_app.php';
requireFoodFields(['quote_token', 'client_order_uuid', 'delivery_address', 'delivery_lat', 'delivery_lng']);
$quoteToken = filterRequest('quote_token');
$clientUuid = filterRequest('client_order_uuid');
$address = filterRequest('delivery_address');
$lat = filterRequest('delivery_lat', 'float');
$lng = filterRequest('delivery_lng', 'float');
$paymentMethod = filterRequest('payment_method') ?: 'wallet';
$note = filterRequest('customer_note');
if (!in_array($paymentMethod, ['wallet', 'cash'], true)) jsonError('Invalid payment_method');
if (!preg_match('/^[0-9a-fA-F-]{36}$/', $clientUuid)) jsonError('client_order_uuid must be a valid UUID');
$quote = foodVerifyQuote($quoteToken);
if ((string)$quote['passenger_id'] !== $food_passenger_id) jsonError('Quote does not belong to this session', 403);
// idempotency — الضغط المزدوج أو إعادة محاولة الشبكة يرجع نفس الطلب لا طلباً جديداً
$dupSt = $food_con->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');