111 lines
4.8 KiB
PHP
111 lines
4.8 KiB
PHP
<?php
|
|
/**
|
|
* Square token-refresh cron job (Phase 2)
|
|
* ───────────────────────────────────────
|
|
* Square access tokens expire after 30 days; refresh tokens (code flow) do not
|
|
* expire. This sweep refreshes every connected school's access token while it
|
|
* still has plenty of runway, so a token is never allowed to lapse and break
|
|
* payments.
|
|
*
|
|
* It is also self-healing for the web side: helper.php refreshes lazily on use,
|
|
* but a school that hasn't transacted in a month would otherwise drift toward
|
|
* expiry — this job covers them.
|
|
*
|
|
* USAGE (CLI only — refuses to run over HTTP):
|
|
* php refresh_tokens.php # refresh tokens expiring within 5 days
|
|
* php refresh_tokens.php --days=10 # widen the window
|
|
* php refresh_tokens.php --force # refresh ALL connected schools now
|
|
* php refresh_tokens.php --dry-run # report what WOULD happen, change nothing
|
|
* php refresh_tokens.php --school="Name"# limit to one school
|
|
*
|
|
* CRON — Square recommends renewing access tokens every 7 days or less, so the
|
|
* RECOMMENDED schedule is a weekly --force sweep (rotates every token weekly):
|
|
* 30 3 * * 0 /usr/bin/php /path/to/square/refresh_tokens.php --force >> /var/log/square_refresh.log 2>&1
|
|
* Optionally add a daily threshold sweep as a safety net against any lapse:
|
|
* 15 3 * * * /usr/bin/php /path/to/square/refresh_tokens.php --days=7 >> /var/log/square_refresh.log 2>&1
|
|
*
|
|
* Exit codes: 0 = all good (or dry-run), 1 = one or more refreshes failed.
|
|
*/
|
|
|
|
if (PHP_SAPI !== 'cli') {
|
|
http_response_code(403);
|
|
header('Content-Type: text/plain');
|
|
echo "refresh_tokens.php is a CLI script and must not be run over HTTP.\n";
|
|
exit(1);
|
|
}
|
|
|
|
// Optional app logging, if these helpers are on the include path. Wrapped so the
|
|
// script also runs standalone (e.g. from a bare cron environment).
|
|
if (file_exists(__DIR__ . '/../include/logging-functions.php')) {
|
|
require_once __DIR__ . '/../include/logging-functions.php';
|
|
if (function_exists('startLog')) startLog('square-refresh-cron');
|
|
}
|
|
|
|
require_once __DIR__ . '/lib/SquareClient.php';
|
|
require_once __DIR__ . '/lib/MerchantStore.php';
|
|
|
|
$config = require __DIR__ . '/config.php';
|
|
|
|
// ── Parse flags ──────────────────────────────────────────────────────────────
|
|
$opts = getopt('', ['days::', 'force', 'dry-run', 'school::', 'help']);
|
|
if (isset($opts['help'])) {
|
|
fwrite(STDOUT, "Usage: php refresh_tokens.php [--days=N] [--force] [--dry-run] [--school=\"Name\"]\n");
|
|
exit(0);
|
|
}
|
|
$thresholdDays = isset($opts['days']) ? max(0, (int)$opts['days']) : 5;
|
|
$force = isset($opts['force']);
|
|
$dryRun = isset($opts['dry-run']);
|
|
$onlySchool = isset($opts['school']) ? (string)$opts['school'] : null;
|
|
|
|
$ts = date('c');
|
|
fwrite(STDOUT, "[$ts] Square token refresh starting"
|
|
. " (days=$thresholdDays" . ($force ? ", force" : "") . ($dryRun ? ", dry-run" : "") . ($onlySchool ? ", school=\"$onlySchool\"" : "") . ")\n");
|
|
|
|
try {
|
|
$client = new SquareClient($config);
|
|
$store = new MerchantStore($config);
|
|
} catch (Throwable $e) {
|
|
fwrite(STDERR, "FATAL: " . $e->getMessage() . "\n");
|
|
exit(1);
|
|
}
|
|
|
|
// record_num => name. Keyed by the unique record_num so same-named schools are
|
|
// each refreshed (a name-keyed sweep would refresh only the first of them).
|
|
$schools = $store->connectedSchoolNums();
|
|
if ($onlySchool !== null) {
|
|
$schools = array_filter($schools, fn($name) => $name === $onlySchool);
|
|
}
|
|
|
|
if (empty($schools)) {
|
|
fwrite(STDOUT, "No connected schools to process.\n");
|
|
exit(0);
|
|
}
|
|
|
|
$counts = ['refreshed' => 0, 'skipped' => 0, 'would_refresh' => 0, 'failed' => 0, 'no_refresh_token' => 0, 'not_connected' => 0];
|
|
|
|
foreach ($schools as $schoolNum => $school) {
|
|
$result = $store->ensureFreshToken($school, $client, [
|
|
'threshold_days' => $thresholdDays,
|
|
'force' => $force,
|
|
'dry_run' => $dryRun,
|
|
'school_num' => $schoolNum,
|
|
]);
|
|
|
|
$action = $result['action'] ?? 'failed';
|
|
$counts[$action] = ($counts[$action] ?? 0) + 1;
|
|
|
|
$line = sprintf(" %-16s %s (#%d)", $action, $school, $schoolNum);
|
|
if (!empty($result['expires_at'])) $line .= " (expires {$result['expires_at']})";
|
|
if (!empty($result['detail'])) $line .= " — {$result['detail']}";
|
|
fwrite(STDOUT, $line . "\n");
|
|
}
|
|
|
|
$ts = date('c');
|
|
fwrite(STDOUT, "[$ts] Done. "
|
|
. "refreshed={$counts['refreshed']} skipped={$counts['skipped']} "
|
|
. "would_refresh={$counts['would_refresh']} failed={$counts['failed']} "
|
|
. "no_refresh_token={$counts['no_refresh_token']} not_connected={$counts['not_connected']}\n");
|
|
|
|
// Non-zero exit if anything failed, so cron / monitoring can alert.
|
|
exit($counts['failed'] > 0 ? 1 : 0);
|