113 lines
4.4 KiB
PHP
113 lines
4.4 KiB
PHP
<?php
|
|
/**
|
|
* Embeddable payment frame (the cross-site "Pay with Square" surface).
|
|
* ────────────────────────────────────────────────────────────────────
|
|
* This page is loaded INSIDE an <iframe> on a school's own external website by
|
|
* embed.js. It resolves the school from its public embed key, renders the
|
|
* existing Web Payments widget, and auto-reports its height to the parent page
|
|
* so the iframe can size itself with no scrollbars.
|
|
*
|
|
* Query params:
|
|
* key (required) the school's public merchant_key (sqpk_…)
|
|
* amount (required) major-units amount, e.g. 25.00
|
|
* currency (optional) ISO-4217, default the school's onboarding currency/CAD
|
|
* email (optional) buyer email for the receipt
|
|
* success (optional) URL to redirect to after a successful charge
|
|
*
|
|
* Because this is meant to be framed, it deliberately does NOT send
|
|
* X-Frame-Options. When enforce_embed_origins is on and the school has
|
|
* registered origins, we additionally restrict framing via CSP frame-ancestors.
|
|
*/
|
|
|
|
require_once __DIR__ . '/lib/SquareClient.php';
|
|
require_once __DIR__ . '/lib/MerchantStore.php';
|
|
|
|
$config = require __DIR__ . '/config.php';
|
|
$store = new MerchantStore($config);
|
|
|
|
$key = isset($_GET['key']) ? trim((string)$_GET['key']) : '';
|
|
$amount = isset($_GET['amount']) && is_numeric($_GET['amount']) ? number_format((float)$_GET['amount'], 2, '.', '') : '';
|
|
$currency = isset($_GET['currency']) ? strtoupper(preg_replace('/[^A-Za-z]/', '', (string)$_GET['currency'])) : '';
|
|
$email = isset($_GET['email']) ? trim((string)$_GET['email']) : '';
|
|
$success = isset($_GET['success']) ? trim((string)$_GET['success']) : '';
|
|
if (!preg_match('#^(https?://|/)#i', $success)) {
|
|
$success = ''; // only real web URLs (blocks javascript:/data: redirects)
|
|
}
|
|
|
|
$school = $store->findSchoolByKey($key);
|
|
|
|
// Restrict who may frame us when origin enforcement is enabled.
|
|
if (!empty($config['enforce_embed_origins']) && $school !== null) {
|
|
$allowed = $store->allowedOrigins($school);
|
|
if (!empty($allowed)) {
|
|
header("Content-Security-Policy: frame-ancestors 'self' " . implode(' ', $allowed));
|
|
}
|
|
}
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Pay</title>
|
|
<style>
|
|
html, body { margin:0; padding:0; background:transparent; }
|
|
body { font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; }
|
|
.sq-frame-pad { padding:2px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="sq-frame-pad">
|
|
<?php
|
|
if ($school === null) {
|
|
echo '<div style="background:#fff5f5;border:1px solid #feb2b2;color:#c53030;'
|
|
. 'border-radius:10px;padding:14px;font-size:13px;">'
|
|
. 'This payment button is not configured. (Unknown or removed key.)</div>';
|
|
} elseif ($amount === '') {
|
|
echo '<div style="background:#fff5f5;border:1px solid #feb2b2;color:#c53030;'
|
|
. 'border-radius:10px;padding:14px;font-size:13px;">Invalid payment amount.</div>';
|
|
} else {
|
|
$sqCheckout = [
|
|
'school_name' => $school,
|
|
'merchant_key' => $key,
|
|
'amount' => $amount,
|
|
'currency' => $currency ?: '', // widget falls back to onboarding currency/CAD
|
|
'order_api_url' => './order-api.php',
|
|
'success_url' => $success,
|
|
'buyer_email' => $email,
|
|
];
|
|
require __DIR__ . '/square-checkout-widget.php';
|
|
}
|
|
?>
|
|
</div>
|
|
|
|
<script>
|
|
// Report our content height to the embedding page so embed.js can size the
|
|
// iframe. Posts on load, on resize, and whenever the widget mutates (e.g. a
|
|
// status message appears). The parent matches on the {sqPayFrame:true} marker.
|
|
(function () {
|
|
function postHeight() {
|
|
var h = Math.max(
|
|
document.body.scrollHeight,
|
|
document.documentElement.scrollHeight,
|
|
document.body.offsetHeight
|
|
);
|
|
try {
|
|
window.parent.postMessage({ sqPayFrame: true, type: 'height', height: h }, '*');
|
|
} catch (e) {}
|
|
}
|
|
window.addEventListener('load', postHeight);
|
|
window.addEventListener('resize', postHeight);
|
|
if (window.MutationObserver) {
|
|
new MutationObserver(postHeight).observe(document.body, {
|
|
childList: true, subtree: true, attributes: true, characterData: true
|
|
});
|
|
}
|
|
// A couple of delayed posts catch the async SDK card-form mount.
|
|
setTimeout(postHeight, 600);
|
|
setTimeout(postHeight, 1500);
|
|
}());
|
|
</script>
|
|
</body>
|
|
</html>
|