/** * 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 = queryExecute( "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 = queryExecute(" SELECT o.OrderID, o.OrderUUID, o.OrderBusinessID, o.OrderStatusID, o.OrderTypeID, o.OrderAddedOn, o.OrderLastEditedOn, b.BusinessName, COALESCE(ot.tt_OrderTypeName, 'Unknown') as OrderTypeName FROM Orders o LEFT JOIN Businesses b ON b.BusinessID = o.OrderBusinessID LEFT JOIN tt_OrderTypes ot ON ot.tt_OrderTypeID = o.OrderTypeID WHERE o.OrderUserID = :userId AND o.OrderStatusID > 0 ORDER BY o.OrderAddedOn 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 = queryExecute(" SELECT COUNT(*) as TotalCount FROM Orders WHERE OrderUserID = :userId AND OrderStatusID > 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 = queryExecute(" SELECT COUNT(*) as ItemCount, SUM(OrderLineItemQuantity * OrderLineItemPrice) as Subtotal FROM OrderLineItems WHERE OrderLineItemOrderID = :orderId AND OrderLineItemParentOrderLineItemID = 0 AND (OrderLineItemIsDeleted = 0 OR OrderLineItemIsDeleted IS NULL) ", { orderId: { value = row.OrderID, cfsqltype = "cf_sql_integer" } }); itemCount = qItems.ItemCount ?: 0; subtotal = qItems.Subtotal ?: 0; tax = subtotal * 0.0875; total = subtotal + tax; // Get status text statusText = ""; switch (row.OrderStatusID) { 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.OrderAddedOn) && len(trim(row.OrderAddedOn))) { createdAt = dateTimeFormat(row.OrderAddedOn, "yyyy-mm-dd'T'HH:nn:ss"); } } catch (any de) { createdAt = ""; } completedAt = ""; try { if (row.OrderStatusID >= 4 && !isNull(row.OrderLastEditedOn) && len(trim(row.OrderLastEditedOn))) { completedAt = dateTimeFormat(row.OrderLastEditedOn, "yyyy-mm-dd'T'HH:nn:ss"); } } catch (any de) { completedAt = ""; } arrayAppend(orders, { "OrderID": val(row.OrderID), "OrderUUID": row.OrderUUID ?: "", "BusinessID": val(row.OrderBusinessID), "BusinessName": row.BusinessName ?: "Unknown", "OrderTotal": round(val(total) * 100) / 100, "OrderStatusID": val(row.OrderStatusID), "StatusName": statusText, "OrderTypeID": val(row.OrderTypeID), "TypeName": row.OrderTypeName ?: "Unknown", "ItemCount": val(itemCount), "CreatedAt": createdAt, "CompletedAt": completedAt }); } 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 ?: "" }); }