This repository has been archived on 2026-03-21. You can view files and clone it, but cannot push or open issues or pull requests.
payfrit-biz/api/orders/getOrCreateCart.cfm
John Mizerek 6b66d2cef8 Fix normalized DB column names across all API files
Sweep of 26 API files to use prefixed column names matching the
database schema (e.g. BusinessID not ID, BusinessName not Name,
BusinessDeliveryFlatFee not DeliveryFlatFee, ServicePointName not Name).

Files fixed: auth, beacons, businesses, menu, orders, setup, stripe,
tasks, and workers endpoints.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 16:56:41 -08:00

332 lines
9.8 KiB
Text

<cfsetting showdebugoutput="false">
<cfsetting enablecfoutputonly="true">
<cffunction name="readJsonBody" access="public" returntype="struct" output="false">
<cfset var raw = getHttpRequestData().content>
<cfif isNull(raw) OR len(trim(raw)) EQ 0>
<cfreturn {}>
</cfif>
<cftry>
<cfset var data = deserializeJSON(raw)>
<cfif isStruct(data)>
<cfreturn data>
<cfelse>
<cfreturn {}>
</cfif>
<cfcatch>
<cfreturn {}>
</cfcatch>
</cftry>
</cffunction>
<cffunction name="apiAbort" access="public" returntype="void" output="true">
<cfargument name="payload" type="struct" required="true">
<cfcontent type="application/json; charset=utf-8">
<cfoutput>#serializeJSON(arguments.payload)#</cfoutput>
<cfabort>
</cffunction>
<cffunction name="loadCartPayload" access="public" returntype="struct" output="false">
<cfargument name="OrderID" type="numeric" required="true">
<cfset var out = {}>
<cfset var qOrder = queryExecute(
"
SELECT
ID,
UUID,
UserID,
BusinessID,
BusinessDeliveryMultiplier,
OrderTypeID,
DeliveryFee,
StatusID,
AddressID,
PaymentID,
Remarks,
AddedOn,
LastEditedOn,
SubmittedOn,
ServicePointID
FROM Orders
WHERE ID = ?
LIMIT 1
",
[ { value = arguments.OrderID, cfsqltype = "cf_sql_integer" } ],
{ datasource = "payfrit" }
)>
<cfif qOrder.recordCount EQ 0>
<cfreturn { "OK": false, "ERROR": "not_found", "MESSAGE": "Order not found", "DETAIL": "" }>
</cfif>
<!--- Get business delivery fee for display in cart --->
<cfset var qBusiness = queryExecute(
"SELECT BusinessDeliveryFlatFee FROM Businesses WHERE BusinessID = ? LIMIT 1",
[ { value = qOrder.BusinessID, cfsqltype = "cf_sql_integer" } ],
{ datasource = "payfrit" }
)>
<cfset var businessDeliveryFee = qBusiness.recordCount GT 0 ? qBusiness.BusinessDeliveryFlatFee : 0>
<cfset out.Order = {
"OrderID": val(qOrder.ID),
"UUID": qOrder.UUID ?: "",
"UserID": val(qOrder.UserID),
"BusinessID": val(qOrder.BusinessID),
"DeliveryMultiplier": val(qOrder.BusinessDeliveryMultiplier),
"OrderTypeID": val(qOrder.OrderTypeID),
"DeliveryFee": val(qOrder.DeliveryFee),
"BusinessDeliveryFee": val(businessDeliveryFee),
"StatusID": val(qOrder.StatusID),
"AddressID": val(qOrder.AddressID),
"PaymentID": val(qOrder.PaymentID),
"Remarks": qOrder.Remarks ?: "",
"AddedOn": qOrder.AddedOn,
"LastEditedOn": qOrder.LastEditedOn,
"SubmittedOn": qOrder.SubmittedOn,
"ServicePointID": val(qOrder.ServicePointID)
}>
<cfset var qLI = queryExecute(
"
SELECT
oli.ID,
oli.ParentOrderLineItemID,
oli.OrderID,
oli.ItemID,
oli.StatusID,
oli.Price,
oli.Quantity,
oli.Remark,
oli.IsDeleted,
oli.AddedOn,
i.Name,
i.ParentItemID,
i.IsCheckedByDefault,
parent.Name AS ItemParentName
FROM OrderLineItems oli
INNER JOIN Items i ON i.ID = oli.ItemID
LEFT JOIN Items parent ON parent.ID = i.ParentItemID
WHERE oli.OrderID = ?
ORDER BY oli.ID
",
[ { value = arguments.OrderID, cfsqltype = "cf_sql_integer" } ],
{ datasource = "payfrit" }
)>
<cfset var rows = []>
<cfloop query="qLI">
<cfset arrayAppend(rows, {
"OrderLineItemID": val(qLI.ID),
"ParentOrderLineItemID": val(qLI.ParentOrderLineItemID),
"OrderID": val(qLI.OrderID),
"ItemID": val(qLI.ItemID),
"StatusID": val(qLI.StatusID),
"Price": val(qLI.Price),
"Quantity": val(qLI.Quantity),
"Remark": qLI.Remark ?: "",
"IsDeleted": val(qLI.IsDeleted),
"AddedOn": qLI.AddedOn,
"Name": qLI.Name ?: "",
"ParentItemID": val(qLI.ParentItemID),
"ItemParentName": qLI.ItemParentName ?: "",
"IsCheckedByDefault": val(qLI.IsCheckedByDefault)
})>
</cfloop>
<cfset out.OrderLineItems = rows>
<cfset out.OK = true>
<cfset out.ERROR = "">
<cfreturn out>
</cffunction>
<cfset data = readJsonBody()>
<cfset BusinessID = val( structKeyExists(data,"BusinessID") ? data.BusinessID : 0 )>
<cfset ServicePointID = val( structKeyExists(data,"ServicePointID") ? data.ServicePointID : 0 )>
<cfset OrderTypeID = val( structKeyExists(data,"OrderTypeID") ? data.OrderTypeID : 0 )>
<cfset UserID = val( structKeyExists(data,"UserID") ? data.UserID : 0 )>
<!--- OrderTypeID: 0=undecided, 1=dine-in, 2=takeaway, 3=delivery --->
<cfif BusinessID LTE 0 OR UserID LTE 0>
<cfset apiAbort({
"OK": false,
"ERROR": "missing_params",
"MESSAGE": "BusinessID and UserID are required.",
"DETAIL": ""
})>
</cfif>
<!--- OrderTypeID can be 0 (undecided) for delivery/takeaway flow, or 1 for dine-in --->
<cfif OrderTypeID LT 0 OR OrderTypeID GT 3>
<cfset apiAbort({
"OK": false,
"ERROR": "invalid_order_type",
"MESSAGE": "OrderTypeID must be 0-3 (0=undecided, 1=dine-in, 2=takeaway, 3=delivery).",
"DETAIL": ""
})>
</cfif>
<cftry>
<!--- Find existing cart (StatusID=0 assumed cart) --->
<!--- Look for any active cart for this user/business - order type can be changed later --->
<cfset qFind = queryExecute(
"
SELECT ID, OrderTypeID
FROM Orders
WHERE UserID = ?
AND BusinessID = ?
AND StatusID = 0
ORDER BY ID DESC
LIMIT 1
",
[
{ value = UserID, cfsqltype = "cf_sql_integer" },
{ value = BusinessID, cfsqltype = "cf_sql_integer" }
],
{ datasource = "payfrit" }
)>
<cfif qFind.recordCount GT 0>
<!--- Always update the service point to match the current table/beacon --->
<cfif ServicePointID GT 0>
<cfset queryExecute(
"UPDATE Orders SET ServicePointID = ?, LastEditedOn = ? WHERE ID = ?",
[
{ value = ServicePointID, cfsqltype = "cf_sql_integer" },
{ value = now(), cfsqltype = "cf_sql_timestamp" },
{ value = qFind.ID, cfsqltype = "cf_sql_integer" }
],
{ datasource = "payfrit" }
)>
</cfif>
<!--- Check if cart order type differs from requested and cart is empty --->
<!--- If so, update the cart's order type to match the new flow --->
<cfif qFind.OrderTypeID NEQ OrderTypeID>
<cfset qLineItems = queryExecute(
"SELECT COUNT(*) AS ItemCount FROM OrderLineItems WHERE OrderID = ? AND IsDeleted = 0",
[ { value = qFind.ID, cfsqltype = "cf_sql_integer" } ],
{ datasource = "payfrit" }
)>
<!--- Only update order type if cart is empty (allows switching flows) --->
<cfif qLineItems.ItemCount EQ 0>
<cfset queryExecute(
"UPDATE Orders SET OrderTypeID = ?, LastEditedOn = ? WHERE ID = ?",
[
{ value = OrderTypeID, cfsqltype = "cf_sql_integer" },
{ value = now(), cfsqltype = "cf_sql_timestamp" },
{ value = qFind.ID, cfsqltype = "cf_sql_integer" }
],
{ datasource = "payfrit" }
)>
</cfif>
</cfif>
<cfset payload = loadCartPayload(qFind.ID)>
<cfset apiAbort(payload)>
</cfif>
<!--- Create new cart order --->
<cfset qBiz = queryExecute(
"
SELECT BusinessDeliveryMultiplier, BusinessDeliveryFlatFee
FROM Businesses
WHERE BusinessID = ?
LIMIT 1
",
[ { value = BusinessID, cfsqltype = "cf_sql_integer" } ],
{ datasource = "payfrit" }
)>
<cfif qBiz.recordCount EQ 0>
<cfset apiAbort({ "OK": false, "ERROR": "bad_business", "MESSAGE": "Business not found", "DETAIL": "" })>
</cfif>
<cfset nowDt = now()>
<cfset newUUID = createUUID()>
<!--- Calculate delivery fee: only for delivery orders (OrderTypeID = 3)
OrderTypeID: 0=undecided, 1=dine-in, 2=takeaway, 3=delivery
Only delivery (3) should have delivery fee.
Note: For undecided orders (0), fee is set later via setOrderType.cfm --->
<cfset deliveryFee = (OrderTypeID EQ 3) ? qBiz.BusinessDeliveryFlatFee : 0>
<!--- Generate new OrderID (table is not auto-inc in SSOT) --->
<cfset qNext = queryExecute(
"SELECT IFNULL(MAX(ID),0) + 1 AS NextID FROM Orders",
[],
{ datasource = "payfrit" }
)>
<cfset NewOrderID = qNext.NextID>
<cfset queryExecute(
"
INSERT INTO Orders (
ID,
UUID,
UserID,
BusinessID,
BusinessDeliveryMultiplier,
OrderTypeID,
DeliveryFee,
StatusID,
AddressID,
PaymentID,
Remarks,
AddedOn,
LastEditedOn,
SubmittedOn,
ServicePointID
) VALUES (
?,
?,
?,
?,
?,
?,
?,
0,
NULL,
NULL,
NULL,
?,
?,
NULL,
?
)
",
[
{ value = NewOrderID, cfsqltype = "cf_sql_integer" },
{ value = newUUID, cfsqltype = "cf_sql_varchar" },
{ value = UserID, cfsqltype = "cf_sql_integer" },
{ value = BusinessID, cfsqltype = "cf_sql_integer" },
{ value = qBiz.BusinessDeliveryMultiplier, cfsqltype = "cf_sql_decimal" },
{ value = OrderTypeID, cfsqltype = "cf_sql_integer" },
{ value = deliveryFee, cfsqltype = "cf_sql_decimal" },
{ value = nowDt, cfsqltype = "cf_sql_timestamp" },
{ value = nowDt, cfsqltype = "cf_sql_timestamp" },
{ value = ServicePointID, cfsqltype = "cf_sql_integer" }
],
{ datasource = "payfrit" }
)>
<!--- Per your rule: OrderID is determined by selecting highest after creation --->
<cfset qLatest = queryExecute(
"SELECT MAX(ID) AS NextID FROM Orders",
[],
{ datasource = "payfrit" }
)>
<cfset FinalOrderID = qLatest.NextID>
<cfset payload = loadCartPayload(FinalOrderID)>
<cfset apiAbort(payload)>
<cfcatch>
<cfset apiAbort({
"OK": false,
"ERROR": "server_error",
"MESSAGE": "DB error creating cart",
"DETAIL": cfcatch.message
})>
</cfcatch>
</cftry>