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

330 lines
9.7 KiB
PHP

<?php
/**
* PayPal Checkout — order details page
* ------------------------------------
* Opened by the "Pay with PayPal" button on the parent payment page
* (user/paypal_ppcp_button.php). Shows the order summary — account balance
* or deposit amount, the school's transaction fee, and the total — next to
* every PayPal-supported payment method (PayPal, Venmo, Pay Later,
* Apple Pay, Google Pay), all rendered by paypal-checkout-widget.php.
*
* The order context arrives via the shared 'hotlunch' session (written by
* the button include), never via the URL, so amounts can't be tampered
* with. The context expires after an hour; an expired or missing context
* shows a friendly "go back" page instead of a broken checkout.
*
* Money flow: order-api.php creates the order with
* payee.merchant_id = <school's onboarded merchant id>, so the capture
* settles directly in the school's PayPal account, and then books the
* payment into the user_account ledger (idempotent on capture_id).
*/
// -- Attach to the main app session (same domain, session name 'hotlunch') -----
if (session_status() !== PHP_SESSION_ACTIVE) {
@session_name('hotlunch');
@session_start();
}
$ctx = $_SESSION['ppp_checkout'] ?? null;
$ctxValid = is_array($ctx)
&& (float)($ctx['total_amount'] ?? 0) > 0
&& trim((string)($ctx['school_name'] ?? '')) !== ''
&& (time() - (int)($ctx['created_at'] ?? 0)) < 3600;
// -- Confirm the school is still connected & ready before rendering payment ----
$schoolReady = false;
if ($ctxValid) {
$merchantsFile = __DIR__ . '/data/school_merchants.json';
if (is_file($merchantsFile)) {
$merchants = json_decode((string)file_get_contents($merchantsFile), true) ?: [];
$record = $merchants[$ctx['school_name']] ?? null;
if ($record && !empty($record['merchant_id'])) {
$schoolReady = !isset($record['paypal_ready']) || (bool)$record['paypal_ready'];
}
}
}
if ($ctxValid) {
$schoolName = (string)$ctx['school_name'];
$symbol = (string)($ctx['currency_symbol'] ?? '$');
$baseAmount = (string)$ctx['base_amount'];
$feeAmount = (string)($ctx['fee_amount'] ?? '0.00');
$total = (string)$ctx['total_amount'];
$baseLabel = (string)($ctx['base_label'] ?? 'Account balance');
$feeLabel = (string)($ctx['fee_label'] ?? 'Transaction fee');
$returnUrl = (string)($ctx['return_url'] ?? '/');
$buyerEmail = (string)($ctx['buyer_email'] ?? '');
$hasFee = (float)$feeAmount > 0;
$currency = strtoupper((string)($ctx['currency'] ?? 'USD'));
// -- Contract for paypal-checkout-widget.php -------------------------------
$items = [[
'name' => $baseLabel,
'quantity' => '1',
'unit_amount' => ['currency_code' => $currency, 'value' => $baseAmount],
]];
if ($hasFee) {
$items[] = [
'name' => $feeLabel,
'quantity' => '1',
'unit_amount' => ['currency_code' => $currency, 'value' => $feeAmount],
];
}
$ppCheckout = [
'school_name' => $schoolName,
'amount' => $total,
'currency' => $currency,
'items' => $items,
'buyer_email' => $buyerEmail,
'page_type' => 'checkout',
'order_api_url' => './order-api.php',
'success_url' => $returnUrl,
'cancel_url' => '',
'user_num' => (int)($ctx['user_num'] ?? 0),
];
} else {
$schoolName = '';
$returnUrl = is_array($ctx) ? (string)($ctx['return_url'] ?? '/') : '/';
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= $ctxValid ? 'Checkout — ' . htmlspecialchars($schoolName) : 'Checkout' ?></title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f7f8fa;
color: #1a202c;
min-height: 100vh;
}
a { color: #003087; }
/* -- Top bar -- */
.topbar {
background: #fff;
border-bottom: 1px solid #e2e8f0;
padding: 14px 24px;
display: flex;
align-items: center;
gap: 10px;
}
.topbar-school { font-weight: 700; font-size: 16px; color: #003087; }
.topbar-secure {
margin-left: auto;
font-size: 12px;
color: #718096;
display: flex;
align-items: center;
gap: 5px;
}
/* -- Layout -- */
.page {
max-width: 920px;
margin: 36px auto 48px;
padding: 0 20px;
}
.page-head { margin-bottom: 26px; }
.page-head h1 { font-size: 24px; font-weight: 700; margin-bottom: 6px; }
.page-head p { font-size: 14px; color: #718096; }
.cols {
display: grid;
grid-template-columns: 380px 1fr;
gap: 28px;
align-items: start;
}
@media (max-width: 760px) { .cols { grid-template-columns: 1fr; } }
/* -- Cards -- */
.card {
background: #fff;
border: 1px solid #e2e8f0;
border-radius: 12px;
padding: 24px;
}
.card h2 {
font-size: 15px;
font-weight: 700;
margin-bottom: 4px;
}
.card .card-sub {
font-size: 13px;
color: #718096;
margin-bottom: 18px;
line-height: 1.5;
}
/* -- Order summary -- */
.sum-row {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: 12px;
padding: 9px 0;
font-size: 14px;
color: #4a5568;
border-bottom: 1px solid #f0f2f5;
}
.sum-row .amt { font-variant-numeric: tabular-nums; white-space: nowrap; }
.sum-total {
display: flex;
justify-content: space-between;
align-items: baseline;
padding: 14px 0 2px;
font-size: 16px;
font-weight: 700;
}
.sum-total .amt { font-size: 22px; font-variant-numeric: tabular-nums; }
.sum-note {
margin-top: 16px;
padding: 12px 14px;
background: #f5f7fa;
border-radius: 8px;
font-size: 12.5px;
color: #4a5568;
line-height: 1.55;
}
.sum-account {
margin-top: 14px;
font-size: 12.5px;
color: #718096;
line-height: 1.5;
}
/* -- After-widget link back -- */
.back-link {
display: inline-block;
margin-top: 2px;
font-size: 13.5px;
}
/* -- Expired / not-ready notice -- */
.notice {
max-width: 480px;
margin: 70px auto;
background: #fff;
border: 1px solid #e2e8f0;
border-radius: 12px;
padding: 34px 30px;
text-align: center;
}
.notice h1 { font-size: 19px; margin-bottom: 10px; }
.notice p { font-size: 14px; color: #718096; line-height: 1.6; margin-bottom: 22px; }
.notice a.btn-back {
display: inline-block;
background: #003087;
color: #fff;
text-decoration: none;
font-size: 14px;
font-weight: 600;
padding: 11px 26px;
border-radius: 8px;
}
.notice a.btn-back:hover { background: #00256b; }
.foot {
text-align: center;
font-size: 12px;
color: #a0aec0;
margin-top: 34px;
line-height: 1.6;
}
</style>
</head>
<body>
<?php if (!$ctxValid): ?>
<div class="notice">
<h1>This checkout link has expired</h1>
<p>For your security, checkout sessions only stay open for a short while.
Head back to your account page and click <strong>Pay with PayPal</strong> again —
it only takes a second.</p>
<a class="btn-back" href="<?= htmlspecialchars($returnUrl) ?>">Back to my account</a>
</div>
<?php elseif (!$schoolReady): ?>
<div class="notice">
<h1>Online payment isn't available right now</h1>
<p><?= htmlspecialchars($schoolName) ?> can't accept PayPal payments at the moment.
Please go back and choose another payment method, or contact the school office.</p>
<a class="btn-back" href="<?= htmlspecialchars($returnUrl) ?>">Back to my account</a>
</div>
<?php else: ?>
<nav class="topbar">
<span class="topbar-school"><?= htmlspecialchars($schoolName) ?></span>
<span class="topbar-secure">&#128274; Secure checkout</span>
</nav>
<div class="page">
<div class="page-head">
<h1>Review &amp; pay</h1>
<p>You're paying <?= htmlspecialchars($schoolName) ?> through PayPal's secure checkout.</p>
</div>
<div class="cols">
<!-- Left: order summary -->
<div class="card">
<h2>Order summary</h2>
<p class="card-sub">Payment to <?= htmlspecialchars($schoolName) ?></p>
<div class="sum-row">
<span><?= htmlspecialchars($baseLabel) ?></span>
<span class="amt"><?= htmlspecialchars($symbol . number_format((float)$baseAmount, 2)) ?></span>
</div>
<?php if ($hasFee): ?>
<div class="sum-row">
<span><?= htmlspecialchars($feeLabel) ?></span>
<span class="amt"><?= htmlspecialchars($symbol . number_format((float)$feeAmount, 2)) ?></span>
</div>
<?php endif; ?>
<div class="sum-total">
<span>Total</span>
<span class="amt"><?= htmlspecialchars($symbol . number_format((float)$total, 2)) ?></span>
</div>
<div class="sum-note">
Your payment goes directly to the school's PayPal account and is credited
to your family account as soon as PayPal confirms it usually right away.
</div>
<?php if ($buyerEmail !== ''): ?>
<p class="sum-account">Paying as <?= htmlspecialchars($buyerEmail) ?></p>
<?php endif; ?>
</div>
<!-- Right: payment methods -->
<div class="card">
<h2>Payment method</h2>
<p class="card-sub">All payments are processed by PayPal &mdash; your card details never touch our servers.</p>
<?php require __DIR__ . '/paypal-checkout-widget.php'; ?>
<a class="back-link" href="<?= htmlspecialchars($returnUrl) ?>">&larr; Go back and choose a different payment method</a>
</div>
</div>
<p class="foot">
Questions about a charge? Contact the school office at <?= htmlspecialchars($schoolName) ?>.<br>
A PayPal receipt will be emailed to you after payment.
</p>
</div>
<?php endif; ?>
</body>
</html>