This repository has been archived on 2026-03-21. You can view files and clone it, but cannot push or open issues or pull requests.
payfrit-biz/api/beacon-sharding/resolve_servicepoint.cfm
John Mizerek 3089f84873 Add beacon UUID sharding system and fix task customer info
- Add beacon-sharding API endpoints for scalable iBeacon addressing
  (64 shard UUIDs × 65k businesses = ~4.2M capacity)
- Fix callServer.cfm to save UserID when creating Call Server tasks
- Fix getDetails.cfm to return customer info from Task.UserID when
  Order.UserID is null (for tasks without orders)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 17:16:08 -08:00

225 lines
6.4 KiB
Text

<cfsetting showdebugoutput="false">
<cfsetting enablecfoutputonly="true">
<cfcontent type="application/json; charset=utf-8" reset="true">
<cfheader name="Cache-Control" value="no-store">
<!---
ResolveServicePoint API
=======================
Resolves a beacon's (UUID, Major, Minor) to a ServicePoint.
Used by customer apps to identify which table/location they're at.
Request:
POST /api/beacon-sharding/resolve_servicepoint.cfm
{ "UUID": "f7826da6-4fa2-4e98-8024-bc5b71e0893e", "Major": 42, "Minor": 7 }
Or for batch resolution:
{ "Beacons": [
{ "UUID": "...", "Major": 42, "Minor": 7 },
{ "UUID": "...", "Major": 42, "Minor": 8 }
]
}
Response (single):
{
"OK": true,
"ServicePointID": 456,
"ServicePointName": "Table 7",
"ServicePointCode": "T7",
"BusinessID": 123,
"BusinessName": "Joe's Diner"
}
Response (batch):
{
"OK": true,
"Results": [
{ "UUID": "...", "Major": 42, "Minor": 7, "ServicePointID": 456, ... },
{ "UUID": "...", "Major": 42, "Minor": 8, "ServicePointID": null, "Error": "not_found" }
]
}
--->
<cftry>
<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));
}
function resolveSingleServicePoint(uuid, major, minor) {
// First find the business by UUID + Major
var qBiz = queryExecute(
"SELECT b.ID AS BusinessID, b.Name AS BusinessName
FROM Businesses b
JOIN BeaconShards bs ON b.BeaconShardID = bs.ID
WHERE bs.UUID = ? AND b.BeaconMajor = ?
LIMIT 1",
[
{ value=uuid, cfsqltype="cf_sql_varchar" },
{ value=major, cfsqltype="cf_sql_smallint" }
],
{ datasource="payfrit" }
);
if (qBiz.recordCount EQ 0) {
return { Found=false, Error="business_not_found" };
}
// Then find the service point by BusinessID + Minor
var qSP = queryExecute(
"SELECT ID, Name, Code, TypeID, Description
FROM ServicePoints
WHERE BusinessID = ? AND BeaconMinor = ? AND IsActive = 1
LIMIT 1",
[
{ value=qBiz.BusinessID, cfsqltype="cf_sql_integer" },
{ value=minor, cfsqltype="cf_sql_smallint" }
],
{ datasource="payfrit" }
);
if (qSP.recordCount EQ 0) {
return {
Found = false,
Error = "servicepoint_not_found",
BusinessID = qBiz.BusinessID,
BusinessName = qBiz.BusinessName
};
}
return {
Found = true,
ServicePointID = qSP.ID,
ServicePointName = qSP.Name,
ServicePointCode = qSP.Code,
ServicePointTypeID = qSP.TypeID,
ServicePointDescription = qSP.Description,
BusinessID = qBiz.BusinessID,
BusinessName = qBiz.BusinessName
};
}
data = readJsonBody();
// Check for batch request
if (structKeyExists(data, "Beacons") && isArray(data.Beacons)) {
results = [];
for (beacon in data.Beacons) {
uuid = normStr(structKeyExists(beacon, "UUID") ? beacon.UUID : "");
major = structKeyExists(beacon, "Major") && isNumeric(beacon.Major) ? int(beacon.Major) : 0;
minor = structKeyExists(beacon, "Minor") && isNumeric(beacon.Minor) ? int(beacon.Minor) : -1;
if (len(uuid) EQ 0 || major LTE 0 || minor LT 0) {
arrayAppend(results, { UUID=uuid, Major=major, Minor=minor, ServicePointID=javaCast("null",""), Error="invalid_params" });
continue;
}
resolved = resolveSingleServicePoint(uuid, major, minor);
if (resolved.Found) {
arrayAppend(results, {
UUID = uuid,
Major = major,
Minor = minor,
ServicePointID = resolved.ServicePointID,
ServicePointName = resolved.ServicePointName,
ServicePointCode = resolved.ServicePointCode,
BusinessID = resolved.BusinessID,
BusinessName = resolved.BusinessName
});
} else {
var errResult = { UUID=uuid, Major=major, Minor=minor, ServicePointID=javaCast("null",""), Error=resolved.Error };
if (structKeyExists(resolved, "BusinessID")) {
errResult.BusinessID = resolved.BusinessID;
errResult.BusinessName = resolved.BusinessName;
}
arrayAppend(results, errResult);
}
}
writeOutput(serializeJSON({ OK=true, COUNT=arrayLen(results), Results=results }));
abort;
}
// Single request
uuid = normStr(structKeyExists(data, "UUID") ? data.UUID : "");
major = 0;
minor = -1;
if (structKeyExists(data, "Major") && isNumeric(data.Major)) {
major = int(data.Major);
}
if (structKeyExists(data, "Minor") && isNumeric(data.Minor)) {
minor = int(data.Minor);
}
// Also check URL params
if (len(uuid) EQ 0 && structKeyExists(url, "UUID")) {
uuid = normStr(url.UUID);
}
if (major LTE 0 && structKeyExists(url, "Major") && isNumeric(url.Major)) {
major = int(url.Major);
}
if (minor LT 0 && structKeyExists(url, "Minor") && isNumeric(url.Minor)) {
minor = int(url.Minor);
}
if (len(uuid) EQ 0) {
apiAbort({ OK=false, ERROR="missing_uuid", MESSAGE="UUID is required" });
}
if (major LTE 0) {
apiAbort({ OK=false, ERROR="missing_major", MESSAGE="Major is required" });
}
if (minor LT 0) {
apiAbort({ OK=false, ERROR="missing_minor", MESSAGE="Minor is required" });
}
resolved = resolveSingleServicePoint(uuid, major, minor);
if (!resolved.Found) {
errResponse = { OK=false, ERROR=resolved.Error };
if (structKeyExists(resolved, "BusinessID")) {
errResponse.BusinessID = resolved.BusinessID;
errResponse.BusinessName = resolved.BusinessName;
errResponse.MESSAGE = "Service point not found for this business";
} else {
errResponse.MESSAGE = "No business found for this beacon";
}
apiAbort(errResponse);
}
</cfscript>
<cfoutput>#serializeJSON({
OK = true,
ServicePointID = resolved.ServicePointID,
ServicePointName = resolved.ServicePointName,
ServicePointCode = resolved.ServicePointCode,
ServicePointTypeID = resolved.ServicePointTypeID,
ServicePointDescription = resolved.ServicePointDescription,
BusinessID = resolved.BusinessID,
BusinessName = resolved.BusinessName
})#</cfoutput>
<cfcatch type="any">
<cfheader statuscode="200" statustext="OK">
<cfcontent type="application/json; charset=utf-8" reset="true">
<cfoutput>#serializeJSON({ OK=false, ERROR="server_error", MESSAGE=cfcatch.message, DETAIL=cfcatch.detail })#</cfoutput>
</cfcatch>
</cftry>