Files
2026-10-19 15:22:52 -07:00

121 lines
4.8 KiB
PHP

<?php
/**
* "Pay with Square" button — production bridge to Square checkout
* ────────────────────────────────────────────────────────────────
* Include from payment_form.php AFTER $balance is known. Same contract as
* paypal_ppcp_button.php: stores the amount in the hotlunch session, then
* links to /square/checkout.php so the parent cannot change the total.
*
* The button renders ONLY when this school is onboarded with Square
* (MerchantStore::isOnboarded: connected, not revoked, token present, active
* location). Otherwise nothing is shown. The charge is made with THIS school's
* own Square token, so the money settles in this school's Square account.
* Hidden-return leaves an HTML comment explaining why (View Source).
*/
$sqWhy = function (string $reason): void {
echo "\n<!-- sq-button hidden: " . htmlspecialchars($reason, ENT_QUOTES) . " -->\n";
};
if (!defined('SQ_WEB_BASE')) {
// Production layout: payment_form.php at /, this file at /square/square_button.php
define('SQ_WEB_BASE', '/square');
}
// The unique school id (names are not unique, so never resolve by name here).
$sqSchoolNum = (int)($school_num ?? ($school_record['record_num'] ?? 0));
if ($sqSchoolNum <= 0) {
$sqWhy('no school on this page');
return;
}
require_once __DIR__ . '/lib/SquareClient.php';
require_once __DIR__ . '/lib/MerchantStore.php';
$sqCfg = require __DIR__ . '/config.php';
$sqStore = new MerchantStore($sqCfg);
// Always read the live state from the DB (a cached $school_record could predate
// a connect / disconnect / revoke).
if (!$sqStore->isOnboarded($sqSchoolNum, new SquareClient($sqCfg))) {
$sqWhy('school is not onboarded with Square');
return;
}
$sqMeta = $sqStore->getMetaByNum($sqSchoolNum);
$sqSchoolName = trim((string)($school_record['school_name'] ?? ''));
if ($sqSchoolName === '') {
$sqSchoolName = (string)($sqStore->schoolNameByNum($sqSchoolNum) ?? '');
}
$sqBase = 0.0;
if (function_exists('db_getVal') && isset($user_num)) {
$sqBase = (float)db_getVal(
"select sum(transaction_amount) as balance from user_account
where school_num = " . $sqSchoolNum . " and user_num = " . (int)$user_num . "
AND (deleted != 'Y' OR deleted IS NULL)"
);
}
if ($sqBase <= 0 && isset($balance)) {
$sqBase = (float)$balance;
}
if (isset($selected_amount) && (float)$selected_amount > 0) {
$sqBase = (float)$selected_amount;
}
$sqBase = round($sqBase, 2);
if ($sqBase <= 0) {
$sqWhy('nothing to charge (total ' . number_format($sqBase, 2) . ')');
return;
}
// Charge in the currency of the school's Square location (Square rejects a
// currency that doesn't match the location).
$sqCurrency = strtoupper(trim((string)($sqMeta['currency'] ?? '')));
if (!preg_match('/^[A-Z]{3}$/', $sqCurrency)) {
$sqCurrency = 'CAD';
}
$sqIsDeposit = isset($selected_amount) && (float)$selected_amount > 0;
if (session_status() !== PHP_SESSION_ACTIVE) {
@session_name('hotlunch');
@session_start();
}
$_SESSION['sq_checkout'] = [
'school_num' => $sqSchoolNum,
'school_name' => $sqSchoolName,
'user_num' => (int)($user_num ?? ($_SESSION['UserInfo']['record_num'] ?? 0)),
'buyer_email' => (string)($_SESSION['UserInfo']['email'] ?? ''),
'base_amount' => number_format($sqBase, 2, '.', ''),
'fee_amount' => '0.00',
'total_amount' => number_format($sqBase, 2, '.', ''),
'base_label' => $sqIsDeposit ? 'Deposit amount' : 'Account balance',
'fee_label' => '',
'currency' => $sqCurrency,
'currency_symbol' => (string)($currencySymbol ?? '$'),
'return_url' => (string)($FormTarget ?? '/'),
// One Square idempotency key per checkout: a repeated click, retry or second
// tab for the same checkout can never charge the parent twice.
'idempotency_key' => 'hl-' . bin2hex(random_bytes(16)),
'created_at' => time(),
];
$sqCheckoutUrl = SQ_WEB_BASE . '/checkout.php';
?>
<div class="sq-pay-wrap">
<a href="<?php echo htmlspecialchars($sqCheckoutUrl); ?>" class="sq-pay-btn">
Pay with Square
</a>
</div>
<style>
.sq-pay-wrap { max-width: 340px; margin: 0 auto 8px; text-align: center; }
.sq-pay-btn {
display: flex; align-items: center; justify-content: center;
width: 100%; min-height: 48px; padding: 12px 24px; box-sizing: border-box;
background: linear-gradient(135deg, #006aff 0%, #003a8c 100%);
border: none; border-radius: 8px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 16px; font-weight: 600; color: #fff; text-decoration: none; cursor: pointer;
}
.sq-pay-btn:hover, .sq-pay-btn:focus { color: #fff; text-decoration: none; box-shadow: 0 8px 18px rgba(0,106,255,.28); }
</style>