false, 'ERROR' => 'missing_TabID']); if ($userID === 0) apiAbort(['OK' => false, 'ERROR' => 'missing_UserID']); $qTab = queryOne("SELECT ID, OwnerUserID, StatusID, StripePaymentIntentID, BusinessID FROM Tabs WHERE ID = ? LIMIT 1", [$tabID]); if (!$qTab) apiAbort(['OK' => false, 'ERROR' => 'tab_not_found']); if ((int) $qTab['StatusID'] !== 1) apiAbort(['OK' => false, 'ERROR' => 'tab_not_open']); if ((int) $qTab['OwnerUserID'] !== $userID) apiAbort(['OK' => false, 'ERROR' => 'not_owner']); // Check for approved orders $qApproved = queryOne(" SELECT COUNT(*) AS Cnt, COALESCE(SUM(SubtotalCents), 0) AS TotalCents FROM TabOrders WHERE TabID = ? AND ApprovalStatus = 'approved' ", [$tabID]); if ((int) $qApproved['Cnt'] > 0 && (int) $qApproved['TotalCents'] > 0) { apiAbort(['OK' => false, 'ERROR' => 'has_orders', 'MESSAGE' => 'Tab has approved orders. Close the tab instead of cancelling.']); } // Cancel Stripe PI if (!empty(trim($qTab['StripePaymentIntentID'] ?? ''))) { stripeRequest('POST', "https://api.stripe.com/v1/payment_intents/{$qTab['StripePaymentIntentID']}/cancel"); } queryTimed("UPDATE Tabs SET StatusID = 4, ClosedOn = NOW(), PaymentStatus = 'cancelled' WHERE ID = ?", [$tabID]); queryTimed("UPDATE TabMembers SET StatusID = 3, LeftOn = NOW() WHERE TabID = ? AND StatusID = 1", [$tabID]); jsonResponse(['OK' => true, 'MESSAGE' => 'Tab cancelled. Card hold released.']); } catch (Exception $e) { jsonResponse(['OK' => false, 'ERROR' => 'server_error', 'MESSAGE' => $e->getMessage()]); }