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();