- New api/tabs/ directory with 13 endpoints: open, close, cancel, get, getActive, addOrder, increaseAuth, addMember, removeMember, getPresence, approveOrder, rejectOrder, pendingOrders - New api/presence/heartbeat.cfm for beacon-based user presence tracking - New cron/expireTabs.cfm for idle tab expiry and presence cleanup - Modified submit.cfm for tab-aware order submission (skip payment, update running total) - Modified getOrCreateCart.cfm to auto-detect active tab and set TabID on new carts - Modified webhook.cfm to handle tab capture events (metadata type=tab_close) - Modified businesses/get.cfm and updateTabs.cfm with new tab config columns - Updated portal tab settings UI with auth amounts, max members, approval toggle - Added tab and presence endpoints to Application.cfm public allowlist Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
74 lines
3.2 KiB
Text
74 lines
3.2 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>
|
|
/**
|
|
* Increase Tab Authorization
|
|
* Updates the Stripe PaymentIntent amount for a higher hold.
|
|
*
|
|
* POST: { TabID: int, UserID: int, NewAuthAmount: number (dollars) }
|
|
*/
|
|
|
|
try {
|
|
requestData = deserializeJSON(toString(getHttpRequestData().content));
|
|
tabID = val(requestData.TabID ?: 0);
|
|
userID = val(requestData.UserID ?: 0);
|
|
newAuthAmount = val(requestData.NewAuthAmount ?: 0);
|
|
|
|
if (tabID == 0) apiAbort({ "OK": false, "ERROR": "missing_TabID" });
|
|
if (userID == 0) apiAbort({ "OK": false, "ERROR": "missing_UserID" });
|
|
if (newAuthAmount <= 0) apiAbort({ "OK": false, "ERROR": "missing_NewAuthAmount" });
|
|
|
|
qTab = queryTimed("
|
|
SELECT t.ID, t.OwnerUserID, t.StatusID, t.AuthAmountCents, t.StripePaymentIntentID,
|
|
b.TabMaxAuthAmount
|
|
FROM Tabs t
|
|
JOIN Businesses b ON b.ID = t.BusinessID
|
|
WHERE t.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" });
|
|
|
|
newAuthCents = round(newAuthAmount * 100);
|
|
maxCents = round(val(qTab.TabMaxAuthAmount) * 100);
|
|
|
|
if (newAuthCents <= qTab.AuthAmountCents) apiAbort({ "OK": false, "ERROR": "not_an_increase", "MESSAGE": "New amount must be higher than current authorization." });
|
|
if (maxCents > 0 && newAuthCents > maxCents) apiAbort({ "OK": false, "ERROR": "exceeds_max", "MESSAGE": "Maximum authorization is $#numberFormat(qTab.TabMaxAuthAmount, '0.00')#." });
|
|
|
|
// Update Stripe PI amount
|
|
cfhttp(method="POST", url="https://api.stripe.com/v1/payment_intents/#qTab.StripePaymentIntentID#", result="updateResp") {
|
|
cfhttpparam(type="header", name="Authorization", value="Bearer #application.stripeSecretKey#");
|
|
cfhttpparam(type="formfield", name="amount", value=newAuthCents);
|
|
}
|
|
|
|
updateData = deserializeJSON(updateResp.fileContent);
|
|
|
|
if (structKeyExists(updateData, "id") && structKeyExists(updateData, "amount") && updateData.amount == newAuthCents) {
|
|
// Success
|
|
queryTimed("UPDATE Tabs SET AuthAmountCents = :newAuth WHERE ID = :tabID", {
|
|
newAuth: { value: newAuthCents, cfsqltype: "cf_sql_integer" },
|
|
tabID: { value: tabID, cfsqltype: "cf_sql_integer" }
|
|
});
|
|
|
|
apiAbort({
|
|
"OK": true,
|
|
"AUTH_AMOUNT_CENTS": newAuthCents,
|
|
"PREVIOUS_AUTH_CENTS": qTab.AuthAmountCents
|
|
});
|
|
} else {
|
|
// Increase declined
|
|
errMsg = structKeyExists(updateData, "error") && structKeyExists(updateData.error, "message") ? updateData.error.message : "Authorization increase declined";
|
|
apiAbort({
|
|
"OK": false, "ERROR": "increase_declined", "MESSAGE": errMsg,
|
|
"CURRENT_AUTH_CENTS": qTab.AuthAmountCents
|
|
});
|
|
}
|
|
|
|
} catch (any e) {
|
|
apiAbort({ "OK": false, "ERROR": "server_error", "MESSAGE": e.message });
|
|
}
|
|
</cfscript>
|