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/grants/create.cfm
John Mizerek f52d14bb7e Add Service Point Sharing infrastructure
Grant-based system allowing businesses to share service points with
other businesses. Includes grant CRUD API, time/eligibility/economics
policies, enforcement at cart creation and order submit, Stripe payment
routing for owner fees, and portal UI for managing grants.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 21:34:03 -08:00

167 lines
6.2 KiB
Text

<cfsetting showdebugoutput="false">
<cfsetting enablecfoutputonly="true">
<cfcontent type="application/json; charset=utf-8" reset="true">
<cfheader name="Cache-Control" value="no-store">
<cfinclude template="_grantUtils.cfm">
<cfscript>
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 = queryExecute(
"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 = queryExecute(
"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 = queryExecute(
"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 = queryExecute(
"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
queryExecute(
"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 = queryExecute(
"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."
}));
</cfscript>