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();
|
||||
|
||||
// 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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
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" }
|
||||
}, { 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,
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Reference in a new issue