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.']); } if ($userId <= 0) { apiAbort(['OK' => false, 'ERROR' => 'not_authenticated', 'MESSAGE' => 'Authentication required.']); } // Validate caller is the owner of OwnerBusinessID $qOwner = queryOne("SELECT UserID FROM Businesses WHERE ID = ? LIMIT 1", [$ownerBusinessID]); if (!$qOwner || (int) $qOwner['UserID'] !== $userId) { apiAbort(['OK' => false, 'ERROR' => 'not_owner', 'MESSAGE' => 'You are not the owner of this business.']); } // Validate ServicePoint belongs to OwnerBusinessID $qSP = queryOne( "SELECT ID FROM ServicePoints WHERE ID = ? AND BusinessID = ? LIMIT 1", [$servicePointID, $ownerBusinessID] ); if (!$qSP) { apiAbort(['OK' => false, 'ERROR' => 'sp_not_owned', 'MESSAGE' => 'Service point does not belong to your business.']); } // Validate GuestBusiness exists $qGuest = queryOne("SELECT ID FROM Businesses WHERE ID = ? LIMIT 1", [$guestBusinessID]); if (!$qGuest) { apiAbort(['OK' => false, 'ERROR' => 'guest_not_found', 'MESSAGE' => 'Guest business not found.']); } // Check no active or pending grant exists for this combo $qExisting = queryOne( "SELECT ID FROM ServicePointGrants WHERE OwnerBusinessID = ? AND GuestBusinessID = ? AND ServicePointID = ? AND StatusID IN (0, 1) LIMIT 1", [$ownerBusinessID, $guestBusinessID, $servicePointID] ); if ($qExisting) { 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 (!in_array($economicsType, $validEconomics)) $economicsType = 'none'; if (!in_array($eligibilityScope, $validEligibility)) $eligibilityScope = 'public'; if (!in_array($timePolicyType, $validTimePolicy)) $timePolicyType = 'always'; // Generate UUID and InviteToken $newUUID = generateUUID(); $inviteToken = generateSecureToken(); // Serialize TimePolicyData $timePolicyJson = null; if (is_array($timePolicyData) && !empty($timePolicyData)) { $timePolicyJson = json_encode($timePolicyData); } elseif (is_string($timePolicyData) && trim($timePolicyData) !== '') { $timePolicyJson = $timePolicyData; } // Insert grant queryTimed( "INSERT INTO ServicePointGrants (UUID, OwnerBusinessID, GuestBusinessID, ServicePointID, StatusID, EconomicsType, EconomicsValue, EligibilityScope, TimePolicyType, TimePolicyData, InviteToken, CreatedByUserID) VALUES (?, ?, ?, ?, 0, ?, ?, ?, ?, ?, ?, ?)", [ $newUUID, $ownerBusinessID, $guestBusinessID, $servicePointID, $economicsType, $economicsValue, $eligibilityScope, $timePolicyType, $timePolicyJson, $inviteToken, $userId, ] ); $grantID = (int) lastInsertId(); recordGrantHistory( $grantID, 'created', $userId, $ownerBusinessID, [], [ 'OwnerBusinessID' => $ownerBusinessID, 'GuestBusinessID' => $guestBusinessID, 'ServicePointID' => $servicePointID, 'EconomicsType' => $economicsType, 'EconomicsValue' => $economicsValue, 'EligibilityScope' => $eligibilityScope, 'TimePolicyType' => $timePolicyType, ] ); jsonResponse([ 'OK' => true, 'GrantID' => $grantID, 'UUID' => $newUUID, 'InviteToken' => $inviteToken, 'StatusID' => 0, 'MESSAGE' => 'Grant created. Awaiting guest acceptance.', ]);