cancel.cfm previously only blocked cancel if orders had StatusID >= 1 (submitted to kitchen). If submitOrder() failed after addOrderToTab() succeeded, the order stayed at StatusID 0 but was linked to the tab with ApprovalStatus='approved'. This allowed cancelling the tab free. Now checks TabOrders directly — any approved orders with non-zero subtotals block the cancel, regardless of kitchen submission status. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
63 lines
2.5 KiB
Text
63 lines
2.5 KiB
Text
<cfsetting showdebugoutput="false">
|
|
<cfsetting enablecfoutputonly="true">
|
|
<cfcontent type="application/json; charset=utf-8" reset="true">
|
|
<cfheader name="Cache-Control" value="no-store">
|
|
|
|
<cfscript>
|
|
/**
|
|
* Cancel Tab
|
|
* Only allowed if no approved orders with non-zero subtotals exist.
|
|
* Releases the Stripe hold.
|
|
*
|
|
* POST: { TabID: int, UserID: int }
|
|
*/
|
|
|
|
try {
|
|
requestData = deserializeJSON(toString(getHttpRequestData().content));
|
|
tabID = val(requestData.TabID ?: 0);
|
|
userID = val(requestData.UserID ?: 0);
|
|
|
|
if (tabID == 0) apiAbort({ "OK": false, "ERROR": "missing_TabID" });
|
|
if (userID == 0) apiAbort({ "OK": false, "ERROR": "missing_UserID" });
|
|
|
|
qTab = queryTimed("
|
|
SELECT ID, OwnerUserID, StatusID, StripePaymentIntentID, BusinessID
|
|
FROM Tabs WHERE ID = :tabID LIMIT 1
|
|
", { tabID: { value: tabID, cfsqltype: "cf_sql_integer" } });
|
|
|
|
if (qTab.recordCount == 0) apiAbort({ "OK": false, "ERROR": "tab_not_found" });
|
|
if (qTab.StatusID != 1) apiAbort({ "OK": false, "ERROR": "tab_not_open" });
|
|
if (qTab.OwnerUserID != userID) apiAbort({ "OK": false, "ERROR": "not_owner" });
|
|
|
|
// Check for any approved orders (regardless of kitchen status)
|
|
qApproved = queryTimed("
|
|
SELECT COUNT(*) AS Cnt, COALESCE(SUM(SubtotalCents), 0) AS TotalCents
|
|
FROM TabOrders
|
|
WHERE TabID = :tabID AND ApprovalStatus = 'approved'
|
|
", { tabID: { value: tabID, cfsqltype: "cf_sql_integer" } });
|
|
|
|
if (qApproved.Cnt > 0 && val(qApproved.TotalCents) > 0) {
|
|
apiAbort({ "OK": false, "ERROR": "has_orders", "MESSAGE": "Tab has approved orders. Close the tab instead of cancelling." });
|
|
}
|
|
|
|
// Cancel Stripe PI
|
|
if (len(trim(qTab.StripePaymentIntentID))) {
|
|
cfhttp(method="POST", url="https://api.stripe.com/v1/payment_intents/#qTab.StripePaymentIntentID#/cancel", result="cancelResp") {
|
|
cfhttpparam(type="header", name="Authorization", value="Bearer #application.stripeSecretKey#");
|
|
}
|
|
}
|
|
|
|
// Mark tab cancelled, release members
|
|
queryTimed("UPDATE Tabs SET StatusID = 4, ClosedOn = NOW(), PaymentStatus = 'cancelled' WHERE ID = :tabID", {
|
|
tabID: { value: tabID, cfsqltype: "cf_sql_integer" }
|
|
});
|
|
queryTimed("UPDATE TabMembers SET StatusID = 3, LeftOn = NOW() WHERE TabID = :tabID AND StatusID = 1", {
|
|
tabID: { value: tabID, cfsqltype: "cf_sql_integer" }
|
|
});
|
|
|
|
apiAbort({ "OK": true, "MESSAGE": "Tab cancelled. Card hold released." });
|
|
|
|
} catch (any e) {
|
|
apiAbort({ "OK": false, "ERROR": "server_error", "MESSAGE": e.message });
|
|
}
|
|
</cfscript>
|