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

136 lines
5.3 KiB
Text

<cfscript>
/**
* Grant utility functions for SP-SM enforcement.
* Include this file where grant time/eligibility checks are needed.
*/
/**
* Check if a grant's time policy is currently active.
* @timePolicyType always | schedule | date_range | event
* @timePolicyData JSON string or struct with policy parameters
* @return boolean
*/
function isGrantTimeActive(required string timePolicyType, any timePolicyData = "") {
if (arguments.timePolicyType == "always") return true;
var policy = {};
if (isSimpleValue(arguments.timePolicyData) && len(trim(arguments.timePolicyData))) {
try { policy = deserializeJSON(arguments.timePolicyData); } catch (any e) { return false; }
} else if (isStruct(arguments.timePolicyData)) {
policy = arguments.timePolicyData;
} else {
return false;
}
var nowDt = now();
switch (arguments.timePolicyType) {
case "schedule":
// policy: { days: [1,2,3,4,5], startTime: "09:00", endTime: "17:00" }
// days: 1=Sunday, 2=Monday, ... 7=Saturday (ColdFusion dayOfWeek)
if (!structKeyExists(policy, "days") || !isArray(policy.days)) return false;
var todayDow = dayOfWeek(nowDt);
if (!arrayFind(policy.days, todayDow)) return false;
if (structKeyExists(policy, "startTime") && structKeyExists(policy, "endTime")) {
var currentTime = timeFormat(nowDt, "HH:mm");
if (currentTime < policy.startTime || currentTime > policy.endTime) return false;
}
return true;
case "date_range":
// policy: { start: "2026-03-01", end: "2026-06-30" }
if (!structKeyExists(policy, "start") || !structKeyExists(policy, "end")) return false;
var today = dateFormat(nowDt, "yyyy-mm-dd");
return (today >= policy.start && today <= policy.end);
case "event":
// policy: { name: "Summer Festival", start: "2026-07-04 10:00", end: "2026-07-04 22:00" }
if (!structKeyExists(policy, "start") || !structKeyExists(policy, "end")) return false;
var nowStr = dateTimeFormat(nowDt, "yyyy-mm-dd HH:nn");
return (nowStr >= policy.start && nowStr <= policy.end);
default:
return false;
}
}
/**
* Check if a user meets the eligibility scope for a grant.
* @eligibilityScope public | employees | guests | internal
* @userID The ordering user's ID
* @ownerBusinessID The SP-owning business
* @guestBusinessID The granted business
* @return boolean
*/
function checkGrantEligibility(
required string eligibilityScope,
required numeric userID,
required numeric ownerBusinessID,
required numeric guestBusinessID
) {
if (arguments.eligibilityScope == "public") return true;
// Check if user is employee of a given business
var isGuestEmployee = queryExecute(
"SELECT 1 FROM Employees WHERE BusinessID = ? AND UserID = ? AND IsActive = 1 LIMIT 1",
[
{ value = arguments.guestBusinessID, cfsqltype = "cf_sql_integer" },
{ value = arguments.userID, cfsqltype = "cf_sql_integer" }
],
{ datasource = "payfrit" }
).recordCount > 0;
var isOwnerEmployee = queryExecute(
"SELECT 1 FROM Employees WHERE BusinessID = ? AND UserID = ? AND IsActive = 1 LIMIT 1",
[
{ value = arguments.ownerBusinessID, cfsqltype = "cf_sql_integer" },
{ value = arguments.userID, cfsqltype = "cf_sql_integer" }
],
{ datasource = "payfrit" }
).recordCount > 0;
switch (arguments.eligibilityScope) {
case "employees":
return isGuestEmployee;
case "guests":
return (!isGuestEmployee && !isOwnerEmployee);
case "internal":
return isOwnerEmployee;
default:
return false;
}
}
/**
* Record an entry in the immutable grant history table.
*/
function recordGrantHistory(
required numeric grantID,
required string action,
required numeric actorUserID,
required numeric actorBusinessID,
any previousData = "",
any newData = ""
) {
var prevJson = isStruct(arguments.previousData) ? serializeJSON(arguments.previousData) : (len(trim(arguments.previousData)) ? arguments.previousData : javaCast("null", ""));
var newJson = isStruct(arguments.newData) ? serializeJSON(arguments.newData) : (len(trim(arguments.newData)) ? arguments.newData : javaCast("null", ""));
var ip = "";
try { ip = cgi.REMOTE_ADDR; } catch (any e) {}
queryExecute(
"INSERT INTO ServicePointGrantHistory (GrantID, Action, ActorUserID, ActorBusinessID, PreviousData, NewData, IPAddress)
VALUES (?, ?, ?, ?, ?, ?, ?)",
[
{ value = arguments.grantID, cfsqltype = "cf_sql_integer" },
{ value = arguments.action, cfsqltype = "cf_sql_varchar" },
{ value = arguments.actorUserID, cfsqltype = "cf_sql_integer" },
{ value = arguments.actorBusinessID, cfsqltype = "cf_sql_integer" },
{ value = prevJson, cfsqltype = "cf_sql_varchar", null = isNull(prevJson) },
{ value = newJson, cfsqltype = "cf_sql_varchar", null = isNull(newJson) },
{ value = ip, cfsqltype = "cf_sql_varchar" }
],
{ datasource = "payfrit" }
);
}
</cfscript>