data = {}; try { raw = toString(getHttpRequestData().content); if (len(trim(raw))) { data = deserializeJSON(raw); if (!isStruct(data)) data = {}; } } catch (any e) { data = {}; } ownerBusinessID = val(data.OwnerBusinessID ?: 0); guestBusinessID = val(data.GuestBusinessID ?: 0); servicePointID = val(data.ServicePointID ?: 0); economicsType = trim(data.EconomicsType ?: "none"); economicsValue = val(data.EconomicsValue ?: 0); eligibilityScope = trim(data.EligibilityScope ?: "public"); timePolicyType = trim(data.TimePolicyType ?: "always"); timePolicyData = structKeyExists(data, "TimePolicyData") ? data.TimePolicyData : ""; // Validate required fields if (ownerBusinessID LTE 0 || guestBusinessID LTE 0 || servicePointID LTE 0) { apiAbort({ "OK": false, "ERROR": "missing_params", "MESSAGE": "OwnerBusinessID, GuestBusinessID, and ServicePointID are required." }); } if (ownerBusinessID == guestBusinessID) { apiAbort({ "OK": false, "ERROR": "self_grant", "MESSAGE": "Cannot grant access to your own business." }); } // Validate caller is the owner of OwnerBusinessID callerUserID = val(structKeyExists(request, "UserID") ? request.UserID : 0); if (callerUserID LTE 0) { apiAbort({ "OK": false, "ERROR": "not_authenticated", "MESSAGE": "Authentication required." }); } qOwner = queryTimed( "SELECT UserID FROM Businesses WHERE ID = ? LIMIT 1", [{ value = ownerBusinessID, cfsqltype = "cf_sql_integer" }], { datasource = "payfrit" } ); if (qOwner.recordCount == 0 || qOwner.UserID != callerUserID) { apiAbort({ "OK": false, "ERROR": "not_owner", "MESSAGE": "You are not the owner of this business." }); } // Validate ServicePoint belongs to OwnerBusinessID qSP = queryTimed( "SELECT ID FROM ServicePoints WHERE ID = ? AND BusinessID = ? LIMIT 1", [ { value = servicePointID, cfsqltype = "cf_sql_integer" }, { value = ownerBusinessID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" } ); if (qSP.recordCount == 0) { apiAbort({ "OK": false, "ERROR": "sp_not_owned", "MESSAGE": "Service point does not belong to your business." }); } // Validate GuestBusiness exists qGuest = queryTimed( "SELECT ID FROM Businesses WHERE ID = ? LIMIT 1", [{ value = guestBusinessID, cfsqltype = "cf_sql_integer" }], { datasource = "payfrit" } ); if (qGuest.recordCount == 0) { apiAbort({ "OK": false, "ERROR": "guest_not_found", "MESSAGE": "Guest business not found." }); } // Check no active or pending grant exists for this combo qExisting = queryTimed( "SELECT ID FROM ServicePointGrants WHERE OwnerBusinessID = ? AND GuestBusinessID = ? AND ServicePointID = ? AND StatusID IN (0, 1) LIMIT 1", [ { value = ownerBusinessID, cfsqltype = "cf_sql_integer" }, { value = guestBusinessID, cfsqltype = "cf_sql_integer" }, { value = servicePointID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" } ); if (qExisting.recordCount > 0) { apiAbort({ "OK": false, "ERROR": "grant_exists", "MESSAGE": "An active or pending grant already exists for this service point and guest business." }); } // Validate enum values validEconomics = ["none", "flat_fee", "percent_of_orders"]; validEligibility = ["public", "employees", "guests", "internal"]; validTimePolicy = ["always", "schedule", "date_range", "event"]; if (!arrayFind(validEconomics, economicsType)) economicsType = "none"; if (!arrayFind(validEligibility, eligibilityScope)) eligibilityScope = "public"; if (!arrayFind(validTimePolicy, timePolicyType)) timePolicyType = "always"; // Generate UUID and InviteToken newUUID = createObject("java", "java.util.UUID").randomUUID().toString(); inviteToken = lcase(hash(generateSecretKey("AES", 256), "SHA-256")); // Serialize TimePolicyData timePolicyJson = javaCast("null", ""); if (isStruct(timePolicyData) && !structIsEmpty(timePolicyData)) { timePolicyJson = serializeJSON(timePolicyData); } else if (isSimpleValue(timePolicyData) && len(trim(timePolicyData))) { timePolicyJson = timePolicyData; } // Insert grant queryTimed( "INSERT INTO ServicePointGrants (UUID, OwnerBusinessID, GuestBusinessID, ServicePointID, StatusID, EconomicsType, EconomicsValue, EligibilityScope, TimePolicyType, TimePolicyData, InviteToken, CreatedByUserID) VALUES (?, ?, ?, ?, 0, ?, ?, ?, ?, ?, ?, ?)", [ { value = newUUID, cfsqltype = "cf_sql_varchar" }, { value = ownerBusinessID, cfsqltype = "cf_sql_integer" }, { value = guestBusinessID, cfsqltype = "cf_sql_integer" }, { value = servicePointID, cfsqltype = "cf_sql_integer" }, { value = economicsType, cfsqltype = "cf_sql_varchar" }, { value = economicsValue, cfsqltype = "cf_sql_decimal" }, { value = eligibilityScope, cfsqltype = "cf_sql_varchar" }, { value = timePolicyType, cfsqltype = "cf_sql_varchar" }, { value = timePolicyJson, cfsqltype = "cf_sql_varchar", null = isNull(timePolicyJson) }, { value = inviteToken, cfsqltype = "cf_sql_varchar" }, { value = callerUserID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" } ); // Get the inserted grant ID qNew = queryTimed( "SELECT ID FROM ServicePointGrants WHERE UUID = ? LIMIT 1", [{ value = newUUID, cfsqltype = "cf_sql_varchar" }], { datasource = "payfrit" } ); grantID = qNew.ID; // Record history recordGrantHistory( grantID = grantID, action = "created", actorUserID = callerUserID, actorBusinessID = ownerBusinessID, newData = { "OwnerBusinessID": ownerBusinessID, "GuestBusinessID": guestBusinessID, "ServicePointID": servicePointID, "EconomicsType": economicsType, "EconomicsValue": economicsValue, "EligibilityScope": eligibilityScope, "TimePolicyType": timePolicyType } ); writeOutput(serializeJSON({ "OK": true, "GrantID": grantID, "UUID": newUUID, "InviteToken": inviteToken, "StatusID": 0, "MESSAGE": "Grant created. Awaiting guest acceptance." }));