Add address types endpoint, fix dev mode SMS skip
- Add /addresses/types.cfm - returns address types list - Update /addresses/list.cfm - include TypeID in response - Update /addresses/add.cfm - accept TypeID instead of hardcoded '2' - Fix loginOTP.cfm and sendOTP.cfm to skip Twilio SMS on dev server Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
477cf6b8b5
commit
cadc66e46a
5 changed files with 67 additions and 22 deletions
|
|
@ -31,6 +31,7 @@ try {
|
||||||
data = readJsonBody();
|
data = readJsonBody();
|
||||||
|
|
||||||
// Required fields
|
// Required fields
|
||||||
|
typeId = val(data.TypeID ?: 0);
|
||||||
line1 = trim(data.Line1 ?: "");
|
line1 = trim(data.Line1 ?: "");
|
||||||
city = trim(data.City ?: "");
|
city = trim(data.City ?: "");
|
||||||
stateId = val(data.StateID ?: 0);
|
stateId = val(data.StateID ?: 0);
|
||||||
|
|
@ -42,25 +43,26 @@ try {
|
||||||
setAsDefault = (data.SetAsDefault ?: false) == true;
|
setAsDefault = (data.SetAsDefault ?: false) == true;
|
||||||
|
|
||||||
// Validation
|
// 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({
|
writeOutput(serializeJSON({
|
||||||
"OK": false,
|
"OK": false,
|
||||||
"ERROR": "missing_fields",
|
"ERROR": "missing_fields",
|
||||||
"MESSAGE": "Line1, City, StateID, and ZIPCode are required"
|
"MESSAGE": "TypeID, Line1, City, StateID, and ZIPCode are required"
|
||||||
}));
|
}));
|
||||||
abort;
|
abort;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If setting as default, clear other defaults first
|
// If setting as default, clear other defaults first (for same type)
|
||||||
if (setAsDefault) {
|
if (setAsDefault) {
|
||||||
queryExecute("
|
queryExecute("
|
||||||
UPDATE Addresses
|
UPDATE Addresses
|
||||||
SET AddressIsDefaultDelivery = 0
|
SET AddressIsDefaultDelivery = 0
|
||||||
WHERE AddressUserID = :userId
|
WHERE AddressUserID = :userId
|
||||||
AND (AddressBusinessID = 0 OR AddressBusinessID IS NULL)
|
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" });
|
}, { datasource: "payfrit" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,7 +90,7 @@ try {
|
||||||
:addressId,
|
:addressId,
|
||||||
:userId,
|
:userId,
|
||||||
0,
|
0,
|
||||||
'2',
|
:typeId,
|
||||||
:label,
|
:label,
|
||||||
:isDefault,
|
:isDefault,
|
||||||
:line1,
|
:line1,
|
||||||
|
|
@ -102,6 +104,7 @@ try {
|
||||||
", {
|
", {
|
||||||
addressId: { value: newAddressId, cfsqltype: "cf_sql_integer" },
|
addressId: { value: newAddressId, cfsqltype: "cf_sql_integer" },
|
||||||
userId: { value: userId, 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" },
|
label: { value: label, cfsqltype: "cf_sql_varchar" },
|
||||||
isDefault: { value: setAsDefault ? 1 : 0, cfsqltype: "cf_sql_integer" },
|
isDefault: { value: setAsDefault ? 1 : 0, cfsqltype: "cf_sql_integer" },
|
||||||
line1: { value: line1, cfsqltype: "cf_sql_varchar" },
|
line1: { value: line1, cfsqltype: "cf_sql_varchar" },
|
||||||
|
|
@ -124,6 +127,7 @@ try {
|
||||||
"OK": true,
|
"OK": true,
|
||||||
"ADDRESS": {
|
"ADDRESS": {
|
||||||
"AddressID": newAddressId,
|
"AddressID": newAddressId,
|
||||||
|
"TypeID": typeId,
|
||||||
"Label": len(label) ? label : "Address",
|
"Label": len(label) ? label : "Address",
|
||||||
"IsDefault": setAsDefault,
|
"IsDefault": setAsDefault,
|
||||||
"Line1": line1,
|
"Line1": line1,
|
||||||
|
|
|
||||||
|
|
@ -46,26 +46,25 @@ if (userId <= 0) {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get user's delivery addresses with GROUP BY to show unique addresses only
|
// Get user's addresses
|
||||||
qAddresses = queryExecute("
|
qAddresses = queryExecute("
|
||||||
SELECT
|
SELECT
|
||||||
MIN(a.AddressID) as AddressID,
|
a.AddressID,
|
||||||
MAX(a.AddressLabel) as AddressLabel,
|
a.AddressLabel,
|
||||||
MAX(a.AddressIsDefaultDelivery) as AddressIsDefaultDelivery,
|
a.AddressTypeID,
|
||||||
|
a.AddressIsDefaultDelivery,
|
||||||
a.AddressLine1,
|
a.AddressLine1,
|
||||||
a.AddressLine2,
|
a.AddressLine2,
|
||||||
a.AddressCity,
|
a.AddressCity,
|
||||||
a.AddressStateID,
|
a.AddressStateID,
|
||||||
MAX(s.tt_StateAbbreviation) as StateAbbreviation,
|
s.tt_StateAbbreviation as StateAbbreviation,
|
||||||
MAX(s.tt_StateName) as StateName,
|
s.tt_StateName as StateName,
|
||||||
a.AddressZIPCode
|
a.AddressZIPCode
|
||||||
FROM Addresses a
|
FROM Addresses a
|
||||||
LEFT JOIN tt_States s ON a.AddressStateID = s.tt_StateID
|
LEFT JOIN tt_States s ON a.AddressStateID = s.tt_StateID
|
||||||
WHERE a.AddressUserID = :userId
|
WHERE a.AddressUserID = :userId
|
||||||
AND a.AddressTypeID LIKE '%2%'
|
|
||||||
AND a.AddressIsDeleted = 0
|
AND a.AddressIsDeleted = 0
|
||||||
GROUP BY a.AddressLine1, a.AddressLine2, a.AddressCity, a.AddressStateID, a.AddressZIPCode
|
ORDER BY a.AddressIsDefaultDelivery DESC, a.AddressID DESC
|
||||||
ORDER BY MAX(a.AddressIsDefaultDelivery) DESC, MIN(a.AddressID) DESC
|
|
||||||
", {
|
", {
|
||||||
userId: { value = userId, cfsqltype = "cf_sql_integer" }
|
userId: { value = userId, cfsqltype = "cf_sql_integer" }
|
||||||
});
|
});
|
||||||
|
|
@ -74,6 +73,7 @@ try {
|
||||||
for (row in qAddresses) {
|
for (row in qAddresses) {
|
||||||
arrayAppend(addresses, {
|
arrayAppend(addresses, {
|
||||||
"AddressID": row.AddressID,
|
"AddressID": row.AddressID,
|
||||||
|
"TypeID": val(row.AddressTypeID),
|
||||||
"Label": len(row.AddressLabel) ? row.AddressLabel : "Address",
|
"Label": len(row.AddressLabel) ? row.AddressLabel : "Address",
|
||||||
"IsDefault": row.AddressIsDefaultDelivery == 1,
|
"IsDefault": row.AddressIsDefaultDelivery == 1,
|
||||||
"Line1": row.AddressLine1,
|
"Line1": row.AddressLine1,
|
||||||
|
|
|
||||||
40
api/addresses/types.cfm
Normal file
40
api/addresses/types.cfm
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
<cfsetting showdebugoutput="false">
|
||||||
|
<cfsetting enablecfoutputonly="true">
|
||||||
|
<cfcontent type="application/json; charset=utf-8" reset="true">
|
||||||
|
<cfheader name="Cache-Control" value="max-age=3600">
|
||||||
|
|
||||||
|
<cfscript>
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
</cfscript>
|
||||||
|
|
@ -87,9 +87,11 @@ try {
|
||||||
userId: { value: qUser.UserID, cfsqltype: "cf_sql_integer" }
|
userId: { value: qUser.UserID, cfsqltype: "cf_sql_integer" }
|
||||||
}, { datasource: "payfrit" });
|
}, { datasource: "payfrit" });
|
||||||
|
|
||||||
// Send OTP via Twilio (if available)
|
// Send OTP via Twilio (skip on dev server)
|
||||||
smsMessage = "Code saved (SMS skipped in dev)";
|
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 {
|
try {
|
||||||
smsResult = application.twilioObj.sendSMS(
|
smsResult = application.twilioObj.sendSMS(
|
||||||
recipientNumber: "+1" & phone,
|
recipientNumber: "+1" & phone,
|
||||||
|
|
|
||||||
|
|
@ -130,18 +130,17 @@ try {
|
||||||
}, { datasource: "payfrit" });
|
}, { datasource: "payfrit" });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send OTP via Twilio (if available)
|
// Send OTP via Twilio (skip on dev server)
|
||||||
smsMessage = "Code saved (SMS skipped in dev)";
|
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 {
|
try {
|
||||||
smsResult = application.twilioObj.sendSMS(
|
smsResult = application.twilioObj.sendSMS(
|
||||||
recipientNumber: "+1" & phone,
|
recipientNumber: "+1" & phone,
|
||||||
messageBody: "Your Payfrit verification code is: " & otp
|
messageBody: "Your Payfrit verification code is: " & otp
|
||||||
);
|
);
|
||||||
smsMessage = smsResult.success ? "Verification code sent" : "SMS failed - please try again";
|
smsMessage = smsResult.success ? "Verification code sent" : "SMS failed - please try again";
|
||||||
if (smsResult.success) devOTP = ""; // Don't leak OTP in production
|
|
||||||
} catch (any smsErr) {
|
} catch (any smsErr) {
|
||||||
smsMessage = "SMS error: " & smsErr.message;
|
smsMessage = "SMS error: " & smsErr.message;
|
||||||
}
|
}
|
||||||
|
|
@ -151,7 +150,7 @@ try {
|
||||||
"OK": true,
|
"OK": true,
|
||||||
"UUID": userUUID,
|
"UUID": userUUID,
|
||||||
"MESSAGE": smsMessage,
|
"MESSAGE": smsMessage,
|
||||||
"DEV_OTP": devOTP
|
"DEV_OTP": otp
|
||||||
}));
|
}));
|
||||||
|
|
||||||
} catch (any e) {
|
} catch (any e) {
|
||||||
|
|
|
||||||
Reference in a new issue