payfrit-api/api/tabs/open.php
John Mizerek 1f81d98c52 Initial PHP API migration from CFML
Complete port of all 163 API endpoints from Lucee/CFML to PHP 8.3.
Shared helpers in api/helpers.php (DB, auth, request/response, security).
PDO prepared statements throughout. Same JSON response shapes as CFML.
2026-03-14 14:26:59 -07:00

142 lines
6 KiB
PHP

<?php
require_once __DIR__ . '/../helpers.php';
require_once __DIR__ . '/../config/stripe.php';
runAuth();
try {
$data = readJsonBody();
$userID = (int) ($data['UserID'] ?? 0);
$businessID = (int) ($data['BusinessID'] ?? 0);
if (isset($data['AuthAmountCents']) && (int) $data['AuthAmountCents'] > 0) {
$authAmount = (int) $data['AuthAmountCents'] / 100;
} else {
$authAmount = (float) ($data['AuthAmount'] ?? 0);
}
$servicePointID = (int) ($data['ServicePointID'] ?? 0);
$approvalMode = isset($data['ApprovalMode']) ? (int) $data['ApprovalMode'] : null;
if ($userID === 0) apiAbort(['OK' => false, 'ERROR' => 'missing_UserID']);
if ($businessID === 0) apiAbort(['OK' => false, 'ERROR' => 'missing_BusinessID']);
$qBiz = queryOne("
SELECT SessionEnabled, TabMinAuthAmount, TabDefaultAuthAmount, TabMaxAuthAmount,
StripeAccountID, StripeOnboardingComplete
FROM Businesses WHERE ID = ? LIMIT 1
", [$businessID]);
if (!$qBiz) apiAbort(['OK' => false, 'ERROR' => 'business_not_found']);
if (!$qBiz['SessionEnabled']) apiAbort(['OK' => false, 'ERROR' => 'tabs_not_enabled', 'MESSAGE' => 'This business does not accept tabs.']);
$minAuth = (float) $qBiz['TabMinAuthAmount'];
$maxAuth = (float) $qBiz['TabMaxAuthAmount'];
if ($authAmount <= 0) $authAmount = (float) $qBiz['TabDefaultAuthAmount'];
if ($authAmount < $minAuth) apiAbort(['OK' => false, 'ERROR' => 'auth_too_low', 'MESSAGE' => "Minimum authorization is \$" . number_format($minAuth, 2), 'MIN' => $minAuth]);
if ($authAmount > $maxAuth) apiAbort(['OK' => false, 'ERROR' => 'auth_too_high', 'MESSAGE' => "Maximum authorization is \$" . number_format($maxAuth, 2), 'MAX' => $maxAuth]);
// Check user not already on a tab
$qExisting = queryOne("
SELECT t.ID, t.BusinessID, b.Name AS BusinessName
FROM TabMembers tm
JOIN Tabs t ON t.ID = tm.TabID
JOIN Businesses b ON b.ID = t.BusinessID
WHERE tm.UserID = ? AND tm.StatusID = 1 AND t.StatusID = 1
LIMIT 1
", [$userID]);
if ($qExisting) {
apiAbort([
'OK' => false, 'ERROR' => 'already_on_tab',
'MESSAGE' => "You're already on a tab at " . $qExisting['BusinessName'] . ".",
'EXISTING_TAB_ID' => (int) $qExisting['ID'],
'EXISTING_BUSINESS_NAME' => $qExisting['BusinessName'],
]);
}
// Get or create Stripe Customer
$qUser = queryOne("SELECT StripeCustomerId, EmailAddress, FirstName, LastName FROM Users WHERE ID = ? LIMIT 1", [$userID]);
if (!$qUser) apiAbort(['OK' => false, 'ERROR' => 'user_not_found']);
$stripeCustomerId = $qUser['StripeCustomerId'] ?? '';
$needNewCustomer = empty(trim($stripeCustomerId));
if (!$needNewCustomer) {
$checkData = stripeRequest('GET', "https://api.stripe.com/v1/customers/$stripeCustomerId");
if (isset($checkData['error']) || !isset($checkData['id'])) {
$needNewCustomer = true;
}
}
if ($needNewCustomer) {
$custParams = [
'name' => trim($qUser['FirstName'] . ' ' . $qUser['LastName']),
'metadata[payfrit_user_id]' => $userID,
];
if (!empty(trim($qUser['EmailAddress'] ?? ''))) $custParams['email'] = $qUser['EmailAddress'];
$custData = stripeRequest('POST', 'https://api.stripe.com/v1/customers', $custParams);
if (isset($custData['id'])) {
$stripeCustomerId = $custData['id'];
queryTimed("UPDATE Users SET StripeCustomerId = ? WHERE ID = ?", [$stripeCustomerId, $userID]);
} else {
apiAbort(['OK' => false, 'ERROR' => 'stripe_customer_failed', 'MESSAGE' => 'Could not create Stripe customer.']);
}
}
$tabUUID = generateUUID();
$authAmountCents = round($authAmount * 100);
// Create PaymentIntent with manual capture
$piParams = [
'amount' => $authAmountCents,
'currency' => 'usd',
'capture_method' => 'manual',
'customer' => $stripeCustomerId,
'setup_future_usage' => 'off_session',
'automatic_payment_methods[enabled]' => 'true',
'metadata[type]' => 'tab_authorization',
'metadata[tab_uuid]' => $tabUUID,
'metadata[business_id]' => $businessID,
'metadata[user_id]' => $userID,
];
if (!empty(trim($qBiz['StripeAccountID'] ?? '')) && (int) ($qBiz['StripeOnboardingComplete'] ?? 0) === 1) {
$piParams['transfer_data[destination]'] = $qBiz['StripeAccountID'];
}
$now = gmdate('YmdHis');
$piData = stripeRequest('POST', 'https://api.stripe.com/v1/payment_intents', $piParams,
['Idempotency-Key' => "tab-open-$userID-$businessID-$now"]);
if (!isset($piData['id'])) {
$errMsg = $piData['error']['message'] ?? 'Stripe error';
apiAbort(['OK' => false, 'ERROR' => 'stripe_pi_failed', 'MESSAGE' => $errMsg]);
}
// Insert tab
$spVal = $servicePointID > 0 ? $servicePointID : null;
queryTimed("
INSERT INTO Tabs (UUID, BusinessID, OwnerUserID, ServicePointID, StatusID,
AuthAmountCents, StripePaymentIntentID, StripeCustomerID, ApprovalMode, OpenedOn, LastActivityOn)
VALUES (?, ?, ?, ?, 1, ?, ?, ?, ?, NOW(), NOW())
", [$tabUUID, $businessID, $userID, $spVal, $authAmountCents, $piData['id'], $stripeCustomerId, $approvalMode]);
$tabID = (int) lastInsertId();
// Add owner as TabMember
queryTimed("INSERT INTO TabMembers (TabID, UserID, RoleID, StatusID, JoinedOn) VALUES (?, ?, 1, 1, NOW())", [$tabID, $userID]);
$config = getStripeConfig();
jsonResponse([
'OK' => true,
'TAB_ID' => $tabID,
'TAB_UUID' => $tabUUID,
'CLIENT_SECRET' => $piData['client_secret'],
'PAYMENT_INTENT_ID' => $piData['id'],
'AUTH_AMOUNT_CENTS' => $authAmountCents,
'PUBLISHABLE_KEY' => $config['publishableKey'],
]);
} catch (Exception $e) {
jsonResponse(['OK' => false, 'ERROR' => 'server_error', 'MESSAGE' => $e->getMessage()]);
}