- createPaymentIntent: improved error handling - webhook: updated payment processing - resolve_business: minor fix Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
174 lines
4.6 KiB
Text
174 lines
4.6 KiB
Text
<cfsetting showdebugoutput="false">
|
|
<cfsetting enablecfoutputonly="true">
|
|
|
|
<cfcontent type="application/json; charset=utf-8" reset="true">
|
|
<cfheader name="Cache-Control" value="no-store">
|
|
|
|
<!---
|
|
ResolveBusiness API
|
|
===================
|
|
Resolves a beacon's (UUID, Major) to a Business.
|
|
Used by customer apps to identify which business a beacon belongs to.
|
|
|
|
Request:
|
|
POST /api/beacon-sharding/resolve_business.cfm
|
|
{ "UUID": "f7826da6-4fa2-4e98-8024-bc5b71e0893e", "Major": 42 }
|
|
|
|
Or for batch resolution:
|
|
{ "Beacons": [
|
|
{ "UUID": "...", "Major": 42 },
|
|
{ "UUID": "...", "Major": 43 }
|
|
]
|
|
}
|
|
|
|
Response (single):
|
|
{
|
|
"OK": true,
|
|
"BusinessID": 123,
|
|
"BusinessName": "Joe's Diner",
|
|
"BrandColor": "#FF5722",
|
|
"HeaderImageURL": "/uploads/businesses/123/header.jpg"
|
|
}
|
|
|
|
Response (batch):
|
|
{
|
|
"OK": true,
|
|
"Results": [
|
|
{ "UUID": "...", "Major": 42, "BusinessID": 123, "BusinessName": "..." },
|
|
{ "UUID": "...", "Major": 43, "BusinessID": 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 resolveSingleBusiness(uuid, major) {
|
|
var qBiz = queryExecute(
|
|
"SELECT b.ID, b.Name, b.BrandColor, b.HeaderImageExtension
|
|
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="not_found" };
|
|
}
|
|
|
|
var headerImageURL = "";
|
|
if (len(qBiz.HeaderImageExtension)) {
|
|
headerImageURL = "/uploads/headers/#qBiz.ID#.#qBiz.HeaderImageExtension#";
|
|
}
|
|
|
|
return {
|
|
Found = true,
|
|
BusinessID = qBiz.ID,
|
|
BusinessName = qBiz.Name,
|
|
BrandColor = qBiz.BrandColor,
|
|
HeaderImageURL = headerImageURL
|
|
};
|
|
}
|
|
|
|
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;
|
|
|
|
if (len(uuid) EQ 0 || major LTE 0) {
|
|
arrayAppend(results, { UUID=uuid, Major=major, BusinessID=javaCast("null",""), Error="invalid_params" });
|
|
continue;
|
|
}
|
|
|
|
resolved = resolveSingleBusiness(uuid, major);
|
|
if (resolved.Found) {
|
|
arrayAppend(results, {
|
|
UUID = uuid,
|
|
Major = major,
|
|
BusinessID = resolved.BusinessID,
|
|
BusinessName = resolved.BusinessName,
|
|
BrandColor = resolved.BrandColor,
|
|
HeaderImageURL = resolved.HeaderImageURL
|
|
});
|
|
} else {
|
|
arrayAppend(results, { UUID=uuid, Major=major, BusinessID=javaCast("null",""), Error="not_found" });
|
|
}
|
|
}
|
|
|
|
writeOutput(serializeJSON({ OK=true, COUNT=arrayLen(results), Results=results }));
|
|
abort;
|
|
}
|
|
|
|
// Single request
|
|
uuid = normStr(structKeyExists(data, "UUID") ? data.UUID : "");
|
|
major = 0;
|
|
if (structKeyExists(data, "Major") && isNumeric(data.Major)) {
|
|
major = int(data.Major);
|
|
}
|
|
|
|
// 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 (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" });
|
|
}
|
|
|
|
resolved = resolveSingleBusiness(uuid, major);
|
|
|
|
if (!resolved.Found) {
|
|
apiAbort({ OK=false, ERROR="not_found", MESSAGE="No business found for this beacon" });
|
|
}
|
|
</cfscript>
|
|
|
|
<cfoutput>#serializeJSON({
|
|
OK = true,
|
|
BusinessID = resolved.BusinessID,
|
|
BusinessName = resolved.BusinessName,
|
|
BrandColor = resolved.BrandColor,
|
|
HeaderImageURL = resolved.HeaderImageURL
|
|
})#</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>
|