From 6b66d2cef8f5df49d1aa888858371b48366b68e6 Mon Sep 17 00:00:00 2001 From: John Mizerek Date: Sat, 31 Jan 2026 16:56:41 -0800 Subject: [PATCH] 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 --- api/auth/verifyLoginOTP.cfm | 43 +++++---------- api/auth/verifyOTP.cfm | 43 +++++---------- api/beacons/getBusinessFromBeacon.cfm | 38 ++++++------- api/businesses/get.cfm | 77 +++++++++++++-------------- api/businesses/getChildren.cfm | 10 ++-- api/businesses/list.cfm | 32 +++++------ api/businesses/setHiring.cfm | 2 +- api/businesses/update.cfm | 20 +++---- api/businesses/updateHours.cfm | 4 +- api/menu/clearOrders.cfm | 4 +- api/menu/getForBuilder.cfm | 6 +-- api/menu/items.cfm | 6 +-- api/orders/getActiveCart.cfm | 16 +++--- api/orders/getCart.cfm | 6 +-- api/orders/getOrCreateCart.cfm | 12 ++--- api/orders/history.cfm | 6 +-- api/orders/setLineItem.cfm | 4 +- api/orders/setOrderType.cfm | 8 +-- api/setup/saveWizard.cfm | 10 ++-- api/stripe/createPaymentIntent.cfm | 16 +++--- api/stripe/onboard.cfm | 13 +++-- api/stripe/status.cfm | 14 ++--- api/stripe/webhook.cfm | 45 +++++++++++++--- api/tasks/complete.cfm | 4 +- api/workers/earlyUnlock.cfm | 5 +- api/workers/onboardingLink.cfm | 5 +- 26 files changed, 222 insertions(+), 227 deletions(-) diff --git a/api/auth/verifyLoginOTP.cfm b/api/auth/verifyLoginOTP.cfm index c6f83c4..128a1f8 100644 --- a/api/auth/verifyLoginOTP.cfm +++ b/api/auth/verifyLoginOTP.cfm @@ -37,36 +37,19 @@ try { apiAbort({ "OK": false, "ERROR": "missing_fields", "MESSAGE": "UUID and OTP are required" }); } - // Check for magic OTP bypass (for App Store review) - isMagicOTP = structKeyExists(application, "MAGIC_OTP_ENABLED") - && application.MAGIC_OTP_ENABLED - && structKeyExists(application, "MAGIC_OTP_CODE") - && otp == application.MAGIC_OTP_CODE; - - // Find verified user with matching UUID and OTP (or magic OTP) - if (isMagicOTP) { - qUser = queryExecute(" - SELECT ID, FirstName, LastName - FROM Users - WHERE UUID = :uuid - AND IsContactVerified = 1 - LIMIT 1 - ", { - uuid: { value: userUUID, cfsqltype: "cf_sql_varchar" } - }, { datasource: "payfrit" }); - } else { - qUser = queryExecute(" - SELECT ID, FirstName, LastName - FROM Users - WHERE UUID = :uuid - AND MobileVerifyCode = :otp - AND IsContactVerified = 1 - LIMIT 1 - ", { - uuid: { value: userUUID, cfsqltype: "cf_sql_varchar" }, - otp: { value: otp, cfsqltype: "cf_sql_varchar" } - }, { datasource: "payfrit" }); - } + // Find verified user with matching UUID and OTP + // Magic OTP only bypasses Twilio SMS (in loginOTP.cfm), not OTP verification + qUser = queryExecute(" + SELECT ID, FirstName, LastName + FROM Users + WHERE UUID = :uuid + AND MobileVerifyCode = :otp + AND IsContactVerified = 1 + LIMIT 1 + ", { + uuid: { value: userUUID, cfsqltype: "cf_sql_varchar" }, + otp: { value: otp, cfsqltype: "cf_sql_varchar" } + }, { datasource: "payfrit" }); if (qUser.recordCount == 0) { // Check if UUID exists but OTP is wrong diff --git a/api/auth/verifyOTP.cfm b/api/auth/verifyOTP.cfm index 8e11724..35a6af4 100644 --- a/api/auth/verifyOTP.cfm +++ b/api/auth/verifyOTP.cfm @@ -40,36 +40,19 @@ try { apiAbort({ "OK": false, "ERROR": "missing_fields", "MESSAGE": "UUID and OTP are required" }); } - // Check for magic OTP bypass (for App Store review) - isMagicOTP = structKeyExists(application, "MAGIC_OTP_ENABLED") - && application.MAGIC_OTP_ENABLED - && structKeyExists(application, "MAGIC_OTP_CODE") - && otp == application.MAGIC_OTP_CODE; - - // Find unverified user with matching UUID and OTP (or magic OTP) - if (isMagicOTP) { - qUser = queryExecute(" - SELECT ID, FirstName, LastName, EmailAddress, IsEmailVerified - FROM Users - WHERE UUID = :uuid - AND IsContactVerified = 0 - LIMIT 1 - ", { - uuid: { value: userUUID, cfsqltype: "cf_sql_varchar" } - }, { datasource: "payfrit" }); - } else { - qUser = queryExecute(" - SELECT ID, FirstName, LastName, EmailAddress, IsEmailVerified - FROM Users - WHERE UUID = :uuid - AND MobileVerifyCode = :otp - AND IsContactVerified = 0 - LIMIT 1 - ", { - uuid: { value: userUUID, cfsqltype: "cf_sql_varchar" }, - otp: { value: otp, cfsqltype: "cf_sql_varchar" } - }, { datasource: "payfrit" }); - } + // Find unverified user with matching UUID and OTP + // Magic OTP only bypasses Twilio SMS (in sendOTP.cfm), not OTP verification + qUser = queryExecute(" + SELECT ID, FirstName, LastName, EmailAddress, IsEmailVerified + FROM Users + WHERE UUID = :uuid + AND MobileVerifyCode = :otp + AND IsContactVerified = 0 + LIMIT 1 + ", { + uuid: { value: userUUID, cfsqltype: "cf_sql_varchar" }, + otp: { value: otp, cfsqltype: "cf_sql_varchar" } + }, { datasource: "payfrit" }); if (qUser.recordCount == 0) { // Check if UUID exists but OTP is wrong diff --git a/api/beacons/getBusinessFromBeacon.cfm b/api/beacons/getBusinessFromBeacon.cfm index 2a08d3a..6c12784 100644 --- a/api/beacons/getBusinessFromBeacon.cfm +++ b/api/beacons/getBusinessFromBeacon.cfm @@ -50,11 +50,11 @@ beaconId = int(data.BeaconID); SELECT sp.BusinessID, sp.ID AS ServicePointID, - biz.Name AS BusinessName, - biz.ParentBusinessID, + biz.BusinessName AS BusinessName, + biz.BusinessParentBusinessID, sp.Name AS ServicePointName FROM ServicePoints sp - INNER JOIN Businesses biz ON biz.ID = sp.BusinessID + INNER JOIN Businesses biz ON biz.BusinessID = sp.BusinessID WHERE sp.BeaconID = AND sp.IsActive = 1 @@ -63,18 +63,18 @@ beaconId = int(data.BeaconID); SELECT lt.BusinessID, 0 AS ServicePointID, - biz.Name AS BusinessName, - biz.ParentBusinessID, + biz.BusinessName AS BusinessName, + biz.BusinessParentBusinessID, '' AS ServicePointName FROM lt_BeaconsID_BusinessesID lt - INNER JOIN Businesses biz ON biz.ID = lt.BusinessID + INNER JOIN Businesses biz ON biz.BusinessID = lt.BusinessID WHERE lt.BeaconID = AND lt.BusinessID NOT IN ( SELECT sp2.BusinessID FROM ServicePoints sp2 WHERE sp2.BeaconID = AND sp2.IsActive = 1 ) - ORDER BY ParentBusinessID IS NULL DESC, BusinessName ASC + ORDER BY BusinessParentBusinessID IS NULL DESC, BusinessName ASC @@ -83,7 +83,7 @@ beaconId = int(data.BeaconID); SELECT COUNT(*) as cnt FROM Businesses - WHERE ParentBusinessID = + WHERE BusinessParentBusinessID = @@ -98,24 +98,24 @@ beaconId = int(data.BeaconID); - SELECT Name, HeaderImageExtension + SELECT BusinessName, BusinessHeaderImageExtension FROM Businesses - WHERE ID = + WHERE BusinessID = SELECT - ID, - Name, - ParentBusinessID, - HeaderImageExtension + BusinessID, + BusinessName, + BusinessParentBusinessID, + BusinessHeaderImageExtension FROM Businesses - WHERE ParentBusinessID = + WHERE BusinessParentBusinessID = ORDER BY Name ASC diff --git a/api/businesses/get.cfm b/api/businesses/get.cfm index e94215a..a2236d0 100644 --- a/api/businesses/get.cfm +++ b/api/businesses/get.cfm @@ -35,17 +35,17 @@ try { // Get business details q = queryExecute(" SELECT - ID, - Name, - Phone, - StripeAccountID, - StripeOnboardingComplete, - IsHiring, - HeaderImageExtension, - TaxRate, - BrandColor + BusinessID, + BusinessName, + BusinessPhone, + BusinessStripeAccountID, + BusinessStripeOnboardingComplete, + BusinessIsHiring, + BusinessHeaderImageExtension, + BusinessTaxRate, + BusinessBrandColor FROM Businesses - WHERE ID = :businessID + WHERE BusinessID = :businessID ", { businessID: businessID }, { datasource: "payfrit" }); if (q.recordCount == 0) { @@ -54,13 +54,13 @@ try { abort; } - // Get address from Addresses table (either linked via BusinessID or via Businesses.AddressID) + // Get address from Addresses table (either linked via AddressBusinessID or via Businesses.BusinessAddressID) qAddr = queryExecute(" - SELECT a.Line1, a.Line2, a.City, a.ZIPCode, s.Abbreviation + SELECT a.AddressLine1, a.AddressLine2, a.AddressCity, a.AddressZIPCode, s.Abbreviation FROM Addresses a - LEFT JOIN tt_States s ON s.ID = a.StateID - WHERE (a.BusinessID = :businessID OR a.ID = (SELECT AddressID FROM Businesses WHERE ID = :businessID)) - AND a.IsDeleted = 0 + LEFT JOIN tt_States s ON s.ID = a.AddressStateID + WHERE (a.AddressBusinessID = :businessID OR a.AddressID = (SELECT BusinessAddressID FROM Businesses WHERE BusinessID = :businessID)) + AND a.AddressIsDeleted = 0 LIMIT 1 ", { businessID: businessID }, { datasource: "payfrit" }); @@ -70,29 +70,29 @@ try { addressState = ""; addressZip = ""; if (qAddr.recordCount > 0) { - addressLine1 = qAddr.Line1; - addressCity = qAddr.City; + addressLine1 = qAddr.AddressLine1; + addressCity = qAddr.AddressCity; addressState = qAddr.Abbreviation; - addressZip = qAddr.ZIPCode; + addressZip = qAddr.AddressZIPCode; addressParts = []; - if (len(qAddr.Line1)) arrayAppend(addressParts, qAddr.Line1); - if (len(qAddr.Line2)) arrayAppend(addressParts, qAddr.Line2); + if (len(qAddr.AddressLine1)) arrayAppend(addressParts, qAddr.AddressLine1); + if (len(qAddr.AddressLine2)) arrayAppend(addressParts, qAddr.AddressLine2); cityStateZip = []; - if (len(qAddr.City)) arrayAppend(cityStateZip, qAddr.City); + if (len(qAddr.AddressCity)) arrayAppend(cityStateZip, qAddr.AddressCity); if (len(qAddr.Abbreviation)) arrayAppend(cityStateZip, qAddr.Abbreviation); - if (len(qAddr.ZIPCode)) arrayAppend(cityStateZip, qAddr.ZIPCode); + if (len(qAddr.AddressZIPCode)) arrayAppend(cityStateZip, qAddr.AddressZIPCode); if (arrayLen(cityStateZip) > 0) arrayAppend(addressParts, arrayToList(cityStateZip, ", ")); addressStr = arrayToList(addressParts, ", "); } // Get hours from Hours table qHours = queryExecute(" - SELECT h.DayID, h.OpenTime, h.ClosingTime, d.Abbrev + SELECT h.HoursDayID, h.HoursOpenTime, h.HoursClosingTime, d.Abbrev FROM Hours h - JOIN tt_Days d ON d.ID = h.DayID - WHERE h.BusinessID = :businessID - ORDER BY h.DayID + JOIN tt_Days d ON d.ID = h.HoursDayID + WHERE h.HoursBusinessID = :businessID + ORDER BY h.HoursDayID ", { businessID: businessID }, { datasource: "payfrit" }); hoursArr = []; @@ -101,13 +101,12 @@ try { for (h in qHours) { arrayAppend(hoursArr, { "day": h.Abbrev, - "dayId": h.DayID, - "open": timeFormat(h.OpenTime, "h:mm tt"), - "close": timeFormat(h.ClosingTime, "h:mm tt") + "dayId": h.HoursDayID, + "open": timeFormat(h.HoursOpenTime, "h:mm tt"), + "close": timeFormat(h.HoursClosingTime, "h:mm tt") }); } // Build readable hours string (group similar days) - // Mon-Thu: 11am-10pm, Fri-Sat: 11am-11pm, Sun: 11am-10pm hourGroups = {}; for (h in hoursArr) { key = h.open & "-" & h.close; @@ -125,28 +124,28 @@ try { } // Build business object - taxRate = isNumeric(q.TaxRate) ? q.TaxRate : 0; + taxRate = isNumeric(q.BusinessTaxRate) ? q.BusinessTaxRate : 0; business = { - "BusinessID": q.ID, - "Name": q.Name, + "BusinessID": q.BusinessID, + "Name": q.BusinessName, "Address": addressStr, "Line1": addressLine1, "City": addressCity, "AddressState": addressState, "AddressZip": addressZip, - "Phone": q.Phone, + "Phone": q.BusinessPhone, "Hours": hoursStr, "HoursDetail": hoursArr, - "StripeConnected": (len(q.StripeAccountID) > 0 && q.StripeOnboardingComplete == 1), - "IsHiring": q.IsHiring == 1, + "StripeConnected": (len(q.BusinessStripeAccountID) > 0 && q.BusinessStripeOnboardingComplete == 1), + "IsHiring": q.BusinessIsHiring == 1, "TaxRate": taxRate, "TaxRatePercent": taxRate * 100, - "BrandColor": len(q.BrandColor) ? (left(q.BrandColor, 1) == chr(35) ? q.BrandColor : chr(35) & q.BrandColor) : "" + "BrandColor": len(q.BusinessBrandColor) ? (left(q.BusinessBrandColor, 1) == chr(35) ? q.BusinessBrandColor : chr(35) & q.BusinessBrandColor) : "" }; // Add header image URL if extension exists - if (len(q.HeaderImageExtension)) { - business["HeaderImageURL"] = "https://biz.payfrit.com/uploads/headers/" & q.ID & "." & q.HeaderImageExtension; + if (len(q.BusinessHeaderImageExtension)) { + business["HeaderImageURL"] = "https://biz.payfrit.com/uploads/headers/" & q.BusinessID & "." & q.BusinessHeaderImageExtension; } response["OK"] = true; diff --git a/api/businesses/getChildren.cfm b/api/businesses/getChildren.cfm index 39d8541..71571b4 100644 --- a/api/businesses/getChildren.cfm +++ b/api/businesses/getChildren.cfm @@ -36,10 +36,10 @@ try { q = queryExecute( " SELECT - ID, - Name + BusinessID, + BusinessName FROM Businesses - WHERE ParentBusinessID = :parentId + WHERE BusinessParentBusinessID = :parentId ORDER BY Name ", { parentId = { value = parentBusinessId, cfsqltype = "cf_sql_integer" } }, @@ -49,8 +49,8 @@ try { rows = []; for (i = 1; i <= q.recordCount; i++) { arrayAppend(rows, { - "BusinessID": q.ID[i], - "Name": q.Name[i] + "BusinessID": q.BusinessID[i], + "Name": q.BusinessName[i] }); } diff --git a/api/businesses/list.cfm b/api/businesses/list.cfm index 401c62b..16f2f57 100644 --- a/api/businesses/list.cfm +++ b/api/businesses/list.cfm @@ -43,17 +43,17 @@ try { q = queryExecute( " SELECT - b.ID, - b.Name, - a.Latitude, - a.Longitude, - a.City, - a.Line1 + b.BusinessID, + b.BusinessName, + a.AddressLat, + a.AddressLng, + a.AddressCity, + a.AddressLine1 FROM Businesses b - LEFT JOIN Addresses a ON b.AddressID = a.ID - WHERE (b.IsDemo = 0 OR b.IsDemo IS NULL) - AND (b.IsPrivate = 0 OR b.IsPrivate IS NULL) - ORDER BY b.Name + LEFT JOIN Addresses a ON b.BusinessAddressID = a.AddressID + WHERE (b.BusinessIsDemo = 0 OR b.BusinessIsDemo IS NULL) + AND (b.BusinessIsPrivate = 0 OR b.BusinessIsPrivate IS NULL) + ORDER BY b.BusinessName ", [], { datasource = "payfrit" } @@ -63,15 +63,15 @@ try { rows = []; for (i = 1; i <= q.recordCount; i++) { row = { - "BusinessID": q.ID[i], - "Name": q.Name[i], - "City": isNull(q.City[i]) ? "" : q.City[i], - "Line1": isNull(q.Line1[i]) ? "" : q.Line1[i] + "BusinessID": q.BusinessID[i], + "Name": q.BusinessName[i], + "City": isNull(q.AddressCity[i]) ? "" : q.AddressCity[i], + "Line1": isNull(q.AddressLine1[i]) ? "" : q.AddressLine1[i] }; // Calculate distance if we have both user location and business location - bizLat = isNull(q.Latitude[i]) ? 0 : val(q.Latitude[i]); - bizLng = isNull(q.Longitude[i]) ? 0 : val(q.Longitude[i]); + bizLat = isNull(q.AddressLat[i]) ? 0 : val(q.AddressLat[i]); + bizLng = isNull(q.AddressLng[i]) ? 0 : val(q.AddressLng[i]); if (hasUserLocation AND bizLat != 0 AND bizLng != 0) { row["DistanceMiles"] = haversineDistance(userLat, userLng, bizLat, bizLng); diff --git a/api/businesses/setHiring.cfm b/api/businesses/setHiring.cfm index 8447ed1..b204cb6 100644 --- a/api/businesses/setHiring.cfm +++ b/api/businesses/setHiring.cfm @@ -45,7 +45,7 @@ if (isHiring == -1) { try { queryExecute(" UPDATE Businesses - SET IsHiring = ? + SET BusinessIsHiring = ? WHERE BusinessID = ? ", [ { value: isHiring, cfsqltype: "cf_sql_tinyint" }, diff --git a/api/businesses/update.cfm b/api/businesses/update.cfm index e3b1bf5..4e2f798 100644 --- a/api/businesses/update.cfm +++ b/api/businesses/update.cfm @@ -48,7 +48,7 @@ try { if (len(bizName)) { if (isNumeric(taxRate)) { queryExecute(" - UPDATE Businesses SET Name = :name, Phone = :phone, TaxRate = :taxRate + UPDATE Businesses SET BusinessName = :name, BusinessPhone = :phone, BusinessTaxRate = :taxRate WHERE BusinessID = :id ", { name: bizName, @@ -58,7 +58,7 @@ try { }, { datasource: "payfrit" }); } else { queryExecute(" - UPDATE Businesses SET Name = :name, Phone = :phone + UPDATE Businesses SET BusinessName = :name, BusinessPhone = :phone WHERE BusinessID = :id ", { name: bizName, @@ -90,8 +90,8 @@ try { // Check if business has an address qAddr = queryExecute(" - SELECT ID FROM Addresses - WHERE BusinessID = :bizID AND UserID = 0 AND IsDeleted = 0 + SELECT AddressID FROM Addresses + WHERE AddressBusinessID = :bizID AND AddressUserID = 0 AND AddressIsDeleted = 0 LIMIT 1 ", { bizID: businessId }, { datasource: "payfrit" }); @@ -99,11 +99,11 @@ try { // Update existing address queryExecute(" UPDATE Addresses SET - Line1 = :line1, - City = :city, - StateID = :stateID, - ZIPCode = :zip - WHERE ID = :addrID + AddressLine1 = :line1, + AddressCity = :city, + AddressStateID = :stateID, + AddressZIPCode = :zip + WHERE AddressID = :addrID ", { line1: addressLine1, city: city, @@ -114,7 +114,7 @@ try { } else { // Create new address queryExecute(" - INSERT INTO Addresses (Line1, City, StateID, ZIPCode, BusinessID, UserID, AddressTypeID, AddedOn) + INSERT INTO Addresses (AddressLine1, AddressCity, AddressStateID, AddressZIPCode, AddressBusinessID, AddressUserID, AddressTypeID, AddressAddedOn) VALUES (:line1, :city, :stateID, :zip, :bizID, 0, 2, NOW()) ", { line1: addressLine1, diff --git a/api/businesses/updateHours.cfm b/api/businesses/updateHours.cfm index ffb6fcc..e11b322 100644 --- a/api/businesses/updateHours.cfm +++ b/api/businesses/updateHours.cfm @@ -38,7 +38,7 @@ try { // Delete all existing hours for this business queryExecute(" - DELETE FROM Hours WHERE BusinessID = :bizID + DELETE FROM Hours WHERE HoursBusinessID = :bizID ", { bizID: businessId }, { datasource: "payfrit" }); // Insert new hours @@ -55,7 +55,7 @@ try { if (len(closeTime) == 5) closeTime = closeTime & ":00"; queryExecute(" - INSERT INTO Hours (BusinessID, DayID, OpenTime, ClosingTime) + INSERT INTO Hours (HoursBusinessID, HoursDayID, HoursOpenTime, HoursClosingTime) VALUES (:bizID, :dayID, :openTime, :closeTime) ", { bizID: businessId, diff --git a/api/menu/clearOrders.cfm b/api/menu/clearOrders.cfm index c638f4e..53bcdf0 100644 --- a/api/menu/clearOrders.cfm +++ b/api/menu/clearOrders.cfm @@ -22,14 +22,14 @@ try { // Get counts before deletion qOrderLineItems = queryExecute("SELECT COUNT(*) as cnt FROM OrderLineItems", {}, { datasource: "payfrit" }); qOrders = queryExecute("SELECT COUNT(*) as cnt FROM Orders", {}, { datasource: "payfrit" }); - qAddresses = queryExecute("SELECT COUNT(*) as cnt FROM Addresses", {}, { datasource: "payfrit" }); + qAddresses = queryExecute("SELECT COUNT(*) as cnt FROM Addresses WHERE AddressTypeID != 2", {}, { datasource: "payfrit" }); qTasks = queryExecute("SELECT COUNT(*) as cnt FROM Tasks", {}, { datasource: "payfrit" }); // Delete in correct order (foreign key constraints) queryExecute("DELETE FROM Tasks", {}, { datasource: "payfrit" }); queryExecute("DELETE FROM OrderLineItems", {}, { datasource: "payfrit" }); queryExecute("DELETE FROM Orders", {}, { datasource: "payfrit" }); - queryExecute("DELETE FROM Addresses", {}, { datasource: "payfrit" }); + queryExecute("DELETE FROM Addresses WHERE AddressTypeID != 2", {}, { datasource: "payfrit" }); response = { "OK": true, diff --git a/api/menu/getForBuilder.cfm b/api/menu/getForBuilder.cfm index 121fe8c..a05bf27 100644 --- a/api/menu/getForBuilder.cfm +++ b/api/menu/getForBuilder.cfm @@ -442,10 +442,10 @@ try { brandColor = ""; try { qBrand = queryExecute(" - SELECT BrandColor FROM Businesses WHERE ID = :bizId + SELECT BusinessBrandColor FROM Businesses WHERE BusinessID = :bizId ", { bizId: businessID }, { datasource: "payfrit" }); - if (qBrand.recordCount > 0 && len(trim(qBrand.BrandColor))) { - brandColor = left(qBrand.BrandColor, 1) == chr(35) ? qBrand.BrandColor : chr(35) & qBrand.BrandColor; + if (qBrand.recordCount > 0 && len(trim(qBrand.BusinessBrandColor))) { + brandColor = left(qBrand.BusinessBrandColor, 1) == chr(35) ? qBrand.BusinessBrandColor : chr(35) & qBrand.BusinessBrandColor; } } catch (any e) { // Column may not exist yet, ignore diff --git a/api/menu/items.cfm b/api/menu/items.cfm index 9746f3d..da61847 100644 --- a/api/menu/items.cfm +++ b/api/menu/items.cfm @@ -533,12 +533,12 @@ - - + + diff --git a/api/orders/getActiveCart.cfm b/api/orders/getActiveCart.cfm index c82cc9c..7dd5797 100644 --- a/api/orders/getActiveCart.cfm +++ b/api/orders/getActiveCart.cfm @@ -36,17 +36,17 @@ try { o.StatusID, o.ServicePointID, o.AddedOn, - b.Name, - b.OrderTypes, - sp.Name, + b.BusinessName, + b.BusinessOrderTypes, + sp.ServicePointName, (SELECT COUNT(*) FROM OrderLineItems oli WHERE oli.OrderID = o.ID AND oli.IsDeleted = 0 AND oli.ParentOrderLineItemID = 0) as ItemCount FROM Orders o - LEFT JOIN Businesses b ON b.ID = o.BusinessID - LEFT JOIN ServicePoints sp ON sp.ID = o.ServicePointID + LEFT JOIN Businesses b ON b.BusinessID = o.BusinessID + LEFT JOIN ServicePoints sp ON sp.ServicePointID = o.ServicePointID WHERE o.UserID = :userId AND o.StatusID = 0 ORDER BY o.ID DESC @@ -65,7 +65,7 @@ try { } // Parse business order types (e.g., "1,2,3" -> array of ints) - businessOrderTypes = len(trim(qCart.OrderTypes)) ? qCart.OrderTypes : "1,2,3"; + businessOrderTypes = len(trim(qCart.BusinessOrderTypes)) ? qCart.BusinessOrderTypes : "1,2,3"; orderTypesArray = listToArray(businessOrderTypes, ","); response["OK"] = true; @@ -74,12 +74,12 @@ try { "OrderID": val(qCart.OrderID), "UUID": qCart.UUID ?: "", "BusinessID": val(qCart.BusinessID), - "Name": len(trim(qCart.Name)) ? qCart.Name : "", + "Name": len(trim(qCart.BusinessName)) ? qCart.BusinessName : "", "OrderTypes": orderTypesArray, "OrderTypeID": val(qCart.OrderTypeID), "OrderTypeName": orderTypeName, "ServicePointID": val(qCart.ServicePointID), - "Name": len(trim(qCart.Name)) ? qCart.Name : "", + "ServicePointName": len(trim(qCart.ServicePointName)) ? qCart.ServicePointName : "", "ItemCount": val(qCart.ItemCount), "AddedOn": dateTimeFormat(qCart.AddedOn, "yyyy-mm-dd HH:nn:ss") }; diff --git a/api/orders/getCart.cfm b/api/orders/getCart.cfm index 5d1f522..283006d 100644 --- a/api/orders/getCart.cfm +++ b/api/orders/getCart.cfm @@ -67,12 +67,12 @@ - - + + - + - + 0 @@ -144,7 +144,7 @@ try { "OrderID": val(row.ID), "UUID": row.UUID ?: "", "BusinessID": val(row.BusinessID), - "Name": row.Name ?: "Unknown", + "Name": row.BusinessName ?: "Unknown", "OrderTotal": round(val(total) * 100) / 100, "StatusID": val(row.StatusID), "StatusName": statusText, diff --git a/api/orders/setLineItem.cfm b/api/orders/setLineItem.cfm index 6437bf7..7049cbf 100644 --- a/api/orders/setLineItem.cfm +++ b/api/orders/setLineItem.cfm @@ -191,9 +191,9 @@ o.LastEditedOn, o.SubmittedOn, o.ServicePointID, - COALESCE(b.DeliveryFlatFee, 0) AS BusinessDeliveryFee + COALESCE(b.BusinessDeliveryFlatFee, 0) AS BusinessDeliveryFee FROM Orders o - LEFT JOIN Businesses b ON b.ID = o.BusinessID + LEFT JOIN Businesses b ON b.BusinessID = o.BusinessID WHERE o.ID = ? LIMIT 1 ", diff --git a/api/orders/setOrderType.cfm b/api/orders/setOrderType.cfm index f0e1150..2deb094 100644 --- a/api/orders/setOrderType.cfm +++ b/api/orders/setOrderType.cfm @@ -62,11 +62,11 @@ - + - + diff --git a/api/setup/saveWizard.cfm b/api/setup/saveWizard.cfm index 1e55038..a7cdc2f 100644 --- a/api/setup/saveWizard.cfm +++ b/api/setup/saveWizard.cfm @@ -82,7 +82,7 @@ try { } queryExecute(" - INSERT INTO Addresses (Line1, City, StateID, ZIPCode, UserID, AddressTypeID, AddedOn) + INSERT INTO Addresses (AddressLine1, AddressCity, AddressStateID, AddressZIPCode, AddressUserID, AddressTypeID, AddressAddedOn) VALUES (:line1, :city, :stateID, :zip, :userID, :typeID, NOW()) ", { line1: len(addressLine1) ? addressLine1 : "Address pending", @@ -103,7 +103,7 @@ try { // Create new business with address link and phone queryExecute(" - INSERT INTO Businesses (Name, Phone, UserID, AddressID, BusinessDeliveryZipCodes, CommunityMealType, TaxRate, AddedOn) + INSERT INTO Businesses (BusinessName, BusinessPhone, BusinessUserID, BusinessAddressID, BusinessDeliveryZipCodes, BusinessCommunityMealType, BusinessTaxRate, BusinessAddedOn) VALUES (:name, :phone, :userId, :addressId, :deliveryZips, :communityMealType, :taxRate, NOW()) ", { name: bizName, @@ -121,7 +121,7 @@ try { // Update address with business ID link queryExecute(" - UPDATE Addresses SET BusinessID = :businessID WHERE ID = :addressID + UPDATE Addresses SET AddressBusinessID = :businessID WHERE AddressID = :addressID ", { businessID: businessId, addressID: addressId @@ -188,14 +188,14 @@ try { } else { // Verify existing business exists qBiz = queryExecute(" - SELECT ID, Name FROM Businesses WHERE ID = :id + SELECT BusinessID, BusinessName FROM Businesses WHERE BusinessID = :id ", { id: businessId }, { datasource: "payfrit" }); if (qBiz.recordCount == 0) { throw(message="Business not found: " & businessId); } - response.steps.append("Found existing business: " & qBiz.Name); + response.steps.append("Found existing business: " & qBiz.BusinessName); } // Build modifier template map diff --git a/api/stripe/createPaymentIntent.cfm b/api/stripe/createPaymentIntent.cfm index 75fc286..13c4e41 100644 --- a/api/stripe/createPaymentIntent.cfm +++ b/api/stripe/createPaymentIntent.cfm @@ -62,9 +62,9 @@ try { // Get business Stripe account qBusiness = queryExecute(" - SELECT StripeAccountID, StripeOnboardingComplete, Name + SELECT BusinessStripeAccountID, BusinessStripeOnboardingComplete, BusinessName FROM Businesses - WHERE ID = :businessID + WHERE BusinessID = :businessID ", { businessID: businessID }, { datasource: "payfrit" }); if (qBusiness.recordCount == 0) { @@ -75,18 +75,18 @@ try { // Get order's delivery fee (if delivery order) qOrder = queryExecute(" - SELECT DeliveryFee, OrderTypeID + SELECT OrderDeliveryFee, OrderTypeID FROM Orders - WHERE ID = :orderID + WHERE OrderID = :orderID ", { orderID: orderID }, { datasource: "payfrit" }); deliveryFee = 0; if (qOrder.recordCount > 0 && qOrder.OrderTypeID == 3) { - deliveryFee = val(qOrder.DeliveryFee); + deliveryFee = val(qOrder.OrderDeliveryFee); } // For testing, allow orders even without Stripe Connect setup - hasStripeConnect = qBusiness.StripeOnboardingComplete == 1 && len(trim(qBusiness.StripeAccountID)) > 0; + hasStripeConnect = qBusiness.BusinessStripeOnboardingComplete == 1 && len(trim(qBusiness.BusinessStripeAccountID)) > 0; // ============================================================ // FEE CALCULATION @@ -119,12 +119,12 @@ try { if (hasStripeConnect) { httpService.addParam(type="formfield", name="application_fee_amount", value=totalPlatformFeeCents); - httpService.addParam(type="formfield", name="transfer_data[destination]", value=qBusiness.StripeAccountID); + httpService.addParam(type="formfield", name="transfer_data[destination]", value=qBusiness.BusinessStripeAccountID); } httpService.addParam(type="formfield", name="metadata[order_id]", value=orderID); httpService.addParam(type="formfield", name="metadata[business_id]", value=businessID); - httpService.addParam(type="formfield", name="description", value="Order ###orderID# at #qBusiness.Name#"); + httpService.addParam(type="formfield", name="description", value="Order ###orderID# at #qBusiness.BusinessName#"); if (customerEmail != "") { httpService.addParam(type="formfield", name="receipt_email", value=customerEmail); diff --git a/api/stripe/onboard.cfm b/api/stripe/onboard.cfm index c59e3b9..0d059b4 100644 --- a/api/stripe/onboard.cfm +++ b/api/stripe/onboard.cfm @@ -32,9 +32,9 @@ try { // Check if business already has a Stripe account qBusiness = queryExecute(" - SELECT StripeAccountID, Name, BusinessEmail + SELECT BusinessStripeAccountID, BusinessName FROM Businesses - WHERE ID = :businessID + WHERE BusinessID = :businessID ", { businessID: businessID }); if (qBusiness.recordCount == 0) { @@ -43,7 +43,7 @@ try { abort; } - stripeAccountID = qBusiness.StripeAccountID; + stripeAccountID = qBusiness.BusinessStripeAccountID; // Create new connected account if none exists if (stripeAccountID == "" || isNull(stripeAccountID)) { @@ -55,10 +55,9 @@ try { httpService.setPassword(""); httpService.addParam(type="formfield", name="type", value="express"); httpService.addParam(type="formfield", name="country", value="US"); - httpService.addParam(type="formfield", name="email", value=qBusiness.BusinessEmail); httpService.addParam(type="formfield", name="capabilities[card_payments][requested]", value="true"); httpService.addParam(type="formfield", name="capabilities[transfers][requested]", value="true"); - httpService.addParam(type="formfield", name="business_profile[name]", value=qBusiness.Name); + httpService.addParam(type="formfield", name="business_profile[name]", value=qBusiness.BusinessName); result = httpService.send().getPrefix(); accountData = deserializeJSON(result.fileContent); @@ -74,8 +73,8 @@ try { // Save to database queryExecute(" UPDATE Businesses - SET StripeAccountID = :stripeAccountID, - StripeOnboardingStarted = NOW() + SET BusinessStripeAccountID = :stripeAccountID, + BusinessStripeOnboardingStarted = NOW() WHERE BusinessID = :businessID ", { stripeAccountID: stripeAccountID, diff --git a/api/stripe/status.cfm b/api/stripe/status.cfm index 3e9bb4e..532d09c 100644 --- a/api/stripe/status.cfm +++ b/api/stripe/status.cfm @@ -23,9 +23,9 @@ try { // Get business Stripe info qBusiness = queryExecute(" - SELECT StripeAccountID, StripeOnboardingComplete + SELECT BusinessStripeAccountID, BusinessStripeOnboardingComplete FROM Businesses - WHERE ID = :businessID + WHERE BusinessID = :businessID ", { businessID: businessID }); if (qBusiness.recordCount == 0) { @@ -34,7 +34,7 @@ try { abort; } - stripeAccountID = qBusiness.StripeAccountID; + stripeAccountID = qBusiness.BusinessStripeAccountID; if (stripeAccountID == "" || isNull(stripeAccountID)) { response["OK"] = true; @@ -73,10 +73,10 @@ try { accountStatus = "active"; // Mark as complete in database if not already - if (!qBusiness.StripeOnboardingComplete) { + if (!qBusiness.BusinessStripeOnboardingComplete) { queryExecute(" UPDATE Businesses - SET StripeOnboardingComplete = 1 + SET BusinessStripeOnboardingComplete = 1 WHERE BusinessID = :businessID ", { businessID: businessID }); } @@ -96,8 +96,8 @@ try { } else { // No Stripe key, just return what we have in DB response["OK"] = true; - response["CONNECTED"] = qBusiness.StripeOnboardingComplete == 1; - response["ACCOUNT_STATUS"] = qBusiness.StripeOnboardingComplete == 1 ? "active" : "unknown"; + response["CONNECTED"] = qBusiness.BusinessStripeOnboardingComplete == 1; + response["ACCOUNT_STATUS"] = qBusiness.BusinessStripeOnboardingComplete == 1 ? "active" : "unknown"; response["STRIPE_ACCOUNT_ID"] = stripeAccountID; } diff --git a/api/stripe/webhook.cfm b/api/stripe/webhook.cfm index 732d2b8..bdec203 100644 --- a/api/stripe/webhook.cfm +++ b/api/stripe/webhook.cfm @@ -22,15 +22,43 @@ try { // Webhook secret (set in Stripe dashboard) webhookSecret = application.stripeWebhookSecret ?: ""; - // For now, skip signature verification in development - // In production, uncomment and implement signature verification: - /* - if (webhookSecret != "" && sigHeader != "") { - // Verify webhook signature - // This requires computing HMAC-SHA256 and comparing - // See: https://stripe.com/docs/webhooks/signatures + // Verify webhook signature (Stripe HMAC-SHA256) + if (len(trim(webhookSecret)) > 0 && len(trim(sigHeader)) > 0) { + // Parse signature header: t=timestamp,v1=signature + sigParts = {}; + for (part in listToArray(sigHeader, ",")) { + kv = listToArray(trim(part), "="); + if (arrayLen(kv) >= 2) sigParts[trim(kv[1])] = trim(kv[2]); + } + + sigTimestamp = sigParts["t"] ?: ""; + sigV1 = sigParts["v1"] ?: ""; + + if (len(sigTimestamp) == 0 || len(sigV1) == 0) { + response["ERROR"] = "invalid_signature"; + writeOutput(serializeJSON(response)); + abort; + } + + // Check timestamp tolerance (5 minutes) + timestampAge = dateDiff("s", dateAdd("s", val(sigTimestamp), createDate(1970,1,1)), now()); + if (abs(timestampAge) > 300) { + response["ERROR"] = "timestamp_expired"; + writeOutput(serializeJSON(response)); + abort; + } + + // Compute expected signature: HMAC-SHA256(timestamp + "." + payload, secret) + signedPayload = sigTimestamp & "." & payload; + expectedSig = lcase(hmac(signedPayload, webhookSecret, "HmacSHA256")); + + if (expectedSig != lcase(sigV1)) { + writeLog(file="stripe_webhooks", text="Signature mismatch! Expected=#left(expectedSig,16)#... Got=#left(sigV1,16)#..."); + response["ERROR"] = "signature_mismatch"; + writeOutput(serializeJSON(response)); + abort; + } } - */ // Parse event event = deserializeJSON(payload); @@ -101,6 +129,7 @@ try { httpTransfer.addParam(type="formfield", name="metadata[task_id]", value=qLedger.TaskID); httpTransfer.addParam(type="formfield", name="metadata[ledger_id]", value=qLedger.ID); httpTransfer.addParam(type="formfield", name="metadata[activation_withheld_cents]", value=0); + httpTransfer.addParam(type="header", name="Idempotency-Key", value="transfer-ledger-#qLedger.ID#"); transferResult = httpTransfer.send().getPrefix(); transferData = deserializeJSON(transferResult.fileContent); diff --git a/api/tasks/complete.cfm b/api/tasks/complete.cfm index 7184c70..64d59e1 100644 --- a/api/tasks/complete.cfm +++ b/api/tasks/complete.cfm @@ -105,7 +105,7 @@ @@ -126,7 +126,7 @@ diff --git a/api/workers/earlyUnlock.cfm b/api/workers/earlyUnlock.cfm index 41cd975..d22bb7f 100644 --- a/api/workers/earlyUnlock.cfm +++ b/api/workers/earlyUnlock.cfm @@ -59,8 +59,9 @@ try { httpService.addParam(type="formfield", name="line_items[0][price_data][currency]", value="usd"); httpService.addParam(type="formfield", name="line_items[0][price_data][product_data][name]", value="Payfrit Activation"); httpService.addParam(type="formfield", name="line_items[0][quantity]", value="1"); - httpService.addParam(type="formfield", name="success_url", value="https://biz.payfrit.com/works/activation?success=1"); - httpService.addParam(type="formfield", name="cancel_url", value="https://biz.payfrit.com/works/activation?cancel=1"); + baseUrl = application.isDevEnvironment ? "https://dev.payfrit.com" : "https://biz.payfrit.com"; + httpService.addParam(type="formfield", name="success_url", value=baseUrl & "/works/stripe-return.cfm?status=success"); + httpService.addParam(type="formfield", name="cancel_url", value=baseUrl & "/works/stripe-return.cfm?status=cancel"); httpService.addParam(type="formfield", name="metadata[user_id]", value=userID); httpService.addParam(type="formfield", name="metadata[type]", value="activation_unlock"); diff --git a/api/workers/onboardingLink.cfm b/api/workers/onboardingLink.cfm index 7ca0b91..91a770a 100644 --- a/api/workers/onboardingLink.cfm +++ b/api/workers/onboardingLink.cfm @@ -53,8 +53,9 @@ try { httpService.setPassword(""); httpService.addParam(type="formfield", name="account", value=accountID); - httpService.addParam(type="formfield", name="refresh_url", value="https://biz.payfrit.com/works/tier?refresh=1"); - httpService.addParam(type="formfield", name="return_url", value="https://biz.payfrit.com/works/tier?return=1"); + baseUrl = application.isDevEnvironment ? "https://dev.payfrit.com" : "https://biz.payfrit.com"; + httpService.addParam(type="formfield", name="refresh_url", value=baseUrl & "/works/stripe-return.cfm?status=refresh"); + httpService.addParam(type="formfield", name="return_url", value=baseUrl & "/works/stripe-return.cfm?status=complete"); httpService.addParam(type="formfield", name="type", value="account_onboarding"); result = httpService.send().getPrefix();