data = {}; try { raw = toString(getHttpRequestData().content); if (len(trim(raw))) { data = deserializeJSON(raw); if (!isStruct(data)) data = {}; } } catch (any e) { data = {}; } grantID = val(data.GrantID ?: 0); if (grantID LTE 0) { apiAbort({ "OK": false, "ERROR": "missing_grantid", "MESSAGE": "GrantID is required." }); } callerUserID = val(structKeyExists(request, "UserID") ? request.UserID : 0); if (callerUserID LTE 0) { apiAbort({ "OK": false, "ERROR": "not_authenticated" }); } // Load grant + verify ownership qGrant = queryTimed( "SELECT g.ID, g.OwnerBusinessID, g.GuestBusinessID, g.StatusID, b.UserID AS OwnerUserID FROM ServicePointGrants g JOIN Businesses b ON b.ID = g.OwnerBusinessID WHERE g.ID = ? LIMIT 1", [{ value = grantID, cfsqltype = "cf_sql_integer" }], { datasource = "payfrit" } ); if (qGrant.recordCount == 0) { apiAbort({ "OK": false, "ERROR": "not_found", "MESSAGE": "Grant not found." }); } if (qGrant.OwnerUserID != callerUserID) { apiAbort({ "OK": false, "ERROR": "not_owner", "MESSAGE": "Only the owner business can revoke a grant." }); } if (qGrant.StatusID == 3) { apiAbort({ "OK": true, "MESSAGE": "Grant is already revoked.", "GrantID": grantID }); } if (qGrant.StatusID != 0 && qGrant.StatusID != 1) { apiAbort({ "OK": false, "ERROR": "bad_state", "MESSAGE": "Only pending or active grants can be revoked." }); } // Revoke immediately queryTimed( "UPDATE ServicePointGrants SET StatusID = 3, RevokedOn = NOW() WHERE ID = ?", [{ value = grantID, cfsqltype = "cf_sql_integer" }], { datasource = "payfrit" } ); recordGrantHistory( grantID = grantID, action = "revoked", actorUserID = callerUserID, actorBusinessID = qGrant.OwnerBusinessID, previousData = { "StatusID": qGrant.StatusID }, newData = { "StatusID": 3 } ); writeOutput(serializeJSON({ "OK": true, "GrantID": grantID, "MESSAGE": "Grant revoked. All access stopped immediately." }));