/** * Order History API * Returns list of completed/submitted orders for the authenticated user * * GET: ?limit=20&offset=0 */ function apiAbort(required struct payload) { writeOutput(serializeJSON(payload)); abort; } // Helper to get header value - use servlet request object (CGI scope doesn't expose custom HTTP headers in Lucee) function getHeader(name) { try { req = getPageContext().getRequest(); val = req.getHeader(arguments.name); if (!isNull(val)) return trim(val); } catch (any e) { // Fall back to CGI scope k = "HTTP_" & ucase(reReplace(arguments.name, "[^A-Za-z0-9]", "_", "all")); if (structKeyExists(cgi, k)) return trim(cgi[k]); } return ""; } // Get authenticated user - try request scope first, then do token lookup userId = 0; if (structKeyExists(request, "UserID") && isNumeric(request.UserID) && request.UserID > 0) { userId = request.UserID; } else { userToken = getHeader("X-User-Token"); if (len(userToken)) { try { qTok = queryTimed( "SELECT UserID FROM UserTokens WHERE Token = ? LIMIT 1", [ { value = userToken, cfsqltype = "cf_sql_varchar" } ], { datasource = "payfrit" } ); if (qTok.recordCount EQ 1) { userId = qTok.UserID; } } catch (any e) { /* ignore */ } } } if (userId <= 0) { apiAbort({ "OK": false, "ERROR": "not_logged_in", "MESSAGE": "Authentication required" }); } // Parse params limit = val(url.limit ?: 20); offset = val(url.offset ?: 0); if (limit < 1) limit = 20; if (limit > 100) limit = 100; if (offset < 0) offset = 0; try { // Get orders for this user (exclude carts - status 0) qOrders = queryTimed(" SELECT o.ID, o.UUID, o.BusinessID, o.StatusID, o.OrderTypeID, o.AddedOn, o.LastEditedOn, b.Name AS BusinessName, CASE o.OrderTypeID WHEN 0 THEN 'Undecided' WHEN 1 THEN 'Dine-In' WHEN 2 THEN 'Takeaway' WHEN 3 THEN 'Delivery' ELSE 'Unknown' END AS OrderTypeName FROM Orders o LEFT JOIN Businesses b ON b.ID = o.BusinessID WHERE o.UserID = :userId AND o.StatusID > 0 ORDER BY o.AddedOn DESC LIMIT :limit OFFSET :offset ", { userId: { value = userId, cfsqltype = "cf_sql_integer" }, limit: { value = limit, cfsqltype = "cf_sql_integer" }, offset: { value = offset, cfsqltype = "cf_sql_integer" } }); // Get total count qCount = queryTimed(" SELECT COUNT(*) as TotalCount FROM Orders WHERE UserID = :userId AND StatusID > 0 ", { userId: { value = userId, cfsqltype = "cf_sql_integer" } }); // Build orders array with item counts and totals orders = []; for (row in qOrders) { // Get line item count and calculate total qItems = queryTimed(" SELECT COUNT(*) as ItemCount, SUM(Quantity * Price) as Subtotal FROM OrderLineItems WHERE OrderID = :orderId AND ParentOrderLineItemID = 0 AND (IsDeleted = 0 OR IsDeleted IS NULL) ", { orderId: { value = row.ID, cfsqltype = "cf_sql_integer" } }); itemCount = val(qItems.ItemCount); subtotal = val(qItems.Subtotal); tax = subtotal * 0.0875; total = subtotal + tax; // Get status text statusText = ""; switch (row.StatusID) { case 1: statusText = "Submitted"; break; case 2: statusText = "In Progress"; break; case 3: statusText = "Ready"; break; case 4: statusText = "Completed"; break; case 5: statusText = "Cancelled"; break; default: statusText = "Unknown"; } // Safely format dates createdAt = ""; try { if (!isNull(row.AddedOn) && len(trim(row.AddedOn))) { createdAt = dateTimeFormat(row.AddedOn, "yyyy-mm-dd'T'HH:nn:ss") & "Z"; } } catch (any de) { createdAt = ""; } completedAt = ""; try { if (row.StatusID >= 4 && !isNull(row.LastEditedOn) && len(trim(row.LastEditedOn))) { completedAt = dateTimeFormat(row.LastEditedOn, "yyyy-mm-dd'T'HH:nn:ss") & "Z"; } } catch (any de) { completedAt = ""; } arrayAppend(orders, { "OrderID": val(row.ID), "OrderUUID": row.UUID ?: "", "BusinessID": val(row.BusinessID), "BusinessName": row.BusinessName ?: "Unknown", "OrderTotal": round(val(total) * 100) / 100, "OrderStatusID": val(row.StatusID), "StatusName": statusText, "OrderTypeID": val(row.OrderTypeID), "TypeName": row.OrderTypeName ?: "Unknown", "ItemCount": val(itemCount), "CreatedAt": createdAt, "CompletedAt": completedAt }); } try{logPerf(0);}catch(any e){} writeOutput(serializeJSON({ "OK": true, "ORDERS": orders, "TOTAL_COUNT": qCount.TotalCount })); } catch (any e) { apiAbort({ "OK": false, "ERROR": "server_error", "MESSAGE": "Failed to load order history", "DETAIL": e.message, "DEBUG_LINE": e.tagContext[1].line ?: 0, "DEBUG_TEMPLATE": e.tagContext[1].template ?: "" }); }