330 lines
9.7 KiB
PHP
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">🔒 Secure checkout</span>
|
|
</nav>
|
|
|
|
<div class="page">
|
|
|
|
<div class="page-head">
|
|
<h1>Review & 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 — your card details never touch our servers.</p>
|
|
|
|
<?php require __DIR__ . '/paypal-checkout-widget.php'; ?>
|
|
|
|
<a class="back-link" href="<?= htmlspecialchars($returnUrl) ?>">← 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>
|