diff --git a/api/addresses/add.cfm b/api/addresses/add.cfm index db41fd0..0903699 100644 --- a/api/addresses/add.cfm +++ b/api/addresses/add.cfm @@ -31,6 +31,7 @@ try { data = readJsonBody(); // Required fields + typeId = val(data.TypeID ?: 0); line1 = trim(data.Line1 ?: ""); city = trim(data.City ?: ""); stateId = val(data.StateID ?: 0); @@ -42,25 +43,26 @@ try { setAsDefault = (data.SetAsDefault ?: false) == true; // Validation - if (len(line1) == 0 || len(city) == 0 || stateId <= 0 || len(zipCode) == 0) { + if (typeId <= 0 || len(line1) == 0 || len(city) == 0 || stateId <= 0 || len(zipCode) == 0) { writeOutput(serializeJSON({ "OK": false, "ERROR": "missing_fields", - "MESSAGE": "Line1, City, StateID, and ZIPCode are required" + "MESSAGE": "TypeID, Line1, City, StateID, and ZIPCode are required" })); abort; } - // If setting as default, clear other defaults first + // If setting as default, clear other defaults first (for same type) if (setAsDefault) { queryExecute(" UPDATE Addresses SET AddressIsDefaultDelivery = 0 WHERE AddressUserID = :userId AND (AddressBusinessID = 0 OR AddressBusinessID IS NULL) - AND AddressTypeID LIKE '%2%' + AND AddressTypeID = :typeId ", { - userId: { value: userId, cfsqltype: "cf_sql_integer" } + userId: { value: userId, cfsqltype: "cf_sql_integer" }, + typeId: { value: typeId, cfsqltype: "cf_sql_integer" } }, { datasource: "payfrit" }); } @@ -88,7 +90,7 @@ try { :addressId, :userId, 0, - '2', + :typeId, :label, :isDefault, :line1, @@ -102,6 +104,7 @@ try { ", { addressId: { value: newAddressId, cfsqltype: "cf_sql_integer" }, userId: { value: userId, cfsqltype: "cf_sql_integer" }, + typeId: { value: typeId, cfsqltype: "cf_sql_integer" }, label: { value: label, cfsqltype: "cf_sql_varchar" }, isDefault: { value: setAsDefault ? 1 : 0, cfsqltype: "cf_sql_integer" }, line1: { value: line1, cfsqltype: "cf_sql_varchar" }, @@ -124,6 +127,7 @@ try { "OK": true, "ADDRESS": { "AddressID": newAddressId, + "TypeID": typeId, "Label": len(label) ? label : "Address", "IsDefault": setAsDefault, "Line1": line1, diff --git a/api/addresses/list.cfm b/api/addresses/list.cfm index df26570..42c3da8 100644 --- a/api/addresses/list.cfm +++ b/api/addresses/list.cfm @@ -46,26 +46,25 @@ if (userId <= 0) { } try { - // Get user's delivery addresses with GROUP BY to show unique addresses only + // Get user's addresses qAddresses = queryExecute(" SELECT - MIN(a.AddressID) as AddressID, - MAX(a.AddressLabel) as AddressLabel, - MAX(a.AddressIsDefaultDelivery) as AddressIsDefaultDelivery, + a.AddressID, + a.AddressLabel, + a.AddressTypeID, + a.AddressIsDefaultDelivery, a.AddressLine1, a.AddressLine2, a.AddressCity, a.AddressStateID, - MAX(s.tt_StateAbbreviation) as StateAbbreviation, - MAX(s.tt_StateName) as StateName, + s.tt_StateAbbreviation as StateAbbreviation, + s.tt_StateName as StateName, a.AddressZIPCode FROM Addresses a LEFT JOIN tt_States s ON a.AddressStateID = s.tt_StateID WHERE a.AddressUserID = :userId - AND a.AddressTypeID LIKE '%2%' AND a.AddressIsDeleted = 0 - GROUP BY a.AddressLine1, a.AddressLine2, a.AddressCity, a.AddressStateID, a.AddressZIPCode - ORDER BY MAX(a.AddressIsDefaultDelivery) DESC, MIN(a.AddressID) DESC + ORDER BY a.AddressIsDefaultDelivery DESC, a.AddressID DESC ", { userId: { value = userId, cfsqltype = "cf_sql_integer" } }); @@ -74,6 +73,7 @@ try { for (row in qAddresses) { arrayAppend(addresses, { "AddressID": row.AddressID, + "TypeID": val(row.AddressTypeID), "Label": len(row.AddressLabel) ? row.AddressLabel : "Address", "IsDefault": row.AddressIsDefaultDelivery == 1, "Line1": row.AddressLine1, diff --git a/api/addresses/types.cfm b/api/addresses/types.cfm new file mode 100644 index 0000000..72b39eb --- /dev/null +++ b/api/addresses/types.cfm @@ -0,0 +1,40 @@ + + + + + + +/** + * Get list of address types + * GET: /api/addresses/types.cfm + * Returns: { OK: true, TYPES: [{ ID: 1, Label: "Billing" }, ...] } + */ + +try { + qTypes = queryExecute(" + SELECT tt_AddressTypeID as ID, tt_AddressType as Label + FROM tt_AddressTypes + ORDER BY tt_AddressTypeID + ", {}, { datasource: "payfrit" }); + + types = []; + for (row in qTypes) { + arrayAppend(types, { + "ID": row.ID, + "Label": row.Label + }); + } + + writeOutput(serializeJSON({ + "OK": true, + "TYPES": types + })); + +} catch (any e) { + writeOutput(serializeJSON({ + "OK": false, + "ERROR": "server_error", + "MESSAGE": e.message + })); +} + diff --git a/api/auth/loginOTP.cfm b/api/auth/loginOTP.cfm index 2130064..0241c56 100644 --- a/api/auth/loginOTP.cfm +++ b/api/auth/loginOTP.cfm @@ -87,9 +87,11 @@ try { userId: { value: qUser.UserID, cfsqltype: "cf_sql_integer" } }, { datasource: "payfrit" }); - // Send OTP via Twilio (if available) + // Send OTP via Twilio (skip on dev server) smsMessage = "Code saved (SMS skipped in dev)"; - if (structKeyExists(application, "twilioObj")) { + isDev = findNoCase("dev.payfrit.com", cgi.SERVER_NAME) > 0; + + if (!isDev && structKeyExists(application, "twilioObj")) { try { smsResult = application.twilioObj.sendSMS( recipientNumber: "+1" & phone, diff --git a/api/auth/sendOTP.cfm b/api/auth/sendOTP.cfm index c059c7f..c558d60 100644 --- a/api/auth/sendOTP.cfm +++ b/api/auth/sendOTP.cfm @@ -130,18 +130,17 @@ try { }, { datasource: "payfrit" }); } - // Send OTP via Twilio (if available) + // Send OTP via Twilio (skip on dev server) smsMessage = "Code saved (SMS skipped in dev)"; - devOTP = otp; // Return OTP in dev mode for testing + isDev = findNoCase("dev.payfrit.com", cgi.SERVER_NAME) > 0; - if (structKeyExists(application, "twilioObj")) { + if (!isDev && structKeyExists(application, "twilioObj")) { try { smsResult = application.twilioObj.sendSMS( recipientNumber: "+1" & phone, messageBody: "Your Payfrit verification code is: " & otp ); smsMessage = smsResult.success ? "Verification code sent" : "SMS failed - please try again"; - if (smsResult.success) devOTP = ""; // Don't leak OTP in production } catch (any smsErr) { smsMessage = "SMS error: " & smsErr.message; } @@ -151,7 +150,7 @@ try { "OK": true, "UUID": userUUID, "MESSAGE": smsMessage, - "DEV_OTP": devOTP + "DEV_OTP": otp })); } catch (any e) {