First Commit
This commit is contained in:
1 parent
5eb9dd037d
commit
2e62c0e60b
11877 files changed
+2035312
No files matched your search
@@ -0,0 +1,329 @@
|
||||
<?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>
|
||||
Reference in new issue
Block a user