checkpoint
This commit is contained in:
parent
848544ba53
commit
363964d9c6
14 changed files with 1513 additions and 565 deletions
|
|
@ -5,7 +5,7 @@
|
||||||
sessiontimeout="#CreateTimeSpan(0,0,30,0)#"
|
sessiontimeout="#CreateTimeSpan(0,0,30,0)#"
|
||||||
clientstorage="cookie">
|
clientstorage="cookie">
|
||||||
|
|
||||||
<CFSET application.datasource = "payfrit">
|
<CFSET application.datasource = "payfrit_local">
|
||||||
<cfset application.businessMasterObj = new library.cfc.businessMaster(odbc = application.datasource) />
|
<cfset application.businessMasterObj = new library.cfc.businessMaster(odbc = application.datasource) />
|
||||||
<cfset application.twilioObj = new library.cfc.twilio() />
|
<cfset application.twilioObj = new library.cfc.twilio() />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,53 +2,70 @@
|
||||||
<cfsetting enablecfoutputonly="true">
|
<cfsetting enablecfoutputonly="true">
|
||||||
|
|
||||||
<!---
|
<!---
|
||||||
FILE: C:\lucee\tomcat\webapps\ROOT\biz.payfrit.com\api\Application.cfm
|
Payfrit API Application.cfm
|
||||||
|
|
||||||
MVP CHANGE:
|
FIX: Provide a DEFAULT datasource so endpoints can call queryExecute()
|
||||||
- Public allowlist for:
|
without specifying { datasource="payfrit" } every time.
|
||||||
/api/businesses/list.cfm
|
|
||||||
/api/servicepoints/list.cfm
|
|
||||||
|
|
||||||
IMPORTANT:
|
Token-auth gate for /api endpoints.
|
||||||
- Do NOT rely on exact SCRIPT_NAME equality; in some deployments it may include
|
|
||||||
the site folder prefix (e.g. /biz.payfrit.com/api/...).
|
Public allowlist (NO auth):
|
||||||
- So we allowlist by "contains" match.
|
- /api/login.cfm
|
||||||
|
- /api/logout.cfm
|
||||||
|
- /api/auth/login.cfm
|
||||||
|
- /api/auth/logout.cfm
|
||||||
|
- /api/businesses/list.cfm
|
||||||
|
- /api/servicepoints/list.cfm
|
||||||
|
|
||||||
|
Authenticated requests should send:
|
||||||
|
- Header: X-User-Token: <token from /api/auth/login.cfm>
|
||||||
|
- Header: X-Business-ID: <selected BusinessID> (required for most endpoints)
|
||||||
--->
|
--->
|
||||||
|
|
||||||
<cfset request.IsApiRequest = true>
|
<!--- OPTION A: default datasource for the whole app (THIS fixes "missing datasource") --->
|
||||||
<cfinclude template="../Application.cfm">
|
<cfapplication
|
||||||
|
name="payfrit_api"
|
||||||
<cfcontent type="application/json; charset=utf-8" reset="true">
|
sessionmanagement="true"
|
||||||
<cfheader name="Cache-Control" value="no-store">
|
clientmanagement="false"
|
||||||
|
setclientcookies="false"
|
||||||
|
datasource="payfrit"
|
||||||
|
>
|
||||||
|
|
||||||
<cfscript>
|
<cfscript>
|
||||||
function apiAbort(obj) {
|
function apiAbort(payload) {
|
||||||
writeOutput(serializeJSON(obj));
|
writeOutput(serializeJSON(payload));
|
||||||
abort;
|
abort;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine current request path
|
function headerValue(name) {
|
||||||
scriptName = "";
|
k = "HTTP_" & ucase(reReplace(arguments.name, "[^A-Za-z0-9]", "_", "all"));
|
||||||
|
if (structKeyExists(cgi, k)) return trim(cgi[k]);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine request path
|
||||||
|
request._api_scriptName = "";
|
||||||
if (structKeyExists(cgi, "SCRIPT_NAME")) {
|
if (structKeyExists(cgi, "SCRIPT_NAME")) {
|
||||||
scriptName = cgi.SCRIPT_NAME;
|
request._api_scriptName = cgi.SCRIPT_NAME;
|
||||||
} else if (structKeyExists(cgi, "PATH_INFO")) {
|
} else if (structKeyExists(cgi, "PATH_INFO")) {
|
||||||
scriptName = cgi.PATH_INFO;
|
request._api_scriptName = cgi.PATH_INFO;
|
||||||
|
}
|
||||||
|
request._api_path = lcase(request._api_scriptName);
|
||||||
|
|
||||||
|
// Public allowlist
|
||||||
|
request._api_isPublic = false;
|
||||||
|
if (len(request._api_path)) {
|
||||||
|
if (findNoCase("/api/login.cfm", request._api_path)) request._api_isPublic = true;
|
||||||
|
if (findNoCase("/api/logout.cfm", request._api_path)) request._api_isPublic = true;
|
||||||
|
|
||||||
|
if (findNoCase("/api/auth/login.cfm", request._api_path)) request._api_isPublic = true;
|
||||||
|
if (findNoCase("/api/auth/logout.cfm", request._api_path)) request._api_isPublic = true;
|
||||||
|
|
||||||
|
if (findNoCase("/api/businesses/list.cfm", request._api_path)) request._api_isPublic = true;
|
||||||
|
if (findNoCase("/api/servicepoints/list.cfm", request._api_path)) request._api_isPublic = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// MVP allowlist: PUBLIC endpoint(s) under /api
|
// Carry session values into request (if present)
|
||||||
isPublicEndpoint = false;
|
|
||||||
if (len(scriptName)) {
|
|
||||||
// Use contains (case-insensitive) so it works whether SCRIPT_NAME is:
|
|
||||||
// /api/servicepoints/list.cfm OR /biz.payfrit.com/api/servicepoints/list.cfm
|
|
||||||
if (findNoCase("/api/businesses/list.cfm", scriptName) GT 0) {
|
|
||||||
isPublicEndpoint = true;
|
|
||||||
}
|
|
||||||
if (findNoCase("/api/servicepoints/list.cfm", scriptName) GT 0) {
|
|
||||||
isPublicEndpoint = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy auth from session if present
|
|
||||||
if (!structKeyExists(request, "UserID") && structKeyExists(session, "UserID")) {
|
if (!structKeyExists(request, "UserID") && structKeyExists(session, "UserID")) {
|
||||||
request.UserID = Duplicate(session.UserID);
|
request.UserID = Duplicate(session.UserID);
|
||||||
}
|
}
|
||||||
|
|
@ -56,13 +73,39 @@ if (!structKeyExists(request, "BusinessID") && structKeyExists(session, "Busines
|
||||||
request.BusinessID = Duplicate(session.BusinessID);
|
request.BusinessID = Duplicate(session.BusinessID);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enforce auth for all /api endpoints EXCEPT allowlisted public endpoints
|
// Token auth: X-User-Token -> request.UserID
|
||||||
if (!isPublicEndpoint) {
|
request._api_userToken = headerValue("X-User-Token");
|
||||||
|
if (len(request._api_userToken)) {
|
||||||
|
try {
|
||||||
|
request._api_qTok = queryExecute(
|
||||||
|
"SELECT UserID FROM UserTokens WHERE Token = ? LIMIT 1",
|
||||||
|
[ { value = request._api_userToken, cfsqltype = "cf_sql_varchar" } ],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (request._api_qTok.recordCount EQ 1) {
|
||||||
|
request.UserID = request._api_qTok.UserID;
|
||||||
|
session.UserID = request._api_qTok.UserID;
|
||||||
|
}
|
||||||
|
} catch (any e) {
|
||||||
|
// ignore; treated as unauthenticated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Business header: X-Business-ID -> request.BusinessID
|
||||||
|
request._api_hdrBiz = headerValue("X-Business-ID");
|
||||||
|
if (len(request._api_hdrBiz) && isNumeric(request._api_hdrBiz)) {
|
||||||
|
request.BusinessID = int(request._api_hdrBiz);
|
||||||
|
session.BusinessID = request.BusinessID;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enforce auth (except public)
|
||||||
|
if (!request._api_isPublic) {
|
||||||
if (!structKeyExists(request, "UserID") || !isNumeric(request.UserID) || request.UserID LTE 0) {
|
if (!structKeyExists(request, "UserID") || !isNumeric(request.UserID) || request.UserID LTE 0) {
|
||||||
apiAbort({ OK=false, ERROR="not_logged_in" });
|
apiAbort({ "OK": false, "ERROR": "not_logged_in" });
|
||||||
}
|
}
|
||||||
if (!structKeyExists(request, "BusinessID") || !isNumeric(request.BusinessID) || request.BusinessID LTE 0) {
|
if (!structKeyExists(request, "BusinessID") || !isNumeric(request.BusinessID) || request.BusinessID LTE 0) {
|
||||||
apiAbort({ OK=false, ERROR="no_business_selected" });
|
apiAbort({ "OK": false, "ERROR": "no_business_selected" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</cfscript>
|
</cfscript>
|
||||||
|
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
<cfsetting showdebugoutput="false">
|
|
||||||
<cfsetting enablecfoutputonly="true">
|
|
||||||
|
|
||||||
<cfcontent type="application/json; charset=utf-8" reset="true">
|
|
||||||
<cfheader name="Cache-Control" value="no-store">
|
|
||||||
|
|
||||||
<cfscript>
|
|
||||||
function apiAbort(obj){ writeOutput(serializeJSON(obj)); abort; }
|
|
||||||
|
|
||||||
function readJsonBody(){
|
|
||||||
raw = toString(getHttpRequestData().content);
|
|
||||||
if (isNull(raw) || len(trim(raw)) EQ 0) return {};
|
|
||||||
try { parsed = deserializeJSON(raw); }
|
|
||||||
catch(any e){ apiAbort({ OK=false, ERROR="bad_json", MESSAGE="Invalid JSON body" }); }
|
|
||||||
if (!isStruct(parsed)) return {};
|
|
||||||
return parsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
data = readJsonBody();
|
|
||||||
|
|
||||||
if (!structKeyExists(request,"UserID") || !isNumeric(request.UserID) || request.UserID LTE 0) apiAbort({OK=false,ERROR="not_logged_in"});
|
|
||||||
if (!structKeyExists(request,"BusinessID") || !isNumeric(request.BusinessID) || request.BusinessID LTE 0) apiAbort({OK=false,ERROR="no_business_selected"});
|
|
||||||
|
|
||||||
if (!structKeyExists(data,"ServicePointID") || !isNumeric(data.ServicePointID) || int(data.ServicePointID) LTE 0) {
|
|
||||||
apiAbort({OK=false,ERROR="missing_servicepoint_id"});
|
|
||||||
}
|
|
||||||
spid = int(data.ServicePointID);
|
|
||||||
</cfscript>
|
|
||||||
|
|
||||||
<cfquery datasource="#application.datasource#">
|
|
||||||
UPDATE ServicePoints
|
|
||||||
SET IsActive = 0
|
|
||||||
WHERE ServicePointID = <cfqueryparam cfsqltype="cf_sql_integer" value="#spid#">
|
|
||||||
AND BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#request.BusinessID#">
|
|
||||||
</cfquery>
|
|
||||||
|
|
||||||
<cfquery name="qCheck" datasource="#application.datasource#">
|
|
||||||
SELECT ServicePointID
|
|
||||||
FROM ServicePoints
|
|
||||||
WHERE ServicePointID = <cfqueryparam cfsqltype="cf_sql_integer" value="#spid#">
|
|
||||||
AND BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#request.BusinessID#">
|
|
||||||
LIMIT 1
|
|
||||||
</cfquery>
|
|
||||||
|
|
||||||
<cfif qCheck.recordCount EQ 0>
|
|
||||||
<cfoutput>#serializeJSON({ OK=false, ERROR="not_found" })#</cfoutput>
|
|
||||||
<cfabort>
|
|
||||||
</cfif>
|
|
||||||
|
|
||||||
<cfoutput>#serializeJSON({ OK=true, ERROR="", ServicePointID=spid })#</cfoutput>
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
<cfsetting showdebugoutput="false">
|
|
||||||
<cfsetting enablecfoutputonly="true">
|
|
||||||
|
|
||||||
<cfcontent type="application/json; charset=utf-8" reset="true">
|
|
||||||
<cfheader name="Cache-Control" value="no-store">
|
|
||||||
|
|
||||||
<cfscript>
|
|
||||||
function apiAbort(obj){ writeOutput(serializeJSON(obj)); abort; }
|
|
||||||
|
|
||||||
function readJsonBody(){
|
|
||||||
raw = toString(getHttpRequestData().content);
|
|
||||||
if (isNull(raw) || len(trim(raw)) EQ 0) return {};
|
|
||||||
try { parsed = deserializeJSON(raw); }
|
|
||||||
catch(any e){ apiAbort({ OK=false, ERROR="bad_json", MESSAGE="Invalid JSON body" }); }
|
|
||||||
if (!isStruct(parsed)) return {};
|
|
||||||
return parsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
data = readJsonBody();
|
|
||||||
|
|
||||||
if (!structKeyExists(request,"UserID") || !isNumeric(request.UserID) || request.UserID LTE 0) apiAbort({OK=false,ERROR="not_logged_in"});
|
|
||||||
if (!structKeyExists(request,"BusinessID") || !isNumeric(request.BusinessID) || request.BusinessID LTE 0) apiAbort({OK=false,ERROR="no_business_selected"});
|
|
||||||
|
|
||||||
if (!structKeyExists(data,"ServicePointID") || !isNumeric(data.ServicePointID) || int(data.ServicePointID) LTE 0) {
|
|
||||||
apiAbort({OK=false,ERROR="missing_servicepoint_id"});
|
|
||||||
}
|
|
||||||
|
|
||||||
spid = int(data.ServicePointID);
|
|
||||||
</cfscript>
|
|
||||||
|
|
||||||
<cfquery name="q" datasource="#application.datasource#">
|
|
||||||
SELECT
|
|
||||||
ServicePointID,
|
|
||||||
BusinessID,
|
|
||||||
ServicePointName,
|
|
||||||
ServicePointTypeID,
|
|
||||||
ServicePointCode,
|
|
||||||
Description,
|
|
||||||
SortOrder,
|
|
||||||
IsActive,
|
|
||||||
CreatedAt,
|
|
||||||
UpdatedAt
|
|
||||||
FROM ServicePoints
|
|
||||||
WHERE ServicePointID = <cfqueryparam cfsqltype="cf_sql_integer" value="#spid#">
|
|
||||||
AND BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#request.BusinessID#">
|
|
||||||
LIMIT 1
|
|
||||||
</cfquery>
|
|
||||||
|
|
||||||
<cfif q.recordCount EQ 0>
|
|
||||||
<cfoutput>#serializeJSON({ OK=false, ERROR="not_found" })#</cfoutput>
|
|
||||||
<cfabort>
|
|
||||||
</cfif>
|
|
||||||
|
|
||||||
<cfset sp = {
|
|
||||||
"ServicePointID"=q.ServicePointID,
|
|
||||||
"BusinessID"=q.BusinessID,
|
|
||||||
"ServicePointName"=q.ServicePointName,
|
|
||||||
"ServicePointTypeID"=q.ServicePointTypeID,
|
|
||||||
"ServicePointCode"=q.ServicePointCode,
|
|
||||||
"Description"=q.Description,
|
|
||||||
"SortOrder"=q.SortOrder,
|
|
||||||
"IsActive"=q.IsActive,
|
|
||||||
"CreatedAt"=(q.CreatedAt & ""),
|
|
||||||
"UpdatedAt"=(q.UpdatedAt & "")
|
|
||||||
}>
|
|
||||||
|
|
||||||
<cfoutput>#serializeJSON({ OK=true, ERROR="", SERVICEPOINT=sp })#</cfoutput>
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
<cfsetting showdebugoutput="false">
|
|
||||||
<cfsetting enablecfoutputonly="true">
|
|
||||||
|
|
||||||
<cfcontent type="application/json; charset=utf-8" reset="true">
|
|
||||||
<cfheader name="Cache-Control" value="no-store">
|
|
||||||
|
|
||||||
<cfscript>
|
|
||||||
function apiAbort(obj) {
|
|
||||||
writeOutput(serializeJSON(obj));
|
|
||||||
abort;
|
|
||||||
}
|
|
||||||
|
|
||||||
function readJsonBody() {
|
|
||||||
raw = toString(getHttpRequestData().content);
|
|
||||||
if (isNull(raw) || len(trim(raw)) EQ 0) return {};
|
|
||||||
try {
|
|
||||||
parsed = deserializeJSON(raw);
|
|
||||||
} catch(any e) {
|
|
||||||
apiAbort({ OK=false, ERROR="bad_json", MESSAGE="Invalid JSON body" });
|
|
||||||
}
|
|
||||||
if (!isStruct(parsed)) return {};
|
|
||||||
return parsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
data = readJsonBody();
|
|
||||||
|
|
||||||
if (!structKeyExists(request, "UserID") || !isNumeric(request.UserID) || request.UserID LTE 0) {
|
|
||||||
apiAbort({ OK=false, ERROR="not_logged_in" });
|
|
||||||
}
|
|
||||||
if (!structKeyExists(request, "BusinessID") || !isNumeric(request.BusinessID) || request.BusinessID LTE 0) {
|
|
||||||
apiAbort({ OK=false, ERROR="no_business_selected" });
|
|
||||||
}
|
|
||||||
|
|
||||||
onlyActive = true;
|
|
||||||
if (structKeyExists(data, "onlyActive")) {
|
|
||||||
if (isBoolean(data.onlyActive)) {
|
|
||||||
onlyActive = data.onlyActive;
|
|
||||||
} else if (isNumeric(data.onlyActive)) {
|
|
||||||
onlyActive = (int(data.onlyActive) EQ 1);
|
|
||||||
} else if (isSimpleValue(data.onlyActive)) {
|
|
||||||
onlyActive = (lcase(trim(toString(data.onlyActive))) EQ "true");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</cfscript>
|
|
||||||
|
|
||||||
<cfquery name="q" datasource="#application.datasource#">
|
|
||||||
SELECT
|
|
||||||
ServicePointID,
|
|
||||||
BusinessID,
|
|
||||||
ServicePointName,
|
|
||||||
ServicePointTypeID,
|
|
||||||
ServicePointCode,
|
|
||||||
Description,
|
|
||||||
SortOrder,
|
|
||||||
IsActive,
|
|
||||||
CreatedAt,
|
|
||||||
UpdatedAt
|
|
||||||
FROM ServicePoints
|
|
||||||
WHERE BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#request.BusinessID#">
|
|
||||||
<cfif onlyActive>
|
|
||||||
AND IsActive = 1
|
|
||||||
</cfif>
|
|
||||||
ORDER BY SortOrder, ServicePointName, ServicePointID
|
|
||||||
</cfquery>
|
|
||||||
|
|
||||||
<cfset sps = []>
|
|
||||||
<cfloop query="q">
|
|
||||||
<cfset arrayAppend(sps, {
|
|
||||||
"ServicePointID"=q.ServicePointID,
|
|
||||||
"BusinessID"=q.BusinessID,
|
|
||||||
"ServicePointName"=q.ServicePointName,
|
|
||||||
"ServicePointTypeID"=q.ServicePointTypeID,
|
|
||||||
"ServicePointCode"=q.ServicePointCode,
|
|
||||||
"Description"=q.Description,
|
|
||||||
"SortOrder"=q.SortOrder,
|
|
||||||
"IsActive"=q.IsActive,
|
|
||||||
"CreatedAt"=(q.CreatedAt & ""),
|
|
||||||
"UpdatedAt"=(q.UpdatedAt & "")
|
|
||||||
})>
|
|
||||||
</cfloop>
|
|
||||||
|
|
||||||
<cfoutput>#serializeJSON({ OK=true, ERROR="", BUSINESSID=(request.BusinessID & ""), COUNT=arrayLen(sps), SERVICEPOINTS=sps })#</cfoutput>
|
|
||||||
|
|
@ -1,136 +0,0 @@
|
||||||
<cfsetting showdebugoutput="false">
|
|
||||||
<cfsetting enablecfoutputonly="true">
|
|
||||||
|
|
||||||
<cfcontent type="application/json; charset=utf-8" reset="true">
|
|
||||||
<cfheader name="Cache-Control" value="no-store">
|
|
||||||
|
|
||||||
<cfscript>
|
|
||||||
function apiAbort(obj){ writeOutput(serializeJSON(obj)); abort; }
|
|
||||||
|
|
||||||
function readJsonBody(){
|
|
||||||
raw = toString(getHttpRequestData().content);
|
|
||||||
if (isNull(raw) || len(trim(raw)) EQ 0) return {};
|
|
||||||
try { parsed = deserializeJSON(raw); }
|
|
||||||
catch(any e){ apiAbort({ OK=false, ERROR="bad_json", MESSAGE="Invalid JSON body" }); }
|
|
||||||
if (!isStruct(parsed)) return {};
|
|
||||||
return parsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
function normStr(v){
|
|
||||||
if (isNull(v)) return "";
|
|
||||||
return trim(toString(v));
|
|
||||||
}
|
|
||||||
|
|
||||||
data = readJsonBody();
|
|
||||||
|
|
||||||
if (!structKeyExists(request,"UserID") || !isNumeric(request.UserID) || request.UserID LTE 0) apiAbort({OK=false,ERROR="not_logged_in"});
|
|
||||||
if (!structKeyExists(request,"BusinessID") || !isNumeric(request.BusinessID) || request.BusinessID LTE 0) apiAbort({OK=false,ERROR="no_business_selected"});
|
|
||||||
|
|
||||||
if (!structKeyExists(data,"ServicePointName") || len(normStr(data.ServicePointName)) EQ 0) {
|
|
||||||
apiAbort({OK=false,ERROR="missing_servicepoint_name",MESSAGE="ServicePointName is required"});
|
|
||||||
}
|
|
||||||
|
|
||||||
spid = 0;
|
|
||||||
if (structKeyExists(data,"ServicePointID") && isNumeric(data.ServicePointID) && int(data.ServicePointID) GT 0) {
|
|
||||||
spid = int(data.ServicePointID);
|
|
||||||
}
|
|
||||||
|
|
||||||
spName = normStr(data.ServicePointName);
|
|
||||||
spTypeID = (structKeyExists(data,"ServicePointTypeID") && isNumeric(data.ServicePointTypeID)) ? int(data.ServicePointTypeID) : 0;
|
|
||||||
spCode = structKeyExists(data,"ServicePointCode") ? normStr(data.ServicePointCode) : "";
|
|
||||||
descr = structKeyExists(data,"Description") ? normStr(data.Description) : "";
|
|
||||||
sortOrd = (structKeyExists(data,"SortOrder") && isNumeric(data.SortOrder)) ? int(data.SortOrder) : 0;
|
|
||||||
|
|
||||||
isActive = 1;
|
|
||||||
if (structKeyExists(data,"IsActive")) {
|
|
||||||
if (isBoolean(data.IsActive)) isActive = (data.IsActive ? 1 : 0);
|
|
||||||
else if (isNumeric(data.IsActive)) isActive = int(data.IsActive);
|
|
||||||
else if (isSimpleValue(data.IsActive)) isActive = (lcase(trim(toString(data.IsActive))) EQ "true" ? 1 : 0);
|
|
||||||
}
|
|
||||||
</cfscript>
|
|
||||||
|
|
||||||
<cfif spid GT 0>
|
|
||||||
<cfquery datasource="#application.datasource#">
|
|
||||||
UPDATE ServicePoints
|
|
||||||
SET
|
|
||||||
ServicePointName = <cfqueryparam cfsqltype="cf_sql_varchar" value="#spName#">,
|
|
||||||
ServicePointTypeID = <cfqueryparam cfsqltype="cf_sql_integer" value="#spTypeID#">,
|
|
||||||
ServicePointCode = <cfqueryparam cfsqltype="cf_sql_varchar" value="#spCode#" null="#(len(spCode) EQ 0)#">,
|
|
||||||
Description = <cfqueryparam cfsqltype="cf_sql_varchar" value="#descr#" null="#(len(descr) EQ 0)#">,
|
|
||||||
SortOrder = <cfqueryparam cfsqltype="cf_sql_integer" value="#sortOrd#">,
|
|
||||||
IsActive = <cfqueryparam cfsqltype="cf_sql_tinyint" value="#isActive#">
|
|
||||||
WHERE ServicePointID = <cfqueryparam cfsqltype="cf_sql_integer" value="#spid#">
|
|
||||||
AND BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#request.BusinessID#">
|
|
||||||
</cfquery>
|
|
||||||
|
|
||||||
<cfquery name="qCheck" datasource="#application.datasource#">
|
|
||||||
SELECT ServicePointID
|
|
||||||
FROM ServicePoints
|
|
||||||
WHERE ServicePointID = <cfqueryparam cfsqltype="cf_sql_integer" value="#spid#">
|
|
||||||
AND BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#request.BusinessID#">
|
|
||||||
LIMIT 1
|
|
||||||
</cfquery>
|
|
||||||
|
|
||||||
<cfif qCheck.recordCount EQ 0>
|
|
||||||
<cfoutput>#serializeJSON({ OK=false, ERROR="not_found" })#</cfoutput>
|
|
||||||
<cfabort>
|
|
||||||
</cfif>
|
|
||||||
<cfelse>
|
|
||||||
<cfquery datasource="#application.datasource#">
|
|
||||||
INSERT INTO ServicePoints (
|
|
||||||
BusinessID,
|
|
||||||
ServicePointName,
|
|
||||||
ServicePointTypeID,
|
|
||||||
ServicePointCode,
|
|
||||||
Description,
|
|
||||||
SortOrder,
|
|
||||||
IsActive
|
|
||||||
) VALUES (
|
|
||||||
<cfqueryparam cfsqltype="cf_sql_integer" value="#request.BusinessID#">,
|
|
||||||
<cfqueryparam cfsqltype="cf_sql_varchar" value="#spName#">,
|
|
||||||
<cfqueryparam cfsqltype="cf_sql_integer" value="#spTypeID#">,
|
|
||||||
<cfqueryparam cfsqltype="cf_sql_varchar" value="#spCode#" null="#(len(spCode) EQ 0)#">,
|
|
||||||
<cfqueryparam cfsqltype="cf_sql_varchar" value="#descr#" null="#(len(descr) EQ 0)#">,
|
|
||||||
<cfqueryparam cfsqltype="cf_sql_integer" value="#sortOrd#">,
|
|
||||||
<cfqueryparam cfsqltype="cf_sql_tinyint" value="#isActive#">
|
|
||||||
)
|
|
||||||
</cfquery>
|
|
||||||
|
|
||||||
<cfquery name="qId" datasource="#application.datasource#">
|
|
||||||
SELECT LAST_INSERT_ID() AS ServicePointID
|
|
||||||
</cfquery>
|
|
||||||
<cfset spid = qId.ServicePointID>
|
|
||||||
</cfif>
|
|
||||||
|
|
||||||
<cfquery name="qOut" datasource="#application.datasource#">
|
|
||||||
SELECT
|
|
||||||
ServicePointID,
|
|
||||||
BusinessID,
|
|
||||||
ServicePointName,
|
|
||||||
ServicePointTypeID,
|
|
||||||
ServicePointCode,
|
|
||||||
Description,
|
|
||||||
SortOrder,
|
|
||||||
IsActive,
|
|
||||||
CreatedAt,
|
|
||||||
UpdatedAt
|
|
||||||
FROM ServicePoints
|
|
||||||
WHERE ServicePointID = <cfqueryparam cfsqltype="cf_sql_integer" value="#spid#">
|
|
||||||
AND BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#request.BusinessID#">
|
|
||||||
LIMIT 1
|
|
||||||
</cfquery>
|
|
||||||
|
|
||||||
<cfset sp = {
|
|
||||||
"ServicePointID"=qOut.ServicePointID,
|
|
||||||
"BusinessID"=qOut.BusinessID,
|
|
||||||
"ServicePointName"=qOut.ServicePointName,
|
|
||||||
"ServicePointTypeID"=qOut.ServicePointTypeID,
|
|
||||||
"ServicePointCode"=qOut.ServicePointCode,
|
|
||||||
"Description"=qOut.Description,
|
|
||||||
"SortOrder"=qOut.SortOrder,
|
|
||||||
"IsActive"=qOut.IsActive,
|
|
||||||
"CreatedAt"=(qOut.CreatedAt & ""),
|
|
||||||
"UpdatedAt"=(qOut.UpdatedAt & "")
|
|
||||||
}>
|
|
||||||
|
|
||||||
<cfoutput>#serializeJSON({ OK=true, ERROR="", SERVICEPOINT=sp })#</cfoutput>
|
|
||||||
114
api/auth/login.cfm
Normal file
114
api/auth/login.cfm
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
<cfsetting showdebugoutput="false">
|
||||||
|
<cfsetting enablecfoutputonly="true">
|
||||||
|
|
||||||
|
<cfscript>
|
||||||
|
/*
|
||||||
|
PATH:
|
||||||
|
C:\lucee\tomcat\webapps\ROOT\biz.payfrit.com\api\auth\login.cfm
|
||||||
|
|
||||||
|
INPUT (JSON):
|
||||||
|
{ "username": "...", "password": "..." }
|
||||||
|
|
||||||
|
OUTPUT (JSON):
|
||||||
|
{ OK:true, ERROR:"", UserID:123, UserFirstName:"...", Token:"..." }
|
||||||
|
|
||||||
|
Uses existing UserTokens table:
|
||||||
|
TokenID (auto), UserID, Token, CreatedAt (DEFAULT CURRENT_TIMESTAMP)
|
||||||
|
-> INSERT does NOT include CreatedAt.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function apiAbort(required struct payload) {
|
||||||
|
writeOutput(serializeJSON(payload));
|
||||||
|
abort;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readJsonBody() {
|
||||||
|
var raw = getHttpRequestData().content;
|
||||||
|
if (isNull(raw)) raw = "";
|
||||||
|
if (!len(trim(raw))) return {};
|
||||||
|
try {
|
||||||
|
var data = deserializeJSON(raw);
|
||||||
|
if (isStruct(data)) return data;
|
||||||
|
} catch (any e) {}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeUsername(required string u) {
|
||||||
|
var x = trim(arguments.u);
|
||||||
|
x = replace(x, " ", "", "all");
|
||||||
|
x = replace(x, "(", "", "all");
|
||||||
|
x = replace(x, ")", "", "all");
|
||||||
|
x = replace(x, "-", "", "all");
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = readJsonBody();
|
||||||
|
var username = structKeyExists(data, "username") ? normalizeUsername("" & data.username) : "";
|
||||||
|
var password = structKeyExists(data, "password") ? ("" & data.password) : "";
|
||||||
|
|
||||||
|
if (!len(username) || !len(password)) {
|
||||||
|
apiAbort({ "OK": false, "ERROR": "missing_fields" });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
var q = queryExecute(
|
||||||
|
"
|
||||||
|
SELECT UserID, UserFirstName
|
||||||
|
FROM Users
|
||||||
|
WHERE
|
||||||
|
(
|
||||||
|
(UserEmailAddress = ?) OR
|
||||||
|
(UserContactNumber = ?)
|
||||||
|
)
|
||||||
|
AND UserPassword = ?
|
||||||
|
AND UserIsEmailVerified = 1
|
||||||
|
AND UserIsContactVerified > 0
|
||||||
|
LIMIT 1
|
||||||
|
",
|
||||||
|
[
|
||||||
|
{ value = username, cfsqltype = "cf_sql_varchar" },
|
||||||
|
{ value = username, cfsqltype = "cf_sql_varchar" },
|
||||||
|
{ value = hash(password), cfsqltype = "cf_sql_varchar" }
|
||||||
|
],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (q.recordCount NEQ 1) {
|
||||||
|
apiAbort({ "OK": false, "ERROR": "bad_credentials" });
|
||||||
|
}
|
||||||
|
|
||||||
|
var token = replace(createUUID(), "-", "", "all");
|
||||||
|
|
||||||
|
queryExecute(
|
||||||
|
"INSERT INTO UserTokens (UserID, Token) VALUES (?, ?)",
|
||||||
|
[
|
||||||
|
{ value = q.UserID, cfsqltype = "cf_sql_integer" },
|
||||||
|
{ value = token, cfsqltype = "cf_sql_varchar" }
|
||||||
|
],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Optional: also set session for browser tools
|
||||||
|
cflock timeout="15" throwontimeout="yes" type="exclusive" scope="session" {
|
||||||
|
session.UserID = q.UserID;
|
||||||
|
}
|
||||||
|
request.UserID = q.UserID;
|
||||||
|
|
||||||
|
writeOutput(serializeJSON({
|
||||||
|
"OK": true,
|
||||||
|
"ERROR": "",
|
||||||
|
"UserID": q.UserID,
|
||||||
|
"UserFirstName": q.UserFirstName,
|
||||||
|
"Token": token
|
||||||
|
}));
|
||||||
|
abort;
|
||||||
|
|
||||||
|
} catch (any e) {
|
||||||
|
apiAbort({
|
||||||
|
"OK": false,
|
||||||
|
"ERROR": "server_error",
|
||||||
|
"MESSAGE": "DB error during login",
|
||||||
|
"DETAIL": e.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</cfscript>
|
||||||
|
|
@ -1,81 +1,52 @@
|
||||||
<cfsetting showdebugoutput="false">
|
<cfsetting showdebugoutput="false">
|
||||||
<cfsetting enablecfoutputonly="true">
|
<cfsetting enablecfoutputonly="true">
|
||||||
|
|
||||||
|
<cfcontent type="application/json; charset=utf-8" reset="true">
|
||||||
|
<cfheader name="Cache-Control" value="no-store">
|
||||||
|
|
||||||
<cfscript>
|
<cfscript>
|
||||||
/*
|
function apiAbort(payload) {
|
||||||
PUBLIC MVP endpoint
|
writeOutput(serializeJSON(payload));
|
||||||
Returns Businesses list with STRICT casing:
|
|
||||||
- Businesses
|
|
||||||
- BusinessID
|
|
||||||
- BusinessName
|
|
||||||
|
|
||||||
Lucee-safe JSON output (NO encodeForJSON)
|
|
||||||
*/
|
|
||||||
|
|
||||||
function jsonString(val) {
|
|
||||||
// serializeJSON("abc") -> "\"abc\""
|
|
||||||
// we strip the surrounding quotes
|
|
||||||
var s = serializeJSON(toString(val));
|
|
||||||
return mid(s, 2, len(s) - 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
function apiAbort(obj) {
|
|
||||||
writeOutput('{');
|
|
||||||
writeOutput('"OK":false');
|
|
||||||
|
|
||||||
if (structKeyExists(obj, "ERROR")) {
|
|
||||||
writeOutput(',"ERROR":"' & jsonString(obj.ERROR) & '"');
|
|
||||||
}
|
|
||||||
if (structKeyExists(obj, "DETAIL")) {
|
|
||||||
writeOutput(',"DETAIL":"' & jsonString(obj.DETAIL) & '"');
|
|
||||||
}
|
|
||||||
|
|
||||||
writeOutput('}');
|
|
||||||
abort;
|
abort;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- datasource resolution (unchanged) ---- */
|
try {
|
||||||
dsn = "";
|
q = queryExecute(
|
||||||
if (structKeyExists(application, "datasource")) {
|
"
|
||||||
dsn = application.datasource;
|
SELECT
|
||||||
} else if (structKeyExists(application, "dsn")) {
|
BusinessID,
|
||||||
dsn = application.dsn;
|
BusinessName
|
||||||
}
|
FROM Businesses
|
||||||
|
ORDER BY BusinessName
|
||||||
|
",
|
||||||
|
[],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
);
|
||||||
|
|
||||||
if (!len(dsn)) {
|
// Convert query -> array of structs (JSON-native)
|
||||||
|
rows = [];
|
||||||
|
for (i = 1; i <= q.recordCount; i++) {
|
||||||
|
arrayAppend(rows, {
|
||||||
|
"BusinessID": q.BusinessID[i],
|
||||||
|
"BusinessName": q.BusinessName[i]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provide BOTH keys to satisfy any Flutter casing expectation
|
||||||
|
writeOutput(serializeJSON({
|
||||||
|
"OK": true,
|
||||||
|
"ERROR": "",
|
||||||
|
"VERSION": "businesses_list_v3",
|
||||||
|
"BUSINESSES": rows,
|
||||||
|
"Businesses": rows
|
||||||
|
}));
|
||||||
|
abort;
|
||||||
|
|
||||||
|
} catch (any e) {
|
||||||
apiAbort({
|
apiAbort({
|
||||||
ERROR = "missing_datasource",
|
"OK": false,
|
||||||
DETAIL = "No datasource configured"
|
"ERROR": "server_error",
|
||||||
|
"DETAIL": e.message
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- query ---- */
|
|
||||||
q = queryExecute(
|
|
||||||
"
|
|
||||||
SELECT
|
|
||||||
BusinessID,
|
|
||||||
BusinessName
|
|
||||||
FROM Businesses
|
|
||||||
ORDER BY BusinessName
|
|
||||||
",
|
|
||||||
[],
|
|
||||||
{ datasource = dsn }
|
|
||||||
);
|
|
||||||
|
|
||||||
/* ---- output ---- */
|
|
||||||
writeOutput('{');
|
|
||||||
writeOutput('"OK":true');
|
|
||||||
writeOutput(',"COUNT":' & q.recordCount);
|
|
||||||
writeOutput(',"Businesses":[');
|
|
||||||
|
|
||||||
for (i = 1; i LTE q.recordCount; i = i + 1) {
|
|
||||||
if (i GT 1) writeOutput(',');
|
|
||||||
|
|
||||||
writeOutput('{');
|
|
||||||
writeOutput('"BusinessID":' & q.BusinessID[i]);
|
|
||||||
writeOutput(',"BusinessName":"' & jsonString(q.BusinessName[i]) & '"');
|
|
||||||
writeOutput('}');
|
|
||||||
}
|
|
||||||
|
|
||||||
writeOutput(']}');
|
|
||||||
</cfscript>
|
</cfscript>
|
||||||
|
|
|
||||||
98
api/menu/items.cfm
Normal file
98
api/menu/items.cfm
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
<cfsetting showdebugoutput="false">
|
||||||
|
<cfsetting enablecfoutputonly="true">
|
||||||
|
|
||||||
|
<cffunction name="readJsonBody" access="public" returntype="struct" output="false">
|
||||||
|
<cfset var raw = getHttpRequestData().content>
|
||||||
|
<cfif isNull(raw) OR len(trim(raw)) EQ 0>
|
||||||
|
<cfreturn {}>
|
||||||
|
</cfif>
|
||||||
|
<cftry>
|
||||||
|
<cfset var data = deserializeJSON(raw)>
|
||||||
|
<cfif isStruct(data)>
|
||||||
|
<cfreturn data>
|
||||||
|
<cfelse>
|
||||||
|
<cfreturn {}>
|
||||||
|
</cfif>
|
||||||
|
<cfcatch>
|
||||||
|
<cfreturn {}>
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="apiAbort" access="public" returntype="void" output="true">
|
||||||
|
<cfargument name="payload" type="struct" required="true">
|
||||||
|
<cfcontent type="application/json; charset=utf-8">
|
||||||
|
<cfoutput>#serializeJSON(arguments.payload)#</cfoutput>
|
||||||
|
<cfabort>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cfset data = readJsonBody()>
|
||||||
|
<cfset BusinessID = 0>
|
||||||
|
<cfif structKeyExists(data, "BusinessID")>
|
||||||
|
<cfset BusinessID = val(data.BusinessID)>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif BusinessID LTE 0>
|
||||||
|
<cfset apiAbort({ "OK": false, "ERROR": "missing_businessid", "MESSAGE": "BusinessID is required.", "DETAIL": "" })>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cftry>
|
||||||
|
<cfset q = queryExecute(
|
||||||
|
"
|
||||||
|
SELECT
|
||||||
|
i.ItemID,
|
||||||
|
i.ItemCategoryID,
|
||||||
|
i.ItemName,
|
||||||
|
i.ItemDescription,
|
||||||
|
i.ItemParentItemID,
|
||||||
|
i.ItemPrice,
|
||||||
|
i.ItemIsActive,
|
||||||
|
i.ItemIsCheckedByDefault,
|
||||||
|
i.ItemRequiresChildSelection,
|
||||||
|
i.ItemMaxNumSelectionReq,
|
||||||
|
i.ItemIsCollapsible,
|
||||||
|
i.ItemSortOrder
|
||||||
|
FROM Items i
|
||||||
|
INNER JOIN Categories c
|
||||||
|
ON c.CategoryID = i.ItemCategoryID
|
||||||
|
WHERE c.CategoryBusinessID = ?
|
||||||
|
ORDER BY i.ItemParentItemID, i.ItemSortOrder, i.ItemID
|
||||||
|
",
|
||||||
|
[ { value = BusinessID, cfsqltype = "cf_sql_integer" } ],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
)>
|
||||||
|
|
||||||
|
<cfset rows = []>
|
||||||
|
<cfloop query="q">
|
||||||
|
<cfset arrayAppend(rows, {
|
||||||
|
"ItemID": q.ItemID,
|
||||||
|
"ItemCategoryID": q.ItemCategoryID,
|
||||||
|
"ItemName": q.ItemName,
|
||||||
|
"ItemDescription": q.ItemDescription,
|
||||||
|
"ItemParentItemID": q.ItemParentItemID,
|
||||||
|
"ItemPrice": q.ItemPrice,
|
||||||
|
"ItemIsActive": q.ItemIsActive,
|
||||||
|
"ItemIsCheckedByDefault": q.ItemIsCheckedByDefault,
|
||||||
|
"ItemRequiresChildSelection": q.ItemRequiresChildSelection,
|
||||||
|
"ItemMaxNumSelectionReq": q.ItemMaxNumSelectionReq,
|
||||||
|
"ItemIsCollapsible": q.ItemIsCollapsible,
|
||||||
|
"ItemSortOrder": q.ItemSortOrder
|
||||||
|
})>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<cfset apiAbort({
|
||||||
|
"OK": true,
|
||||||
|
"ERROR": "",
|
||||||
|
"Items": rows,
|
||||||
|
"COUNT": arrayLen(rows)
|
||||||
|
})>
|
||||||
|
|
||||||
|
<cfcatch>
|
||||||
|
<cfset apiAbort({
|
||||||
|
"OK": false,
|
||||||
|
"ERROR": "server_error",
|
||||||
|
"MESSAGE": "DB error loading items",
|
||||||
|
"DETAIL": cfcatch.message
|
||||||
|
})>
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
135
api/orders/getCart.cfm
Normal file
135
api/orders/getCart.cfm
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
<cfsetting showdebugoutput="false">
|
||||||
|
<cfsetting enablecfoutputonly="true">
|
||||||
|
|
||||||
|
<cffunction name="readJsonBody" access="public" returntype="struct" output="false">
|
||||||
|
<cfset var raw = getHttpRequestData().content>
|
||||||
|
<cfif isNull(raw) OR len(trim(raw)) EQ 0>
|
||||||
|
<cfreturn {}>
|
||||||
|
</cfif>
|
||||||
|
<cftry>
|
||||||
|
<cfset var data = deserializeJSON(raw)>
|
||||||
|
<cfif isStruct(data)>
|
||||||
|
<cfreturn data>
|
||||||
|
<cfelse>
|
||||||
|
<cfreturn {}>
|
||||||
|
</cfif>
|
||||||
|
<cfcatch>
|
||||||
|
<cfreturn {}>
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="apiAbort" access="public" returntype="void" output="true">
|
||||||
|
<cfargument name="payload" type="struct" required="true">
|
||||||
|
<cfcontent type="application/json; charset=utf-8">
|
||||||
|
<cfoutput>#serializeJSON(arguments.payload)#</cfoutput>
|
||||||
|
<cfabort>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cfset data = readJsonBody()>
|
||||||
|
<cfset OrderID = val( structKeyExists(data,"OrderID") ? data.OrderID : 0 )>
|
||||||
|
|
||||||
|
<cfif OrderID LTE 0>
|
||||||
|
<cfset apiAbort({ "OK": false, "ERROR": "missing_orderid", "MESSAGE": "OrderID is required.", "DETAIL": "" })>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cftry>
|
||||||
|
<cfset qOrder = queryExecute(
|
||||||
|
"
|
||||||
|
SELECT
|
||||||
|
OrderID,
|
||||||
|
OrderUUID,
|
||||||
|
OrderUserID,
|
||||||
|
OrderBusinessID,
|
||||||
|
OrderBusinessDeliveryMultiplier,
|
||||||
|
OrderTypeID,
|
||||||
|
OrderDeliveryFee,
|
||||||
|
OrderStatusID,
|
||||||
|
OrderAddressID,
|
||||||
|
OrderPaymentID,
|
||||||
|
OrderRemarks,
|
||||||
|
OrderAddedOn,
|
||||||
|
OrderLastEditedOn,
|
||||||
|
OrderSubmittedOn,
|
||||||
|
OrderServicePointID
|
||||||
|
FROM Orders
|
||||||
|
WHERE OrderID = ?
|
||||||
|
LIMIT 1
|
||||||
|
",
|
||||||
|
[ { value = OrderID, cfsqltype = "cf_sql_integer" } ],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
)>
|
||||||
|
|
||||||
|
<cfif qOrder.recordCount EQ 0>
|
||||||
|
<cfset apiAbort({ "OK": false, "ERROR": "not_found", "MESSAGE": "Order not found.", "DETAIL": "" })>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset qLI = queryExecute(
|
||||||
|
"
|
||||||
|
SELECT
|
||||||
|
OrderLineItemID,
|
||||||
|
OrderLineItemParentOrderLineItemID,
|
||||||
|
OrderLineItemOrderID,
|
||||||
|
OrderLineItemItemID,
|
||||||
|
OrderLineItemStatusID,
|
||||||
|
OrderLineItemPrice,
|
||||||
|
OrderLineItemQuantity,
|
||||||
|
OrderLineItemRemark,
|
||||||
|
OrderLineItemIsDeleted,
|
||||||
|
OrderLineItemAddedOn
|
||||||
|
FROM OrderLineItems
|
||||||
|
WHERE OrderLineItemOrderID = ?
|
||||||
|
ORDER BY OrderLineItemID
|
||||||
|
",
|
||||||
|
[ { value = OrderID, cfsqltype = "cf_sql_integer" } ],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
)>
|
||||||
|
|
||||||
|
<cfset rows = []>
|
||||||
|
<cfloop query="qLI">
|
||||||
|
<cfset arrayAppend(rows, {
|
||||||
|
"OrderLineItemID": qLI.OrderLineItemID,
|
||||||
|
"OrderLineItemParentOrderLineItemID": qLI.OrderLineItemParentOrderLineItemID,
|
||||||
|
"OrderLineItemOrderID": qLI.OrderLineItemOrderID,
|
||||||
|
"OrderLineItemItemID": qLI.OrderLineItemItemID,
|
||||||
|
"OrderLineItemStatusID": qLI.OrderLineItemStatusID,
|
||||||
|
"OrderLineItemPrice": qLI.OrderLineItemPrice,
|
||||||
|
"OrderLineItemQuantity": qLI.OrderLineItemQuantity,
|
||||||
|
"OrderLineItemRemark": qLI.OrderLineItemRemark,
|
||||||
|
"OrderLineItemIsDeleted": qLI.OrderLineItemIsDeleted,
|
||||||
|
"OrderLineItemAddedOn": qLI.OrderLineItemAddedOn
|
||||||
|
})>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<cfset apiAbort({
|
||||||
|
"OK": true,
|
||||||
|
"ERROR": "",
|
||||||
|
"Order": {
|
||||||
|
"OrderID": qOrder.OrderID,
|
||||||
|
"OrderUUID": qOrder.OrderUUID,
|
||||||
|
"OrderUserID": qOrder.OrderUserID,
|
||||||
|
"OrderBusinessID": qOrder.OrderBusinessID,
|
||||||
|
"OrderBusinessDeliveryMultiplier": qOrder.OrderBusinessDeliveryMultiplier,
|
||||||
|
"OrderTypeID": qOrder.OrderTypeID,
|
||||||
|
"OrderDeliveryFee": qOrder.OrderDeliveryFee,
|
||||||
|
"OrderStatusID": qOrder.OrderStatusID,
|
||||||
|
"OrderAddressID": qOrder.OrderAddressID,
|
||||||
|
"OrderPaymentID": qOrder.OrderPaymentID,
|
||||||
|
"OrderRemarks": qOrder.OrderRemarks,
|
||||||
|
"OrderAddedOn": qOrder.OrderAddedOn,
|
||||||
|
"OrderLastEditedOn": qOrder.OrderLastEditedOn,
|
||||||
|
"OrderSubmittedOn": qOrder.OrderSubmittedOn,
|
||||||
|
"OrderServicePointID": qOrder.OrderServicePointID
|
||||||
|
},
|
||||||
|
"OrderLineItems": rows
|
||||||
|
})>
|
||||||
|
|
||||||
|
<cfcatch>
|
||||||
|
<cfset apiAbort({
|
||||||
|
"OK": false,
|
||||||
|
"ERROR": "server_error",
|
||||||
|
"MESSAGE": "DB error loading cart",
|
||||||
|
"DETAIL": cfcatch.message
|
||||||
|
})>
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
263
api/orders/getOrCreateCart.cfm
Normal file
263
api/orders/getOrCreateCart.cfm
Normal file
|
|
@ -0,0 +1,263 @@
|
||||||
|
<cfsetting showdebugoutput="false">
|
||||||
|
<cfsetting enablecfoutputonly="true">
|
||||||
|
|
||||||
|
<cffunction name="readJsonBody" access="public" returntype="struct" output="false">
|
||||||
|
<cfset var raw = getHttpRequestData().content>
|
||||||
|
<cfif isNull(raw) OR len(trim(raw)) EQ 0>
|
||||||
|
<cfreturn {}>
|
||||||
|
</cfif>
|
||||||
|
<cftry>
|
||||||
|
<cfset var data = deserializeJSON(raw)>
|
||||||
|
<cfif isStruct(data)>
|
||||||
|
<cfreturn data>
|
||||||
|
<cfelse>
|
||||||
|
<cfreturn {}>
|
||||||
|
</cfif>
|
||||||
|
<cfcatch>
|
||||||
|
<cfreturn {}>
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="apiAbort" access="public" returntype="void" output="true">
|
||||||
|
<cfargument name="payload" type="struct" required="true">
|
||||||
|
<cfcontent type="application/json; charset=utf-8">
|
||||||
|
<cfoutput>#serializeJSON(arguments.payload)#</cfoutput>
|
||||||
|
<cfabort>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="loadCartPayload" access="public" returntype="struct" output="false">
|
||||||
|
<cfargument name="OrderID" type="numeric" required="true">
|
||||||
|
|
||||||
|
<cfset var out = {}>
|
||||||
|
<cfset var qOrder = queryExecute(
|
||||||
|
"
|
||||||
|
SELECT
|
||||||
|
OrderID,
|
||||||
|
OrderUUID,
|
||||||
|
OrderUserID,
|
||||||
|
OrderBusinessID,
|
||||||
|
OrderBusinessDeliveryMultiplier,
|
||||||
|
OrderTypeID,
|
||||||
|
OrderDeliveryFee,
|
||||||
|
OrderStatusID,
|
||||||
|
OrderAddressID,
|
||||||
|
OrderPaymentID,
|
||||||
|
OrderRemarks,
|
||||||
|
OrderAddedOn,
|
||||||
|
OrderLastEditedOn,
|
||||||
|
OrderSubmittedOn,
|
||||||
|
OrderServicePointID
|
||||||
|
FROM Orders
|
||||||
|
WHERE OrderID = ?
|
||||||
|
LIMIT 1
|
||||||
|
",
|
||||||
|
[ { value = arguments.OrderID, cfsqltype = "cf_sql_integer" } ],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
)>
|
||||||
|
|
||||||
|
<cfif qOrder.recordCount EQ 0>
|
||||||
|
<cfreturn { "OK": false, "ERROR": "not_found", "MESSAGE": "Order not found", "DETAIL": "" }>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset out.Order = {
|
||||||
|
"OrderID": qOrder.OrderID,
|
||||||
|
"OrderUUID": qOrder.OrderUUID,
|
||||||
|
"OrderUserID": qOrder.OrderUserID,
|
||||||
|
"OrderBusinessID": qOrder.OrderBusinessID,
|
||||||
|
"OrderBusinessDeliveryMultiplier": qOrder.OrderBusinessDeliveryMultiplier,
|
||||||
|
"OrderTypeID": qOrder.OrderTypeID,
|
||||||
|
"OrderDeliveryFee": qOrder.OrderDeliveryFee,
|
||||||
|
"OrderStatusID": qOrder.OrderStatusID,
|
||||||
|
"OrderAddressID": qOrder.OrderAddressID,
|
||||||
|
"OrderPaymentID": qOrder.OrderPaymentID,
|
||||||
|
"OrderRemarks": qOrder.OrderRemarks,
|
||||||
|
"OrderAddedOn": qOrder.OrderAddedOn,
|
||||||
|
"OrderLastEditedOn": qOrder.OrderLastEditedOn,
|
||||||
|
"OrderSubmittedOn": qOrder.OrderSubmittedOn,
|
||||||
|
"OrderServicePointID": qOrder.OrderServicePointID
|
||||||
|
}>
|
||||||
|
|
||||||
|
<cfset var qLI = queryExecute(
|
||||||
|
"
|
||||||
|
SELECT
|
||||||
|
OrderLineItemID,
|
||||||
|
OrderLineItemParentOrderLineItemID,
|
||||||
|
OrderLineItemOrderID,
|
||||||
|
OrderLineItemItemID,
|
||||||
|
OrderLineItemStatusID,
|
||||||
|
OrderLineItemPrice,
|
||||||
|
OrderLineItemQuantity,
|
||||||
|
OrderLineItemRemark,
|
||||||
|
OrderLineItemIsDeleted,
|
||||||
|
OrderLineItemAddedOn
|
||||||
|
FROM OrderLineItems
|
||||||
|
WHERE OrderLineItemOrderID = ?
|
||||||
|
ORDER BY OrderLineItemID
|
||||||
|
",
|
||||||
|
[ { value = arguments.OrderID, cfsqltype = "cf_sql_integer" } ],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
)>
|
||||||
|
|
||||||
|
<cfset var rows = []>
|
||||||
|
<cfloop query="qLI">
|
||||||
|
<cfset arrayAppend(rows, {
|
||||||
|
"OrderLineItemID": qLI.OrderLineItemID,
|
||||||
|
"OrderLineItemParentOrderLineItemID": qLI.OrderLineItemParentOrderLineItemID,
|
||||||
|
"OrderLineItemOrderID": qLI.OrderLineItemOrderID,
|
||||||
|
"OrderLineItemItemID": qLI.OrderLineItemItemID,
|
||||||
|
"OrderLineItemStatusID": qLI.OrderLineItemStatusID,
|
||||||
|
"OrderLineItemPrice": qLI.OrderLineItemPrice,
|
||||||
|
"OrderLineItemQuantity": qLI.OrderLineItemQuantity,
|
||||||
|
"OrderLineItemRemark": qLI.OrderLineItemRemark,
|
||||||
|
"OrderLineItemIsDeleted": qLI.OrderLineItemIsDeleted,
|
||||||
|
"OrderLineItemAddedOn": qLI.OrderLineItemAddedOn
|
||||||
|
})>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<cfset out.OrderLineItems = rows>
|
||||||
|
<cfset out.OK = true>
|
||||||
|
<cfset out.ERROR = "">
|
||||||
|
<cfreturn out>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cfset data = readJsonBody()>
|
||||||
|
|
||||||
|
<cfset BusinessID = val( structKeyExists(data,"BusinessID") ? data.BusinessID : 0 )>
|
||||||
|
<cfset OrderServicePointID = val( structKeyExists(data,"OrderServicePointID") ? data.OrderServicePointID : 0 )>
|
||||||
|
<cfset OrderTypeID = val( structKeyExists(data,"OrderTypeID") ? data.OrderTypeID : 0 )>
|
||||||
|
<cfset OrderUserID = val( structKeyExists(data,"OrderUserID") ? data.OrderUserID : 0 )>
|
||||||
|
|
||||||
|
<cfif BusinessID LTE 0 OR OrderServicePointID LTE 0 OR OrderTypeID NEQ 1 OR OrderUserID LTE 0>
|
||||||
|
<cfset apiAbort({
|
||||||
|
"OK": false,
|
||||||
|
"ERROR": "missing_params",
|
||||||
|
"MESSAGE": "BusinessID, OrderServicePointID, OrderTypeID=1, and OrderUserID are required.",
|
||||||
|
"DETAIL": ""
|
||||||
|
})>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cftry>
|
||||||
|
<!--- Find existing cart (OrderStatusID=0 assumed cart) --->
|
||||||
|
<cfset qFind = queryExecute(
|
||||||
|
"
|
||||||
|
SELECT OrderID
|
||||||
|
FROM Orders
|
||||||
|
WHERE OrderUserID = ?
|
||||||
|
AND OrderBusinessID = ?
|
||||||
|
AND OrderTypeID = 1
|
||||||
|
AND OrderStatusID = 0
|
||||||
|
AND OrderServicePointID = ?
|
||||||
|
ORDER BY OrderID DESC
|
||||||
|
LIMIT 1
|
||||||
|
",
|
||||||
|
[
|
||||||
|
{ value = OrderUserID, cfsqltype = "cf_sql_integer" },
|
||||||
|
{ value = BusinessID, cfsqltype = "cf_sql_integer" },
|
||||||
|
{ value = OrderServicePointID, cfsqltype = "cf_sql_integer" }
|
||||||
|
],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
)>
|
||||||
|
|
||||||
|
<cfif qFind.recordCount GT 0>
|
||||||
|
<cfset payload = loadCartPayload(qFind.OrderID)>
|
||||||
|
<cfset apiAbort(payload)>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<!--- Create new cart order --->
|
||||||
|
<cfset qBiz = queryExecute(
|
||||||
|
"
|
||||||
|
SELECT BusinessDeliveryMultiplier, BusinessDeliveryFlatFee
|
||||||
|
FROM Businesses
|
||||||
|
WHERE BusinessID = ?
|
||||||
|
LIMIT 1
|
||||||
|
",
|
||||||
|
[ { value = BusinessID, cfsqltype = "cf_sql_integer" } ],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
)>
|
||||||
|
|
||||||
|
<cfif qBiz.recordCount EQ 0>
|
||||||
|
<cfset apiAbort({ "OK": false, "ERROR": "bad_business", "MESSAGE": "Business not found", "DETAIL": "" })>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset nowDt = now()>
|
||||||
|
<cfset newUUID = createUUID()>
|
||||||
|
|
||||||
|
<!--- Generate new OrderID (table is not auto-inc in SSOT) --->
|
||||||
|
<cfset qNext = queryExecute(
|
||||||
|
"SELECT IFNULL(MAX(OrderID),0) + 1 AS NextID FROM Orders",
|
||||||
|
[],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
)>
|
||||||
|
<cfset NewOrderID = qNext.NextID>
|
||||||
|
|
||||||
|
<cfset queryExecute(
|
||||||
|
"
|
||||||
|
INSERT INTO Orders (
|
||||||
|
OrderID,
|
||||||
|
OrderUUID,
|
||||||
|
OrderUserID,
|
||||||
|
OrderBusinessID,
|
||||||
|
OrderBusinessDeliveryMultiplier,
|
||||||
|
OrderTypeID,
|
||||||
|
OrderDeliveryFee,
|
||||||
|
OrderStatusID,
|
||||||
|
OrderAddressID,
|
||||||
|
OrderPaymentID,
|
||||||
|
OrderRemarks,
|
||||||
|
OrderAddedOn,
|
||||||
|
OrderLastEditedOn,
|
||||||
|
OrderSubmittedOn,
|
||||||
|
OrderServicePointID
|
||||||
|
) VALUES (
|
||||||
|
?,
|
||||||
|
?,
|
||||||
|
?,
|
||||||
|
?,
|
||||||
|
?,
|
||||||
|
1,
|
||||||
|
?,
|
||||||
|
0,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
?,
|
||||||
|
?,
|
||||||
|
NULL,
|
||||||
|
?
|
||||||
|
)
|
||||||
|
",
|
||||||
|
[
|
||||||
|
{ value = NewOrderID, cfsqltype = "cf_sql_integer" },
|
||||||
|
{ value = newUUID, cfsqltype = "cf_sql_varchar" },
|
||||||
|
{ value = OrderUserID, cfsqltype = "cf_sql_integer" },
|
||||||
|
{ value = BusinessID, cfsqltype = "cf_sql_integer" },
|
||||||
|
{ value = qBiz.BusinessDeliveryMultiplier, cfsqltype = "cf_sql_decimal" },
|
||||||
|
{ value = qBiz.BusinessDeliveryFlatFee, cfsqltype = "cf_sql_decimal" },
|
||||||
|
{ value = nowDt, cfsqltype = "cf_sql_timestamp" },
|
||||||
|
{ value = nowDt, cfsqltype = "cf_sql_timestamp" },
|
||||||
|
{ value = OrderServicePointID, cfsqltype = "cf_sql_integer" }
|
||||||
|
],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
)>
|
||||||
|
|
||||||
|
<!--- Per your rule: OrderID is determined by selecting highest after creation --->
|
||||||
|
<cfset qLatest = queryExecute(
|
||||||
|
"SELECT MAX(OrderID) AS OrderID FROM Orders",
|
||||||
|
[],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
)>
|
||||||
|
<cfset FinalOrderID = qLatest.OrderID>
|
||||||
|
|
||||||
|
<cfset payload = loadCartPayload(FinalOrderID)>
|
||||||
|
<cfset apiAbort(payload)>
|
||||||
|
|
||||||
|
<cfcatch>
|
||||||
|
<cfset apiAbort({
|
||||||
|
"OK": false,
|
||||||
|
"ERROR": "server_error",
|
||||||
|
"MESSAGE": "DB error creating cart",
|
||||||
|
"DETAIL": cfcatch.message
|
||||||
|
})>
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
399
api/orders/setLineItem.cfm
Normal file
399
api/orders/setLineItem.cfm
Normal file
|
|
@ -0,0 +1,399 @@
|
||||||
|
<cfsetting showdebugoutput="false">
|
||||||
|
<cfsetting enablecfoutputonly="true">
|
||||||
|
|
||||||
|
<cffunction name="readJsonBody" access="public" returntype="struct" output="false">
|
||||||
|
<cfset var raw = getHttpRequestData().content>
|
||||||
|
<cfif isNull(raw) OR len(trim(raw)) EQ 0>
|
||||||
|
<cfreturn {}>
|
||||||
|
</cfif>
|
||||||
|
<cftry>
|
||||||
|
<cfset var data = deserializeJSON(raw)>
|
||||||
|
<cfif isStruct(data)>
|
||||||
|
<cfreturn data>
|
||||||
|
<cfelse>
|
||||||
|
<cfreturn {}>
|
||||||
|
</cfif>
|
||||||
|
<cfcatch>
|
||||||
|
<cfreturn {}>
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="apiAbort" access="public" returntype="void" output="true">
|
||||||
|
<cfargument name="payload" type="struct" required="true">
|
||||||
|
<cfcontent type="application/json; charset=utf-8">
|
||||||
|
<cfoutput>#serializeJSON(arguments.payload)#</cfoutput>
|
||||||
|
<cfabort>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="nextId" access="public" returntype="numeric" output="false">
|
||||||
|
<cfargument name="tableName" type="string" required="true">
|
||||||
|
<cfargument name="idField" type="string" required="true">
|
||||||
|
|
||||||
|
<cfset var q = queryExecute(
|
||||||
|
"SELECT IFNULL(MAX(#arguments.idField#),0) + 1 AS NextID FROM #arguments.tableName#",
|
||||||
|
[],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
)>
|
||||||
|
<cfreturn q.NextID>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="attachDefaultChildren" access="public" returntype="void" output="false">
|
||||||
|
<cfargument name="OrderID" type="numeric" required="true">
|
||||||
|
<cfargument name="ParentLineItemID" type="numeric" required="true">
|
||||||
|
<cfargument name="ParentItemID" type="numeric" required="true">
|
||||||
|
|
||||||
|
<!--- Find immediate children where checked by default --->
|
||||||
|
<cfset var qKids = queryExecute(
|
||||||
|
"
|
||||||
|
SELECT ItemID, ItemPrice
|
||||||
|
FROM Items
|
||||||
|
WHERE ItemParentItemID = ?
|
||||||
|
AND ItemIsCheckedByDefault = 1
|
||||||
|
AND ItemIsActive = b'1'
|
||||||
|
ORDER BY ItemSortOrder, ItemID
|
||||||
|
",
|
||||||
|
[ { value = arguments.ParentItemID, cfsqltype = "cf_sql_integer" } ],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
)>
|
||||||
|
|
||||||
|
<cfloop query="qKids">
|
||||||
|
<!--- If existing, undelete; else insert new --->
|
||||||
|
<cfset var qExisting = queryExecute(
|
||||||
|
"
|
||||||
|
SELECT OrderLineItemID
|
||||||
|
FROM OrderLineItems
|
||||||
|
WHERE OrderLineItemOrderID = ?
|
||||||
|
AND OrderLineItemParentOrderLineItemID = ?
|
||||||
|
AND OrderLineItemItemID = ?
|
||||||
|
LIMIT 1
|
||||||
|
",
|
||||||
|
[
|
||||||
|
{ value = arguments.OrderID, cfsqltype = "cf_sql_integer" },
|
||||||
|
{ value = arguments.ParentLineItemID, cfsqltype = "cf_sql_integer" },
|
||||||
|
{ value = qKids.ItemID, cfsqltype = "cf_sql_integer" }
|
||||||
|
],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
)>
|
||||||
|
|
||||||
|
<cfif qExisting.recordCount GT 0>
|
||||||
|
<cfset queryExecute(
|
||||||
|
"
|
||||||
|
UPDATE OrderLineItems
|
||||||
|
SET OrderLineItemIsDeleted = b'0'
|
||||||
|
WHERE OrderLineItemID = ?
|
||||||
|
",
|
||||||
|
[ { value = qExisting.OrderLineItemID, cfsqltype = "cf_sql_integer" } ],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
)>
|
||||||
|
<cfset attachDefaultChildren(arguments.OrderID, qExisting.OrderLineItemID, qKids.ItemID)>
|
||||||
|
<cfelse>
|
||||||
|
<cfset var NewLIID = nextId("OrderLineItems","OrderLineItemID")>
|
||||||
|
<cfset queryExecute(
|
||||||
|
"
|
||||||
|
INSERT INTO OrderLineItems (
|
||||||
|
OrderLineItemID,
|
||||||
|
OrderLineItemParentOrderLineItemID,
|
||||||
|
OrderLineItemOrderID,
|
||||||
|
OrderLineItemItemID,
|
||||||
|
OrderLineItemStatusID,
|
||||||
|
OrderLineItemPrice,
|
||||||
|
OrderLineItemQuantity,
|
||||||
|
OrderLineItemRemark,
|
||||||
|
OrderLineItemIsDeleted,
|
||||||
|
OrderLineItemAddedOn
|
||||||
|
) VALUES (
|
||||||
|
?,
|
||||||
|
?,
|
||||||
|
?,
|
||||||
|
?,
|
||||||
|
0,
|
||||||
|
?,
|
||||||
|
1,
|
||||||
|
NULL,
|
||||||
|
b'0',
|
||||||
|
?
|
||||||
|
)
|
||||||
|
",
|
||||||
|
[
|
||||||
|
{ value = NewLIID, cfsqltype = "cf_sql_integer" },
|
||||||
|
{ value = arguments.ParentLineItemID, cfsqltype = "cf_sql_integer" },
|
||||||
|
{ value = arguments.OrderID, cfsqltype = "cf_sql_integer" },
|
||||||
|
{ value = qKids.ItemID, cfsqltype = "cf_sql_integer" },
|
||||||
|
{ value = qKids.ItemPrice, cfsqltype = "cf_sql_decimal" },
|
||||||
|
{ value = now(), cfsqltype = "cf_sql_timestamp" }
|
||||||
|
],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
)>
|
||||||
|
<cfset attachDefaultChildren(arguments.OrderID, NewLIID, qKids.ItemID)>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="loadCartPayload" access="public" returntype="struct" output="false">
|
||||||
|
<cfargument name="OrderID" type="numeric" required="true">
|
||||||
|
|
||||||
|
<cfset var out = {}>
|
||||||
|
<cfset var qOrder = queryExecute(
|
||||||
|
"
|
||||||
|
SELECT
|
||||||
|
OrderID,
|
||||||
|
OrderUUID,
|
||||||
|
OrderUserID,
|
||||||
|
OrderBusinessID,
|
||||||
|
OrderBusinessDeliveryMultiplier,
|
||||||
|
OrderTypeID,
|
||||||
|
OrderDeliveryFee,
|
||||||
|
OrderStatusID,
|
||||||
|
OrderAddressID,
|
||||||
|
OrderPaymentID,
|
||||||
|
OrderRemarks,
|
||||||
|
OrderAddedOn,
|
||||||
|
OrderLastEditedOn,
|
||||||
|
OrderSubmittedOn,
|
||||||
|
OrderServicePointID
|
||||||
|
FROM Orders
|
||||||
|
WHERE OrderID = ?
|
||||||
|
LIMIT 1
|
||||||
|
",
|
||||||
|
[ { value = arguments.OrderID, cfsqltype = "cf_sql_integer" } ],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
)>
|
||||||
|
|
||||||
|
<cfif qOrder.recordCount EQ 0>
|
||||||
|
<cfreturn { "OK": false, "ERROR": "not_found", "MESSAGE": "Order not found", "DETAIL": "" }>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset out.Order = {
|
||||||
|
"OrderID": qOrder.OrderID,
|
||||||
|
"OrderUUID": qOrder.OrderUUID,
|
||||||
|
"OrderUserID": qOrder.OrderUserID,
|
||||||
|
"OrderBusinessID": qOrder.OrderBusinessID,
|
||||||
|
"OrderBusinessDeliveryMultiplier": qOrder.OrderBusinessDeliveryMultiplier,
|
||||||
|
"OrderTypeID": qOrder.OrderTypeID,
|
||||||
|
"OrderDeliveryFee": qOrder.OrderDeliveryFee,
|
||||||
|
"OrderStatusID": qOrder.OrderStatusID,
|
||||||
|
"OrderAddressID": qOrder.OrderAddressID,
|
||||||
|
"OrderPaymentID": qOrder.OrderPaymentID,
|
||||||
|
"OrderRemarks": qOrder.OrderRemarks,
|
||||||
|
"OrderAddedOn": qOrder.OrderAddedOn,
|
||||||
|
"OrderLastEditedOn": qOrder.OrderLastEditedOn,
|
||||||
|
"OrderSubmittedOn": qOrder.OrderSubmittedOn,
|
||||||
|
"OrderServicePointID": qOrder.OrderServicePointID
|
||||||
|
}>
|
||||||
|
|
||||||
|
<cfset var qLI = queryExecute(
|
||||||
|
"
|
||||||
|
SELECT
|
||||||
|
OrderLineItemID,
|
||||||
|
OrderLineItemParentOrderLineItemID,
|
||||||
|
OrderLineItemOrderID,
|
||||||
|
OrderLineItemItemID,
|
||||||
|
OrderLineItemStatusID,
|
||||||
|
OrderLineItemPrice,
|
||||||
|
OrderLineItemQuantity,
|
||||||
|
OrderLineItemRemark,
|
||||||
|
OrderLineItemIsDeleted,
|
||||||
|
OrderLineItemAddedOn
|
||||||
|
FROM OrderLineItems
|
||||||
|
WHERE OrderLineItemOrderID = ?
|
||||||
|
ORDER BY OrderLineItemID
|
||||||
|
",
|
||||||
|
[ { value = arguments.OrderID, cfsqltype = "cf_sql_integer" } ],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
)>
|
||||||
|
|
||||||
|
<cfset var rows = []>
|
||||||
|
<cfloop query="qLI">
|
||||||
|
<cfset arrayAppend(rows, {
|
||||||
|
"OrderLineItemID": qLI.OrderLineItemID,
|
||||||
|
"OrderLineItemParentOrderLineItemID": qLI.OrderLineItemParentOrderLineItemID,
|
||||||
|
"OrderLineItemOrderID": qLI.OrderLineItemOrderID,
|
||||||
|
"OrderLineItemItemID": qLI.OrderLineItemItemID,
|
||||||
|
"OrderLineItemStatusID": qLI.OrderLineItemStatusID,
|
||||||
|
"OrderLineItemPrice": qLI.OrderLineItemPrice,
|
||||||
|
"OrderLineItemQuantity": qLI.OrderLineItemQuantity,
|
||||||
|
"OrderLineItemRemark": qLI.OrderLineItemRemark,
|
||||||
|
"OrderLineItemIsDeleted": qLI.OrderLineItemIsDeleted,
|
||||||
|
"OrderLineItemAddedOn": qLI.OrderLineItemAddedOn
|
||||||
|
})>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<cfset out.OrderLineItems = rows>
|
||||||
|
<cfset out.OK = true>
|
||||||
|
<cfset out.ERROR = "">
|
||||||
|
<cfreturn out>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cfset data = readJsonBody()>
|
||||||
|
|
||||||
|
<cfset OrderID = val( structKeyExists(data,"OrderID") ? data.OrderID : 0 )>
|
||||||
|
<cfset ParentLineItemID = val( structKeyExists(data,"ParentOrderLineItemID") ? data.ParentOrderLineItemID : 0 )>
|
||||||
|
<cfset ItemID = val( structKeyExists(data,"ItemID") ? data.ItemID : 0 )>
|
||||||
|
<cfset IsSelected = false>
|
||||||
|
<cfif structKeyExists(data, "IsSelected")>
|
||||||
|
<cfset IsSelected = (data.IsSelected EQ true OR data.IsSelected EQ 1 OR (isSimpleValue(data.IsSelected) AND lcase(toString(data.IsSelected)) EQ "true"))>
|
||||||
|
</cfif>
|
||||||
|
<cfset Quantity = structKeyExists(data,"Quantity") ? val(data.Quantity) : 0>
|
||||||
|
<cfset Remark = structKeyExists(data,"Remark") ? toString(data.Remark) : "">
|
||||||
|
|
||||||
|
<cfif OrderID LTE 0 OR ItemID LTE 0>
|
||||||
|
<cfset apiAbort({ "OK": false, "ERROR": "missing_params", "MESSAGE": "OrderID and ItemID are required.", "DETAIL": "" })>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cftry>
|
||||||
|
<!--- Load item price --->
|
||||||
|
<cfset qItem = queryExecute(
|
||||||
|
"
|
||||||
|
SELECT ItemID, ItemPrice, ItemParentItemID, ItemIsActive
|
||||||
|
FROM Items
|
||||||
|
WHERE ItemID = ?
|
||||||
|
LIMIT 1
|
||||||
|
",
|
||||||
|
[ { value = ItemID, cfsqltype = "cf_sql_integer" } ],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
)>
|
||||||
|
|
||||||
|
<cfif qItem.recordCount EQ 0 OR qItem.ItemIsActive NEQ true>
|
||||||
|
<cfset apiAbort({ "OK": false, "ERROR": "bad_item", "MESSAGE": "Item not found or inactive.", "DETAIL": "" })>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<!--- Root vs modifier rules --->
|
||||||
|
<cfif ParentLineItemID EQ 0>
|
||||||
|
<!--- Root item quantity required when selecting --->
|
||||||
|
<cfif IsSelected AND Quantity LTE 0>
|
||||||
|
<cfset apiAbort({ "OK": false, "ERROR": "bad_quantity", "MESSAGE": "Root line items require Quantity > 0.", "DETAIL": "" })>
|
||||||
|
</cfif>
|
||||||
|
<cfelse>
|
||||||
|
<!--- Modifier quantity is implicitly tied => force 1 when selecting --->
|
||||||
|
<cfif IsSelected>
|
||||||
|
<cfset Quantity = 1>
|
||||||
|
<cfelse>
|
||||||
|
<cfset Quantity = 1>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<!--- Find existing line item (by order, parent LI, item) --->
|
||||||
|
<cfset qExisting = queryExecute(
|
||||||
|
"
|
||||||
|
SELECT OrderLineItemID
|
||||||
|
FROM OrderLineItems
|
||||||
|
WHERE OrderLineItemOrderID = ?
|
||||||
|
AND OrderLineItemParentOrderLineItemID = ?
|
||||||
|
AND OrderLineItemItemID = ?
|
||||||
|
LIMIT 1
|
||||||
|
",
|
||||||
|
[
|
||||||
|
{ value = OrderID, cfsqltype = "cf_sql_integer" },
|
||||||
|
{ value = ParentLineItemID, cfsqltype = "cf_sql_integer" },
|
||||||
|
{ value = ItemID, cfsqltype = "cf_sql_integer" }
|
||||||
|
],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
)>
|
||||||
|
|
||||||
|
<cfif qExisting.recordCount GT 0>
|
||||||
|
<!--- Update existing --->
|
||||||
|
<cfif IsSelected>
|
||||||
|
<cfset queryExecute(
|
||||||
|
"
|
||||||
|
UPDATE OrderLineItems
|
||||||
|
SET
|
||||||
|
OrderLineItemIsDeleted = b'0',
|
||||||
|
OrderLineItemQuantity = ?,
|
||||||
|
OrderLineItemPrice = ?,
|
||||||
|
OrderLineItemRemark = ?,
|
||||||
|
OrderLineItemStatusID = 0
|
||||||
|
WHERE OrderLineItemID = ?
|
||||||
|
",
|
||||||
|
[
|
||||||
|
{ value = Quantity, cfsqltype = "cf_sql_integer" },
|
||||||
|
{ value = qItem.ItemPrice, cfsqltype = "cf_sql_decimal" },
|
||||||
|
{ value = (len(trim(Remark)) EQ 0 ? javacast("null","") : Remark), cfsqltype = "cf_sql_varchar", null = (len(trim(Remark)) EQ 0) },
|
||||||
|
{ value = qExisting.OrderLineItemID, cfsqltype = "cf_sql_integer" }
|
||||||
|
],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
)>
|
||||||
|
|
||||||
|
<!--- Attach default children for this node (recursively) --->
|
||||||
|
<cfset attachDefaultChildren(OrderID, qExisting.OrderLineItemID, ItemID)>
|
||||||
|
<cfelse>
|
||||||
|
<cfset queryExecute(
|
||||||
|
"
|
||||||
|
UPDATE OrderLineItems
|
||||||
|
SET OrderLineItemIsDeleted = b'1'
|
||||||
|
WHERE OrderLineItemID = ?
|
||||||
|
",
|
||||||
|
[ { value = qExisting.OrderLineItemID, cfsqltype = "cf_sql_integer" } ],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
)>
|
||||||
|
</cfif>
|
||||||
|
<cfelse>
|
||||||
|
<!--- Insert new if selecting, otherwise no-op --->
|
||||||
|
<cfif IsSelected>
|
||||||
|
<cfset NewLIID = nextId("OrderLineItems","OrderLineItemID")>
|
||||||
|
|
||||||
|
<cfset queryExecute(
|
||||||
|
"
|
||||||
|
INSERT INTO OrderLineItems (
|
||||||
|
OrderLineItemID,
|
||||||
|
OrderLineItemParentOrderLineItemID,
|
||||||
|
OrderLineItemOrderID,
|
||||||
|
OrderLineItemItemID,
|
||||||
|
OrderLineItemStatusID,
|
||||||
|
OrderLineItemPrice,
|
||||||
|
OrderLineItemQuantity,
|
||||||
|
OrderLineItemRemark,
|
||||||
|
OrderLineItemIsDeleted,
|
||||||
|
OrderLineItemAddedOn
|
||||||
|
) VALUES (
|
||||||
|
?,
|
||||||
|
?,
|
||||||
|
?,
|
||||||
|
?,
|
||||||
|
0,
|
||||||
|
?,
|
||||||
|
?,
|
||||||
|
?,
|
||||||
|
b'0',
|
||||||
|
?
|
||||||
|
)
|
||||||
|
",
|
||||||
|
[
|
||||||
|
{ value = NewLIID, cfsqltype = "cf_sql_integer" },
|
||||||
|
{ value = ParentLineItemID, cfsqltype = "cf_sql_integer" },
|
||||||
|
{ value = OrderID, cfsqltype = "cf_sql_integer" },
|
||||||
|
{ value = ItemID, cfsqltype = "cf_sql_integer" },
|
||||||
|
{ value = qItem.ItemPrice, cfsqltype = "cf_sql_decimal" },
|
||||||
|
{ value = (ParentLineItemID EQ 0 ? Quantity : 1), cfsqltype = "cf_sql_integer" },
|
||||||
|
{ value = (len(trim(Remark)) EQ 0 ? javacast("null","") : Remark), cfsqltype = "cf_sql_varchar", null = (len(trim(Remark)) EQ 0) },
|
||||||
|
{ value = now(), cfsqltype = "cf_sql_timestamp" }
|
||||||
|
],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
)>
|
||||||
|
|
||||||
|
<cfset attachDefaultChildren(OrderID, NewLIID, ItemID)>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<!--- Touch order last edited --->
|
||||||
|
<cfset queryExecute(
|
||||||
|
"UPDATE Orders SET OrderLastEditedOn = ? WHERE OrderID = ?",
|
||||||
|
[
|
||||||
|
{ value = now(), cfsqltype = "cf_sql_timestamp" },
|
||||||
|
{ value = OrderID, cfsqltype = "cf_sql_integer" }
|
||||||
|
],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
)>
|
||||||
|
|
||||||
|
<cfset payload = loadCartPayload(OrderID)>
|
||||||
|
<cfset apiAbort(payload)>
|
||||||
|
|
||||||
|
<cfcatch>
|
||||||
|
<cfset apiAbort({
|
||||||
|
"OK": false,
|
||||||
|
"ERROR": "server_error",
|
||||||
|
"MESSAGE": "DB error setting line item",
|
||||||
|
"DETAIL": cfcatch.message
|
||||||
|
})>
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
261
api/orders/submit.cfm
Normal file
261
api/orders/submit.cfm
Normal file
|
|
@ -0,0 +1,261 @@
|
||||||
|
<cfsetting showdebugoutput="false">
|
||||||
|
<cfsetting enablecfoutputonly="true">
|
||||||
|
|
||||||
|
<cffunction name="readJsonBody" access="public" returntype="struct" output="false">
|
||||||
|
<cfset var raw = getHttpRequestData().content>
|
||||||
|
<cfif isNull(raw) OR len(trim(raw)) EQ 0>
|
||||||
|
<cfreturn {}>
|
||||||
|
</cfif>
|
||||||
|
<cftry>
|
||||||
|
<cfset var data = deserializeJSON(raw)>
|
||||||
|
<cfif isStruct(data)>
|
||||||
|
<cfreturn data>
|
||||||
|
<cfelse>
|
||||||
|
<cfreturn {}>
|
||||||
|
</cfif>
|
||||||
|
<cfcatch>
|
||||||
|
<cfreturn {}>
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="apiAbort" access="public" returntype="void" output="true">
|
||||||
|
<cfargument name="payload" type="struct" required="true">
|
||||||
|
<cfcontent type="application/json; charset=utf-8">
|
||||||
|
<cfoutput>#serializeJSON(arguments.payload)#</cfoutput>
|
||||||
|
<cfabort>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="buildLineItemsGraph" access="public" returntype="struct" output="false">
|
||||||
|
<cfargument name="OrderID" type="numeric" required="true">
|
||||||
|
|
||||||
|
<cfset var out = {}>
|
||||||
|
<cfset out.items = {}> <!--- lineItemId -> struct --->
|
||||||
|
<cfset out.children = {}> <!--- parentLineItemId -> array(lineItemId) --->
|
||||||
|
<cfset out.itemMeta = {}> <!--- ItemID -> struct(meta) --->
|
||||||
|
|
||||||
|
<cfset var qLI = queryExecute(
|
||||||
|
"
|
||||||
|
SELECT
|
||||||
|
OrderLineItemID,
|
||||||
|
OrderLineItemParentOrderLineItemID,
|
||||||
|
OrderLineItemItemID,
|
||||||
|
OrderLineItemIsDeleted
|
||||||
|
FROM OrderLineItems
|
||||||
|
WHERE OrderLineItemOrderID = ?
|
||||||
|
ORDER BY OrderLineItemID
|
||||||
|
",
|
||||||
|
[ { value = arguments.OrderID, cfsqltype = "cf_sql_integer" } ],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
)>
|
||||||
|
|
||||||
|
<cfif qLI.recordCount EQ 0>
|
||||||
|
<cfreturn out>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset var itemIds = []>
|
||||||
|
<cfloop query="qLI">
|
||||||
|
<cfset out.items[qLI.OrderLineItemID] = {
|
||||||
|
"id": qLI.OrderLineItemID,
|
||||||
|
"parentId": qLI.OrderLineItemParentOrderLineItemID,
|
||||||
|
"itemId": qLI.OrderLineItemItemID,
|
||||||
|
"isDeleted": (qLI.OrderLineItemIsDeleted EQ true)
|
||||||
|
}>
|
||||||
|
<cfif NOT structKeyExists(out.children, qLI.OrderLineItemParentOrderLineItemID)>
|
||||||
|
<cfset out.children[qLI.OrderLineItemParentOrderLineItemID] = []>
|
||||||
|
</cfif>
|
||||||
|
<cfset arrayAppend(out.children[qLI.OrderLineItemParentOrderLineItemID], qLI.OrderLineItemID)>
|
||||||
|
<cfset arrayAppend(itemIds, qLI.OrderLineItemItemID)>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<!--- Load meta for involved items --->
|
||||||
|
<cfset var uniq = {} >
|
||||||
|
<cfloop array="#itemIds#" index="iid">
|
||||||
|
<cfset uniq[iid] = true>
|
||||||
|
</cfloop>
|
||||||
|
<cfset var uniqIds = structKeyArray(uniq)>
|
||||||
|
<cfif arrayLen(uniqIds) GT 0>
|
||||||
|
<cfset var inList = arrayToList(uniqIds)>
|
||||||
|
<cfset var qMeta = queryExecute(
|
||||||
|
"
|
||||||
|
SELECT
|
||||||
|
ItemID,
|
||||||
|
ItemRequiresChildSelection,
|
||||||
|
ItemMaxNumSelectionReq
|
||||||
|
FROM Items
|
||||||
|
WHERE ItemID IN (#inList#)
|
||||||
|
",
|
||||||
|
[],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
)>
|
||||||
|
|
||||||
|
<cfloop query="qMeta">
|
||||||
|
<cfset out.itemMeta[qMeta.ItemID] = {
|
||||||
|
"requires": qMeta.ItemRequiresChildSelection,
|
||||||
|
"maxSel": qMeta.ItemMaxNumSelectionReq
|
||||||
|
}>
|
||||||
|
</cfloop>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfreturn out>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="hasSelectedDescendant" access="public" returntype="boolean" output="false">
|
||||||
|
<cfargument name="graph" type="struct" required="true">
|
||||||
|
<cfargument name="lineItemId" type="numeric" required="true">
|
||||||
|
|
||||||
|
<cfset var stack = []>
|
||||||
|
<cfif structKeyExists(arguments.graph.children, arguments.lineItemId)>
|
||||||
|
<cfset stack = duplicate(arguments.graph.children[arguments.lineItemId])>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfloop condition="arrayLen(stack) GT 0">
|
||||||
|
<cfset var id = stack[arrayLen(stack)]>
|
||||||
|
<cfset arrayDeleteAt(stack, arrayLen(stack))>
|
||||||
|
|
||||||
|
<cfif structKeyExists(arguments.graph.items, id)>
|
||||||
|
<cfset var node = arguments.graph.items[id]>
|
||||||
|
<cfif NOT node.isDeleted>
|
||||||
|
<cfreturn true>
|
||||||
|
</cfif>
|
||||||
|
<cfif structKeyExists(arguments.graph.children, id)>
|
||||||
|
<cfset var kids = arguments.graph.children[id]>
|
||||||
|
<cfloop array="#kids#" index="kidId">
|
||||||
|
<cfset arrayAppend(stack, kidId)>
|
||||||
|
</cfloop>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<cfreturn false>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cfset data = readJsonBody()>
|
||||||
|
<cfset OrderID = val( structKeyExists(data,"OrderID") ? data.OrderID : 0 )>
|
||||||
|
|
||||||
|
<cfif OrderID LTE 0>
|
||||||
|
<cfset apiAbort({ "OK": false, "ERROR": "missing_orderid", "MESSAGE": "OrderID is required.", "DETAIL": "" })>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cftry>
|
||||||
|
<cfset qOrder = queryExecute(
|
||||||
|
"
|
||||||
|
SELECT OrderID, OrderStatusID, OrderTypeID
|
||||||
|
FROM Orders
|
||||||
|
WHERE OrderID = ?
|
||||||
|
LIMIT 1
|
||||||
|
",
|
||||||
|
[ { value = OrderID, cfsqltype = "cf_sql_integer" } ],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
)>
|
||||||
|
|
||||||
|
<cfif qOrder.recordCount EQ 0>
|
||||||
|
<cfset apiAbort({ "OK": false, "ERROR": "not_found", "MESSAGE": "Order not found.", "DETAIL": "" })>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif qOrder.OrderStatusID NEQ 0>
|
||||||
|
<cfset apiAbort({ "OK": false, "ERROR": "bad_state", "MESSAGE": "Order is not in cart state.", "DETAIL": "" })>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif qOrder.OrderTypeID NEQ 1>
|
||||||
|
<cfset apiAbort({ "OK": false, "ERROR": "bad_type", "MESSAGE": "Only dine-in orders (OrderTypeID=1) are supported in this MVP submit endpoint.", "DETAIL": "" })>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<!--- Must have at least one non-deleted root line item --->
|
||||||
|
<cfset qRoots = queryExecute(
|
||||||
|
"
|
||||||
|
SELECT COUNT(*) AS Cnt
|
||||||
|
FROM OrderLineItems
|
||||||
|
WHERE OrderLineItemOrderID = ?
|
||||||
|
AND OrderLineItemParentOrderLineItemID = 0
|
||||||
|
AND OrderLineItemIsDeleted = b'0'
|
||||||
|
",
|
||||||
|
[ { value = OrderID, cfsqltype = "cf_sql_integer" } ],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
)>
|
||||||
|
|
||||||
|
<cfif qRoots.Cnt LTE 0>
|
||||||
|
<cfset apiAbort({ "OK": false, "ERROR": "empty_order", "MESSAGE": "Order has no items.", "DETAIL": "" })>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<!--- Validate requires-descendant and max immediate selections --->
|
||||||
|
<cfset graph = buildLineItemsGraph(OrderID)>
|
||||||
|
|
||||||
|
<!--- Loop all non-deleted nodes and validate --->
|
||||||
|
<cfloop collection="#graph.items#" item="k">
|
||||||
|
<cfset node = graph.items[k]>
|
||||||
|
<cfif node.isDeleted>
|
||||||
|
<!--- skip deleted nodes --->
|
||||||
|
<cfcontinue>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset meta = structKeyExists(graph.itemMeta, node.itemId) ? graph.itemMeta[node.itemId] : { "requires": 0, "maxSel": 0 }>
|
||||||
|
|
||||||
|
<!--- max immediate selections --->
|
||||||
|
<cfset maxSel = val(meta.maxSel)>
|
||||||
|
<cfif maxSel GT 0>
|
||||||
|
<cfset selCount = 0>
|
||||||
|
<cfif structKeyExists(graph.children, node.id)>
|
||||||
|
<cfset kids = graph.children[node.id]>
|
||||||
|
<cfloop array="#kids#" index="kidId">
|
||||||
|
<cfif structKeyExists(graph.items, kidId)>
|
||||||
|
<cfset kidNode = graph.items[kidId]>
|
||||||
|
<cfif NOT kidNode.isDeleted>
|
||||||
|
<cfset selCount = selCount + 1>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif selCount GT maxSel>
|
||||||
|
<cfset apiAbort({
|
||||||
|
"OK": false,
|
||||||
|
"ERROR": "max_selection_exceeded",
|
||||||
|
"MESSAGE": "Too many selections under a modifier group.",
|
||||||
|
"DETAIL": "LineItemID #node.id# has #selCount# immediate children selected; max is #maxSel#."
|
||||||
|
})>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<!--- requires descendant selection --->
|
||||||
|
<cfif val(meta.requires) EQ 1>
|
||||||
|
<cfif NOT hasSelectedDescendant(graph, node.id)>
|
||||||
|
<cfset apiAbort({
|
||||||
|
"OK": false,
|
||||||
|
"ERROR": "required_selection_missing",
|
||||||
|
"MESSAGE": "A required modifier selection is missing.",
|
||||||
|
"DETAIL": "LineItemID #node.id# requires at least one descendant selection."
|
||||||
|
})>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<!--- Submit: mark submitted + status 1 --->
|
||||||
|
<cfset queryExecute(
|
||||||
|
"
|
||||||
|
UPDATE Orders
|
||||||
|
SET
|
||||||
|
OrderStatusID = 1,
|
||||||
|
OrderSubmittedOn = ?,
|
||||||
|
OrderLastEditedOn = ?
|
||||||
|
WHERE OrderID = ?
|
||||||
|
",
|
||||||
|
[
|
||||||
|
{ value = now(), cfsqltype = "cf_sql_timestamp" },
|
||||||
|
{ value = now(), cfsqltype = "cf_sql_timestamp" },
|
||||||
|
{ value = OrderID, cfsqltype = "cf_sql_integer" }
|
||||||
|
],
|
||||||
|
{ datasource = "payfrit" }
|
||||||
|
)>
|
||||||
|
|
||||||
|
<cfset apiAbort({ "OK": true, "ERROR": "", "OrderID": OrderID, "MESSAGE": "submitted" })>
|
||||||
|
|
||||||
|
<cfcatch>
|
||||||
|
<cfset apiAbort({
|
||||||
|
"OK": false,
|
||||||
|
"ERROR": "server_error",
|
||||||
|
"MESSAGE": "DB error submitting order",
|
||||||
|
"DETAIL": cfcatch.message
|
||||||
|
})>
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
|
|
@ -1,137 +1,136 @@
|
||||||
<cfsetting showdebugoutput="false">
|
<cfsetting showdebugoutput="false">
|
||||||
<cfsetting enablecfoutputonly="true">
|
<cfsetting enablecfoutputonly="true">
|
||||||
|
<cfcontent type="application/json; charset=utf-8">
|
||||||
<cfcontent type="application/json; charset=utf-8" reset="true">
|
|
||||||
<cfheader name="Cache-Control" value="no-store">
|
|
||||||
|
|
||||||
<cfscript>
|
<cfscript>
|
||||||
/*
|
function apiAbort(payload) {
|
||||||
FILE: C:\lucee\tomcat\webapps\ROOT\biz.payfrit.com\api\servicepoints\list.cfm
|
writeOutput(serializeJSON(payload));
|
||||||
|
|
||||||
MVP:
|
|
||||||
- PUBLIC (no login required)
|
|
||||||
- Reads BusinessID from JSON body
|
|
||||||
- Optional: onlyActive (default true)
|
|
||||||
- Returns hump-case JSON:
|
|
||||||
OK, ERROR, BusinessID, COUNT, ServicePoints
|
|
||||||
*/
|
|
||||||
|
|
||||||
function jsonString(val) {
|
|
||||||
// Lucee-safe string escaping:
|
|
||||||
// serializeJSON("abc") -> "\"abc\"" ; strip surrounding quotes
|
|
||||||
s = serializeJSON(toString(val));
|
|
||||||
return mid(s, 2, len(s) - 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
function apiAbort(obj) {
|
|
||||||
writeOutput('{');
|
|
||||||
writeOutput('"OK":false');
|
|
||||||
|
|
||||||
if (structKeyExists(obj, "ERROR")) {
|
|
||||||
writeOutput(',"ERROR":"' & jsonString(obj.ERROR) & '"');
|
|
||||||
}
|
|
||||||
if (structKeyExists(obj, "MESSAGE")) {
|
|
||||||
writeOutput(',"MESSAGE":"' & jsonString(obj.MESSAGE) & '"');
|
|
||||||
}
|
|
||||||
if (structKeyExists(obj, "DETAIL")) {
|
|
||||||
writeOutput(',"DETAIL":"' & jsonString(obj.DETAIL) & '"');
|
|
||||||
}
|
|
||||||
|
|
||||||
writeOutput('}');
|
|
||||||
abort;
|
abort;
|
||||||
}
|
}
|
||||||
|
|
||||||
function readJsonBody() {
|
// Resolve BusinessID tolerant MVP way
|
||||||
raw = toString(getHttpRequestData().content);
|
bizId = 0;
|
||||||
if (isNull(raw) || len(trim(raw)) EQ 0) return {};
|
|
||||||
|
|
||||||
|
if (structKeyExists(request, "BusinessID") && isNumeric(request.BusinessID)) {
|
||||||
|
bizId = int(request.BusinessID);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bizId LTE 0) {
|
||||||
try {
|
try {
|
||||||
parsed = deserializeJSON(raw);
|
raw = toString(getHttpRequestData().content);
|
||||||
} catch(any e) {
|
if (len(trim(raw))) {
|
||||||
apiAbort({ ERROR="bad_json", MESSAGE="Invalid JSON body" });
|
body = deserializeJSON(raw);
|
||||||
|
if (isStruct(body) && structKeyExists(body, "BusinessID") && isNumeric(body.BusinessID)) {
|
||||||
|
bizId = int(body.BusinessID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (any e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bizId LTE 0 && structKeyExists(url, "BusinessID") && isNumeric(url.BusinessID)) {
|
||||||
|
bizId = int(url.BusinessID);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bizId LTE 0) {
|
||||||
|
apiAbort({ "OK": false, "ERROR": "missing_businessid", "DETAIL": "" });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Detect the correct business FK column in ServicePoints
|
||||||
|
qCols = queryExecute(
|
||||||
|
"
|
||||||
|
SELECT COLUMN_NAME
|
||||||
|
FROM INFORMATION_SCHEMA.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = DATABASE()
|
||||||
|
AND TABLE_NAME = 'ServicePoints'
|
||||||
|
",
|
||||||
|
[],
|
||||||
|
{ datasource: "payfrit" }
|
||||||
|
);
|
||||||
|
|
||||||
|
cols = [];
|
||||||
|
for (r in qCols) arrayAppend(cols, r.COLUMN_NAME);
|
||||||
|
|
||||||
|
// Candidates in preferred order (add more if needed)
|
||||||
|
candidates = [
|
||||||
|
"BusinessID",
|
||||||
|
"BusinessId",
|
||||||
|
"ServicePointBusinessID",
|
||||||
|
"ServicePointsBusinessID",
|
||||||
|
"Business_ID",
|
||||||
|
"Business",
|
||||||
|
"BusinessFk",
|
||||||
|
"BusinessFK"
|
||||||
|
];
|
||||||
|
|
||||||
|
bizCol = "";
|
||||||
|
// First: exact candidate match
|
||||||
|
for (c in candidates) {
|
||||||
|
for (colName in cols) {
|
||||||
|
if (lcase(colName) EQ lcase(c)) {
|
||||||
|
bizCol = colName;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (len(bizCol)) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isStruct(parsed)) return {};
|
// Second: heuristic: any column containing "business" and "id"
|
||||||
return parsed;
|
if (!len(bizCol)) {
|
||||||
}
|
for (colName in cols) {
|
||||||
|
if (findNoCase("business", colName) && findNoCase("id", colName)) {
|
||||||
data = readJsonBody();
|
bizCol = colName;
|
||||||
|
break;
|
||||||
// Require BusinessID in JSON body for public MVP endpoint
|
}
|
||||||
if (!structKeyExists(data, "BusinessID") || !isNumeric(data.BusinessID) || int(data.BusinessID) LTE 0) {
|
}
|
||||||
apiAbort({ ERROR="missing_businessid", MESSAGE="Body must include numeric BusinessID" });
|
|
||||||
}
|
|
||||||
BusinessID = int(data.BusinessID);
|
|
||||||
|
|
||||||
// Default: only active service points unless onlyActive is explicitly false/0
|
|
||||||
onlyActive = true;
|
|
||||||
if (structKeyExists(data, "onlyActive")) {
|
|
||||||
if (isBoolean(data.onlyActive)) {
|
|
||||||
onlyActive = data.onlyActive;
|
|
||||||
} else if (isNumeric(data.onlyActive)) {
|
|
||||||
onlyActive = (int(data.onlyActive) EQ 1);
|
|
||||||
} else if (isSimpleValue(data.onlyActive)) {
|
|
||||||
onlyActive = (lcase(trim(toString(data.onlyActive))) EQ "true");
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Datasource (use existing app config)
|
if (!len(bizCol)) {
|
||||||
dsn = "";
|
apiAbort({
|
||||||
if (structKeyExists(application, "datasource") && len(trim(toString(application.datasource)))) {
|
"OK": false,
|
||||||
dsn = trim(toString(application.datasource));
|
"ERROR": "schema_mismatch",
|
||||||
} else if (structKeyExists(application, "dsn") && len(trim(toString(application.dsn)))) {
|
"DETAIL": "Could not find a BusinessID-like column in ServicePoints. Available columns: " & arrayToList(cols, ", ")
|
||||||
dsn = trim(toString(application.dsn));
|
});
|
||||||
}
|
}
|
||||||
if (!len(dsn)) {
|
|
||||||
apiAbort({ ERROR="missing_datasource", MESSAGE="application.datasource is not set" });
|
// Build SQL using detected column name (safe because it comes from INFORMATION_SCHEMA)
|
||||||
|
sql = "
|
||||||
|
SELECT
|
||||||
|
ServicePointID,
|
||||||
|
ServicePointName
|
||||||
|
FROM ServicePoints
|
||||||
|
WHERE #bizCol# = ?
|
||||||
|
ORDER BY ServicePointName
|
||||||
|
";
|
||||||
|
|
||||||
|
q = queryExecute(
|
||||||
|
sql,
|
||||||
|
[ { value: bizId, cfsqltype: "cf_sql_integer" } ],
|
||||||
|
{ datasource: "payfrit" }
|
||||||
|
);
|
||||||
|
|
||||||
|
servicePoints = [];
|
||||||
|
for (row in q) {
|
||||||
|
arrayAppend(servicePoints, {
|
||||||
|
"ServicePointID": row.ServicePointID,
|
||||||
|
"ServicePointName": row.ServicePointName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
writeOutput(serializeJSON({
|
||||||
|
"OK": true,
|
||||||
|
"ERROR": "",
|
||||||
|
"DETAIL": "",
|
||||||
|
"BusinessID": bizId,
|
||||||
|
"BusinessColumn": bizCol,
|
||||||
|
"COUNT": arrayLen(servicePoints),
|
||||||
|
"ServicePoints": servicePoints
|
||||||
|
}));
|
||||||
|
} catch (any e) {
|
||||||
|
apiAbort({
|
||||||
|
"OK": false,
|
||||||
|
"ERROR": "db_error",
|
||||||
|
"DETAIL": e.message
|
||||||
|
});
|
||||||
}
|
}
|
||||||
</cfscript>
|
</cfscript>
|
||||||
|
|
||||||
<cfquery name="q" datasource="#dsn#">
|
|
||||||
SELECT
|
|
||||||
ServicePointID,
|
|
||||||
ServicePointBusinessID,
|
|
||||||
ServicePointName,
|
|
||||||
ServicePointTypeID,
|
|
||||||
ServicePointCode,
|
|
||||||
ServicePointDescription,
|
|
||||||
ServicePointSortOrder,
|
|
||||||
ServicePointIsActive,
|
|
||||||
ServicePointCreatedAt,
|
|
||||||
ServicePointUpdatedAt
|
|
||||||
FROM ServicePoints
|
|
||||||
WHERE ServicePointBusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#BusinessID#">
|
|
||||||
<cfif onlyActive>
|
|
||||||
AND ServicePointIsActive = 1
|
|
||||||
</cfif>
|
|
||||||
ORDER BY ServicePointSortOrder, ServicePointName, ServicePointID
|
|
||||||
</cfquery>
|
|
||||||
|
|
||||||
<cfscript>
|
|
||||||
// Manual JSON output for exact key casing
|
|
||||||
writeOutput('{');
|
|
||||||
writeOutput('"OK":true');
|
|
||||||
writeOutput(',"ERROR":""');
|
|
||||||
writeOutput(',"BusinessID":' & BusinessID);
|
|
||||||
writeOutput(',"COUNT":' & q.recordCount);
|
|
||||||
writeOutput(',"ServicePoints":[');
|
|
||||||
|
|
||||||
for (i = 1; i LTE q.recordCount; i = i + 1) {
|
|
||||||
if (i GT 1) writeOutput(',');
|
|
||||||
|
|
||||||
writeOutput('{');
|
|
||||||
writeOutput('"ServicePointID":' & q.ServicePointID[i]);
|
|
||||||
writeOutput(',"BusinessID":' & q.ServicePointBusinessID[i]);
|
|
||||||
writeOutput(',"ServicePointName":"' & jsonString(q.ServicePointName[i]) & '"');
|
|
||||||
writeOutput(',"ServicePointTypeID":' & q.ServicePointTypeID[i]);
|
|
||||||
writeOutput(',"ServicePointCode":' & (isNull(q.ServicePointCode[i]) ? 'null' : '"' & jsonString(q.ServicePointCode[i]) & '"'));
|
|
||||||
writeOutput(',"ServicePointDescription":' & (isNull(q.ServicePointDescription[i]) ? 'null' : '"' & jsonString(q.ServicePointDescription[i]) & '"'));
|
|
||||||
writeOutput(',"ServicePointSortOrder":' & q.ServicePointSortOrder[i]);
|
|
||||||
writeOutput(',"ServicePointIsActive":' & q.ServicePointIsActive[i]);
|
|
||||||
writeOutput(',"ServicePointCreatedAt":"' & jsonString(q.ServicePointCreatedAt[i] & "") & '"');
|
|
||||||
writeOutput(',"ServicePointUpdatedAt":"' & jsonString(q.ServicePointUpdatedAt[i] & "") & '"');
|
|
||||||
writeOutput('}');
|
|
||||||
}
|
|
||||||
|
|
||||||
writeOutput(']}');
|
|
||||||
</cfscript>
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue