114 lines
5.3 KiB
PHP
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');
|