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/verify_beacon_broadcast.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

171 lines
4.5 KiB
Text

<cfsetting showdebugoutput="false">
<cfsetting enablecfoutputonly="true">
<cfcontent type="application/json; charset=utf-8" reset="true">
<cfheader name="Cache-Control" value="no-store">
<!---
VerifyBeaconBroadcast API
=========================
Records a scan confirmation that a beacon is broadcasting the expected values.
Called by provisioning app after writing config and seeing the beacon advertise.
Request:
POST /api/beacon-sharding/verify_beacon_broadcast.cfm
{
"HardwareId": "AA:BB:CC:DD:EE:FF",
"UUID": "f7826da6-4fa2-4e98-8024-bc5b71e0893e",
"Major": 42,
"Minor": 7,
"RSSI": -65,
"SeenAt": "2024-01-15T10:30:00Z"
}
Response:
{
"OK": true,
"BeaconHardwareID": 1,
"Status": "verified",
"VerifiedAt": "2024-01-15T10:30:00Z"
}
Logic:
1. Find beacon hardware by HardwareId
2. Verify UUID/Major/Minor match expected values
3. Update status to "verified" and record timestamp/RSSI
--->
<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));
}
data = readJsonBody();
// Required fields
hardwareId = normStr(structKeyExists(data, "HardwareId") ? data.HardwareId : "");
if (len(hardwareId) EQ 0) {
apiAbort({ OK=false, ERROR="missing_hardware_id", MESSAGE="HardwareId is required" });
}
beaconUUID = normStr(structKeyExists(data, "UUID") ? data.UUID : "");
if (len(beaconUUID) EQ 0) {
apiAbort({ OK=false, ERROR="missing_uuid", MESSAGE="UUID is required" });
}
major = 0;
if (structKeyExists(data, "Major") && isNumeric(data.Major)) {
major = int(data.Major);
}
minor = 0;
if (structKeyExists(data, "Minor") && isNumeric(data.Minor)) {
minor = int(data.Minor);
}
// Optional fields
rssi = "";
if (structKeyExists(data, "RSSI") && isNumeric(data.RSSI)) {
rssi = int(data.RSSI);
}
seenAt = now();
if (structKeyExists(data, "SeenAt") && len(data.SeenAt)) {
try {
seenAt = parseDateTime(data.SeenAt);
} catch(any e) {
// Use current time if parse fails
}
}
</cfscript>
<!--- Find beacon hardware --->
<cfquery name="qHW" datasource="payfrit">
SELECT ID, BusinessID, ServicePointID, ShardUUID, Major, Minor, Status
FROM BeaconHardware
WHERE HardwareId = <cfqueryparam cfsqltype="cf_sql_varchar" value="#hardwareId#">
LIMIT 1
</cfquery>
<cfif qHW.recordCount EQ 0>
<cfset apiAbort({ OK=false, ERROR="hardware_not_found", MESSAGE="Beacon hardware not registered" })>
</cfif>
<!--- Verify broadcast matches expected values --->
<cfif compareNoCase(qHW.ShardUUID, beaconUUID) NEQ 0>
<cfset apiAbort({
OK=false,
ERROR="uuid_mismatch",
MESSAGE="Beacon is broadcasting wrong UUID",
ExpectedUUID=qHW.ShardUUID,
BroadcastUUID=beaconUUID
})>
</cfif>
<cfif qHW.Major NEQ major>
<cfset apiAbort({
OK=false,
ERROR="major_mismatch",
MESSAGE="Beacon is broadcasting wrong Major",
ExpectedMajor=qHW.Major,
BroadcastMajor=major
})>
</cfif>
<cfif qHW.Minor NEQ minor>
<cfset apiAbort({
OK=false,
ERROR="minor_mismatch",
MESSAGE="Beacon is broadcasting wrong Minor",
ExpectedMinor=qHW.Minor,
BroadcastMinor=minor
})>
</cfif>
<!--- Update to verified status --->
<cfquery datasource="payfrit">
UPDATE BeaconHardware
SET
Status = 'verified',
VerifiedAt = <cfqueryparam cfsqltype="cf_sql_timestamp" value="#seenAt#">,
LastSeenAt = <cfqueryparam cfsqltype="cf_sql_timestamp" value="#seenAt#">,
<cfif len(rssi)>LastRssi = <cfqueryparam cfsqltype="cf_sql_smallint" value="#rssi#">,</cfif>
UpdatedAt = NOW()
WHERE HardwareId = <cfqueryparam cfsqltype="cf_sql_varchar" value="#hardwareId#">
</cfquery>
<cfoutput>#serializeJSON({
OK = true,
BeaconHardwareID = qHW.ID,
HardwareId = hardwareId,
BusinessID = qHW.BusinessID,
ServicePointID = qHW.ServicePointID,
Status = "verified",
VerifiedAt = dateTimeFormat(seenAt, "yyyy-mm-dd'T'HH:nn:ss'Z'")
})#</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>