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>
153 lines
5.4 KiB
Text
153 lines
5.4 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 = {}; }
|
|
|
|
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 current grant
|
|
qGrant = queryExecute(
|
|
"SELECT g.*, 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 update grant terms." });
|
|
}
|
|
|
|
if (qGrant.StatusID != 0 && qGrant.StatusID != 1) {
|
|
apiAbort({ "OK": false, "ERROR": "bad_state", "MESSAGE": "Only pending or active grants can be updated." });
|
|
}
|
|
|
|
// Collect updates
|
|
setClauses = [];
|
|
setParams = [];
|
|
previousData = {};
|
|
newData = {};
|
|
|
|
validEconomics = ["none", "flat_fee", "percent_of_orders"];
|
|
validEligibility = ["public", "employees", "guests", "internal"];
|
|
validTimePolicy = ["always", "schedule", "date_range", "event"];
|
|
|
|
// Economics
|
|
if (structKeyExists(data, "EconomicsType") || structKeyExists(data, "EconomicsValue")) {
|
|
eType = trim(data.EconomicsType ?: qGrant.EconomicsType);
|
|
eValue = val(structKeyExists(data, "EconomicsValue") ? data.EconomicsValue : qGrant.EconomicsValue);
|
|
if (!arrayFind(validEconomics, eType)) eType = qGrant.EconomicsType;
|
|
|
|
if (eType != qGrant.EconomicsType || eValue != qGrant.EconomicsValue) {
|
|
previousData["EconomicsType"] = qGrant.EconomicsType;
|
|
previousData["EconomicsValue"] = qGrant.EconomicsValue;
|
|
newData["EconomicsType"] = eType;
|
|
newData["EconomicsValue"] = eValue;
|
|
arrayAppend(setClauses, "EconomicsType = ?");
|
|
arrayAppend(setParams, { value = eType, cfsqltype = "cf_sql_varchar" });
|
|
arrayAppend(setClauses, "EconomicsValue = ?");
|
|
arrayAppend(setParams, { value = eValue, cfsqltype = "cf_sql_decimal" });
|
|
}
|
|
}
|
|
|
|
// Eligibility
|
|
if (structKeyExists(data, "EligibilityScope")) {
|
|
eScope = trim(data.EligibilityScope);
|
|
if (!arrayFind(validEligibility, eScope)) eScope = qGrant.EligibilityScope;
|
|
if (eScope != qGrant.EligibilityScope) {
|
|
previousData["EligibilityScope"] = qGrant.EligibilityScope;
|
|
newData["EligibilityScope"] = eScope;
|
|
arrayAppend(setClauses, "EligibilityScope = ?");
|
|
arrayAppend(setParams, { value = eScope, cfsqltype = "cf_sql_varchar" });
|
|
}
|
|
}
|
|
|
|
// Time policy
|
|
if (structKeyExists(data, "TimePolicyType") || structKeyExists(data, "TimePolicyData")) {
|
|
tType = trim(data.TimePolicyType ?: qGrant.TimePolicyType);
|
|
if (!arrayFind(validTimePolicy, tType)) tType = qGrant.TimePolicyType;
|
|
tData = structKeyExists(data, "TimePolicyData") ? data.TimePolicyData : qGrant.TimePolicyData;
|
|
|
|
changed = (tType != qGrant.TimePolicyType);
|
|
if (!changed && isStruct(tData)) {
|
|
changed = (serializeJSON(tData) != (qGrant.TimePolicyData ?: ""));
|
|
}
|
|
|
|
if (changed) {
|
|
previousData["TimePolicyType"] = qGrant.TimePolicyType;
|
|
previousData["TimePolicyData"] = qGrant.TimePolicyData ?: "";
|
|
newData["TimePolicyType"] = tType;
|
|
newData["TimePolicyData"] = tData;
|
|
arrayAppend(setClauses, "TimePolicyType = ?");
|
|
arrayAppend(setParams, { value = tType, cfsqltype = "cf_sql_varchar" });
|
|
|
|
tDataJson = javaCast("null", "");
|
|
if (isStruct(tData) && !structIsEmpty(tData)) {
|
|
tDataJson = serializeJSON(tData);
|
|
} else if (isSimpleValue(tData) && len(trim(tData))) {
|
|
tDataJson = tData;
|
|
}
|
|
arrayAppend(setClauses, "TimePolicyData = ?");
|
|
arrayAppend(setParams, { value = tDataJson, cfsqltype = "cf_sql_varchar", null = isNull(tDataJson) });
|
|
}
|
|
}
|
|
|
|
if (arrayLen(setClauses) == 0) {
|
|
apiAbort({ "OK": true, "MESSAGE": "No changes detected.", "GrantID": grantID });
|
|
}
|
|
|
|
// Execute update
|
|
arrayAppend(setParams, { value = grantID, cfsqltype = "cf_sql_integer" });
|
|
queryExecute(
|
|
"UPDATE ServicePointGrants SET #arrayToList(setClauses, ', ')# WHERE ID = ?",
|
|
setParams,
|
|
{ datasource = "payfrit" }
|
|
);
|
|
|
|
// Determine action name for history
|
|
action = "updated";
|
|
if (structKeyExists(newData, "EconomicsType") || structKeyExists(newData, "EconomicsValue")) action = "updated_economics";
|
|
if (structKeyExists(newData, "EligibilityScope")) action = "updated_eligibility";
|
|
if (structKeyExists(newData, "TimePolicyType")) action = "updated_time_policy";
|
|
|
|
recordGrantHistory(
|
|
grantID = grantID,
|
|
action = action,
|
|
actorUserID = callerUserID,
|
|
actorBusinessID = qGrant.OwnerBusinessID,
|
|
previousData = previousData,
|
|
newData = newData
|
|
);
|
|
|
|
writeOutput(serializeJSON({
|
|
"OK": true,
|
|
"GrantID": grantID,
|
|
"MESSAGE": "Grant updated."
|
|
}));
|
|
</cfscript>
|