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

254 lines
7.7 KiB
Text

<cfsetting showdebugoutput="false">
<cfsetting enablecfoutputonly="true">
<cfcontent type="application/json; charset=utf-8" reset="true">
<cfheader name="Cache-Control" value="no-store">
<!---
RegisterBeaconHardware API
==========================
Registers a physical beacon device after provisioning.
Request:
POST /api/beacon-sharding/register_beacon_hardware.cfm
{
"HardwareId": "AA:BB:CC:DD:EE:FF",
"BusinessID": 123,
"ServicePointID": 456,
"UUID": "f7826da6-4fa2-4e98-8024-bc5b71e0893e",
"Major": 42,
"Minor": 7,
"TxPower": -59,
"AdvertisingInterval": 350,
"FirmwareVersion": "1.2.3"
}
Response:
{
"OK": true,
"BeaconHardwareID": 1,
"Status": "assigned"
}
Logic:
1. Validate all required fields
2. Verify business namespace matches UUID/Major
3. Verify service point Minor matches
4. Create or update BeaconHardware record
--->
<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" });
}
bizId = 0;
if (structKeyExists(data, "BusinessID") && isNumeric(data.BusinessID)) {
bizId = int(data.BusinessID);
}
if (bizId LTE 0) {
apiAbort({ OK=false, ERROR="missing_business_id", MESSAGE="BusinessID is required" });
}
spId = 0;
if (structKeyExists(data, "ServicePointID") && isNumeric(data.ServicePointID)) {
spId = int(data.ServicePointID);
}
if (spId LTE 0) {
apiAbort({ OK=false, ERROR="missing_servicepoint_id", MESSAGE="ServicePointID 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
txPower = "";
if (structKeyExists(data, "TxPower") && isNumeric(data.TxPower)) {
txPower = int(data.TxPower);
}
advInterval = "";
if (structKeyExists(data, "AdvertisingInterval") && isNumeric(data.AdvertisingInterval)) {
advInterval = int(data.AdvertisingInterval);
}
firmwareVersion = normStr(structKeyExists(data, "FirmwareVersion") ? data.FirmwareVersion : "");
</cfscript>
<!--- Verify business exists and has matching namespace --->
<cfquery name="qBiz" datasource="payfrit">
SELECT b.ID, b.BeaconShardID, b.BeaconMajor, bs.UUID AS ShardUUID
FROM Businesses b
LEFT JOIN BeaconShards bs ON b.BeaconShardID = bs.ID
WHERE b.ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#bizId#">
LIMIT 1
</cfquery>
<cfif qBiz.recordCount EQ 0>
<cfset apiAbort({ OK=false, ERROR="invalid_business", MESSAGE="Business not found" })>
</cfif>
<!--- Verify UUID matches business's shard --->
<cfif compareNoCase(qBiz.ShardUUID, beaconUUID) NEQ 0>
<cfset apiAbort({
OK=false,
ERROR="uuid_mismatch",
MESSAGE="UUID does not match business's assigned shard",
ExpectedUUID=qBiz.ShardUUID,
ProvidedUUID=beaconUUID
})>
</cfif>
<!--- Verify Major matches --->
<cfif qBiz.BeaconMajor NEQ major>
<cfset apiAbort({
OK=false,
ERROR="major_mismatch",
MESSAGE="Major does not match business's assigned value",
ExpectedMajor=qBiz.BeaconMajor,
ProvidedMajor=major
})>
</cfif>
<!--- Verify service point exists and has matching minor --->
<cfquery name="qSP" datasource="payfrit">
SELECT ID, BusinessID, BeaconMinor, Name
FROM ServicePoints
WHERE ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#spId#">
LIMIT 1
</cfquery>
<cfif qSP.recordCount EQ 0>
<cfset apiAbort({ OK=false, ERROR="invalid_servicepoint", MESSAGE="Service point not found" })>
</cfif>
<cfif qSP.BusinessID NEQ bizId>
<cfset apiAbort({ OK=false, ERROR="servicepoint_business_mismatch", MESSAGE="Service point does not belong to this business" })>
</cfif>
<cfif qSP.BeaconMinor NEQ minor>
<cfset apiAbort({
OK=false,
ERROR="minor_mismatch",
MESSAGE="Minor does not match service point's assigned value",
ExpectedMinor=qSP.BeaconMinor,
ProvidedMinor=minor
})>
</cfif>
<!--- Check if hardware already exists --->
<cfquery name="qExisting" datasource="payfrit">
SELECT ID, Status FROM BeaconHardware
WHERE HardwareId = <cfqueryparam cfsqltype="cf_sql_varchar" value="#hardwareId#">
LIMIT 1
</cfquery>
<cfif qExisting.recordCount GT 0>
<!--- Update existing record --->
<cfquery datasource="payfrit">
UPDATE BeaconHardware
SET
BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#bizId#">,
ServicePointID = <cfqueryparam cfsqltype="cf_sql_integer" value="#spId#">,
ShardUUID = <cfqueryparam cfsqltype="cf_sql_varchar" value="#beaconUUID#">,
Major = <cfqueryparam cfsqltype="cf_sql_smallint" value="#major#">,
Minor = <cfqueryparam cfsqltype="cf_sql_smallint" value="#minor#">,
Status = 'assigned',
<cfif len(txPower)>TxPower = <cfqueryparam cfsqltype="cf_sql_tinyint" value="#txPower#">,</cfif>
<cfif len(advInterval)>AdvertisingInterval = <cfqueryparam cfsqltype="cf_sql_smallint" value="#advInterval#">,</cfif>
<cfif len(firmwareVersion)>FirmwareVersion = <cfqueryparam cfsqltype="cf_sql_varchar" value="#firmwareVersion#">,</cfif>
UpdatedAt = NOW()
WHERE HardwareId = <cfqueryparam cfsqltype="cf_sql_varchar" value="#hardwareId#">
</cfquery>
<cfset hwId = qExisting.ID>
<cfelse>
<!--- Insert new record --->
<cfquery datasource="payfrit">
INSERT INTO BeaconHardware (
HardwareId,
BusinessID,
ServicePointID,
ShardUUID,
Major,
Minor,
Status
<cfif len(txPower)>,TxPower</cfif>
<cfif len(advInterval)>,AdvertisingInterval</cfif>
<cfif len(firmwareVersion)>,FirmwareVersion</cfif>
) VALUES (
<cfqueryparam cfsqltype="cf_sql_varchar" value="#hardwareId#">,
<cfqueryparam cfsqltype="cf_sql_integer" value="#bizId#">,
<cfqueryparam cfsqltype="cf_sql_integer" value="#spId#">,
<cfqueryparam cfsqltype="cf_sql_varchar" value="#beaconUUID#">,
<cfqueryparam cfsqltype="cf_sql_smallint" value="#major#">,
<cfqueryparam cfsqltype="cf_sql_smallint" value="#minor#">,
'assigned'
<cfif len(txPower)>,<cfqueryparam cfsqltype="cf_sql_tinyint" value="#txPower#"></cfif>
<cfif len(advInterval)>,<cfqueryparam cfsqltype="cf_sql_smallint" value="#advInterval#"></cfif>
<cfif len(firmwareVersion)>,<cfqueryparam cfsqltype="cf_sql_varchar" value="#firmwareVersion#"></cfif>
)
</cfquery>
<cfquery name="qId" datasource="payfrit">
SELECT LAST_INSERT_ID() AS ID
</cfquery>
<cfset hwId = qId.ID>
</cfif>
<cfoutput>#serializeJSON({
OK = true,
BeaconHardwareID = hwId,
HardwareId = hardwareId,
BusinessID = bizId,
ServicePointID = spId,
ServicePointName = qSP.Name,
UUID = beaconUUID,
Major = major,
Minor = minor,
Status = "assigned"
})#</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>