First Commit
This commit is contained in:
1 parent
5eb9dd037d
commit
2e62c0e60b
11877 files changed
+2035312
No files matched your search
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
/**
|
||||
* Square Webhook Handler (Phase 2)
|
||||
* ────────────────────────────────
|
||||
* Setup (do once):
|
||||
* 1. Deploy this file and note its public URL, e.g. https://app.hotlunches.net/square/webhook.php
|
||||
* 2. Square Developer Dashboard → your App → Webhooks → Add Subscription:
|
||||
* • Notification URL: the exact URL above (must match config 'webhook_url')
|
||||
* • Events: oauth.authorization.revoked (Phase 5 will add payment.updated, refund.updated)
|
||||
* 3. Copy the subscription's Signature Key → set 'webhook_signature_key' in config.php
|
||||
* 4. Set 'webhook_url' in config.php to the exact Notification URL you entered.
|
||||
*
|
||||
* Square retries on any non-2xx for ~72h, so on transient problems we respond
|
||||
* 200 (work is logged); on a FAILED signature we respond 403 (never process an
|
||||
* unverified body). The raw body is read first, before anything can corrupt it.
|
||||
*/
|
||||
|
||||
$rawBody = file_get_contents('php://input');
|
||||
|
||||
// Optional app logging.
|
||||
ob_start();
|
||||
if (file_exists(__DIR__ . '/../include/logging-functions.php')) {
|
||||
require_once __DIR__ . '/../include/logging-functions.php';
|
||||
if (function_exists('startLog')) startLog('square-webhook');
|
||||
}
|
||||
ob_end_clean();
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$config = require __DIR__ . '/config.php';
|
||||
require_once __DIR__ . '/lib/MerchantStore.php';
|
||||
|
||||
function sqLog(string $msg): void {
|
||||
if (function_exists('writeLog')) writeLog($msg);
|
||||
}
|
||||
|
||||
// ── Header helper (works on Apache + Nginx + PHP-FPM) ────────────────────────
|
||||
function sqWebhookHeader(string $name): string {
|
||||
if (function_exists('getallheaders')) {
|
||||
foreach (getallheaders() as $k => $v) {
|
||||
if (strcasecmp($k, $name) === 0) return $v;
|
||||
}
|
||||
}
|
||||
$serverKey = 'HTTP_' . strtoupper(str_replace('-', '_', $name));
|
||||
return $_SERVER[$serverKey] ?? '';
|
||||
}
|
||||
|
||||
// ── Resolve the notification URL exactly as registered (signature depends on it).
|
||||
function sqNotificationUrl(array $config): string {
|
||||
$u = trim($config['webhook_url'] ?? '');
|
||||
if ($u !== '') return $u;
|
||||
// Fallback: reconstruct from the request (works when not behind a rewriting proxy).
|
||||
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https'
|
||||
: ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? 'https');
|
||||
$host = $_SERVER['HTTP_HOST'] ?? '';
|
||||
$uri = $_SERVER['REQUEST_URI'] ?? '';
|
||||
return $scheme . '://' . $host . $uri;
|
||||
}
|
||||
|
||||
// ── Guard: signature key must be configured ──────────────────────────────────
|
||||
$signatureKey = $config['webhook_signature_key'] ?? '';
|
||||
if ($signatureKey === '') {
|
||||
sqLog('square-webhook: webhook_signature_key not set — refusing to process unverified events');
|
||||
http_response_code(500);
|
||||
echo json_encode(['status' => 'signature_key_not_configured']);
|
||||
exit();
|
||||
}
|
||||
|
||||
// ── Verify HMAC-SHA256 signature ─────────────────────────────────────────────
|
||||
// signature = base64( HMAC_SHA256( key, notificationUrl + rawBody ) )
|
||||
$providedSig = sqWebhookHeader('x-square-hmacsha256-signature');
|
||||
if ($providedSig === '') {
|
||||
sqLog('square-webhook: missing x-square-hmacsha256-signature header — ignored');
|
||||
http_response_code(403);
|
||||
echo json_encode(['status' => 'missing_signature']);
|
||||
exit();
|
||||
}
|
||||
|
||||
$notificationUrl = sqNotificationUrl($config);
|
||||
$expectedSig = base64_encode(hash_hmac('sha256', $notificationUrl . $rawBody, $signatureKey, true));
|
||||
|
||||
if (!hash_equals($expectedSig, $providedSig)) {
|
||||
sqLog('square-webhook: signature MISMATCH (notification_url=' . $notificationUrl . ') — rejected');
|
||||
http_response_code(403);
|
||||
echo json_encode(['status' => 'signature_verification_failed']);
|
||||
exit();
|
||||
}
|
||||
|
||||
// ── Parse the verified event ─────────────────────────────────────────────────
|
||||
$event = json_decode($rawBody, true) ?: [];
|
||||
$eventType = $event['type'] ?? '';
|
||||
$merchantId = $event['merchant_id'] ?? ($event['data']['object']['merchant_id'] ?? '');
|
||||
|
||||
sqLog('square-webhook: verified type=' . $eventType . ' event_id=' . ($event['event_id'] ?? '?') . ' merchant_id=' . ($merchantId ?: '?'));
|
||||
|
||||
http_response_code(200);
|
||||
|
||||
try {
|
||||
$store = new MerchantStore($config);
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// EVENT: oauth.authorization.revoked
|
||||
// The seller removed our application from THEIR Square account. The tokens
|
||||
// are already dead at Square — clear ours and flag the school so payments
|
||||
// stop and staff know a reconnect is required.
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
if ($eventType === 'oauth.authorization.revoked') {
|
||||
if ($merchantId === '') {
|
||||
sqLog('square-webhook: revoked event without merchant_id — nothing to do');
|
||||
echo json_encode(['status' => 'ok', 'note' => 'no merchant_id']);
|
||||
exit();
|
||||
}
|
||||
$school = $store->markAuthorizationRevoked($merchantId);
|
||||
sqLog('square-webhook: authorization revoked for merchant_id=' . $merchantId
|
||||
. ($school ? (' (school=' . $school . ')') : ' (no matching school)'));
|
||||
echo json_encode(['status' => 'ok', 'school' => $school]);
|
||||
exit();
|
||||
}
|
||||
|
||||
// ── Unhandled (but verified) event — 200 so Square stops retrying ─────────
|
||||
sqLog('square-webhook: unhandled event type=' . $eventType);
|
||||
echo json_encode(['status' => 'ok', 'note' => 'event type not handled']);
|
||||
exit();
|
||||
|
||||
} catch (Throwable $e) {
|
||||
// Respond 200 so Square doesn't hammer us; the failure is logged for review.
|
||||
sqLog('square-webhook: handler error — ' . $e->getMessage());
|
||||
echo json_encode(['status' => 'ok', 'note' => 'handler error logged']);
|
||||
exit();
|
||||
}
|
||||
Reference in new issue
Block a user