Files
HotLunches/payp/sync_merchants.php
2026-10-19 15:22:52 -07:00

125 lines
6.5 KiB
PHP

<?php
$config = require __DIR__ . '/config.php';
require __DIR__ . '/lib/PayPalClient.php';
require __DIR__ . '/lib/Storage.php';
try {
$client = new PayPalClient($config);
$storage = new Storage($config['storage_path']);
$pending = $storage->get('pending_onboarding', []);
$merchants = $storage->get('merchants', []);
$syncDebug = $storage->get('sync_debug', []);
$updated = 0;
foreach ($pending as $track => &$entry) {
if (!is_array($entry)) continue;
$school = $entry['school'] ?? null;
if (!$school) continue;
if (!empty($entry['merchant_id'])) continue; // already resolved
$merchantId = null;
// Try by tracking_id
$path = '/v1/customer/partners/' . urlencode($config['partner_merchant_id']) . '/merchant-integrations?tracking_id=' . urlencode($track);
$resp = $client->api('GET', $path);
$syncDebug[] = ['ts' => time(), 'track' => $track, 'school' => $school, 'attempt' => 'by-tracking', 'status' => $resp['status'] ?? null, 'body' => $resp['body'] ?? null];
if ($resp['status'] >= 200 && $resp['status'] < 300) {
if (!empty($resp['body']['merchant_id'])) {
$foundId = $resp['body']['merchant_id'];
// Don't accept the partner's own ID - means onboarding not completed
if (strcasecmp($foundId, $config['partner_merchant_id']) !== 0) {
$merchantId = $foundId;
}
}
}
// Try by partner_referral_id if available
$ridCandidate = $entry['partner_referral_id'] ?? ($entry['referral_token'] ?? null);
if (!$merchantId && $ridCandidate) {
$path = '/v1/customer/partners/' . urlencode($config['partner_merchant_id']) . '/merchant-integrations?onboarded=true';
$resp2 = $client->api('GET', $path);
$syncDebug[] = ['ts' => time(), 'track' => $track, 'school' => $school, 'attempt' => 'list-onboarded', 'status' => $resp2['status'] ?? null, 'body_count' => isset($resp2['body']['merchant_integrations']) ? count($resp2['body']['merchant_integrations']) : null];
if ($resp2['status'] >= 200 && $resp2['status'] < 300 && !empty($resp2['body']['merchant_integrations'])) {
foreach ($resp2['body']['merchant_integrations'] as $mi) {
$rid = $mi['partner_referral_id'] ?? ($mi['referral_id'] ?? null);
$foundId = $mi['merchant_id'] ?? null;
// Only accept if it's not the partner's own ID
if ($rid && strcasecmp($rid, $ridCandidate) === 0 && $foundId && strcasecmp($foundId, $config['partner_merchant_id']) !== 0) {
$merchantId = $foundId;
break;
}
}
}
}
// Final fallback: list without onboarded filter and match by tracking_id/referral
if (!$merchantId) {
$path = '/v1/customer/partners/' . urlencode($config['partner_merchant_id']) . '/merchant-integrations?page_size=200';
$resp3 = $client->api('GET', $path);
$syncDebug[] = ['ts' => time(), 'track' => $track, 'school' => $school, 'attempt' => 'list-all', 'status' => $resp3['status'] ?? null, 'body_count' => isset($resp3['body']['merchant_integrations']) ? count($resp3['body']['merchant_integrations']) : null];
if ($resp3['status'] >= 200 && $resp3['status'] < 300 && !empty($resp3['body']['merchant_integrations'])) {
foreach ($resp3['body']['merchant_integrations'] as $mi) {
$tid = $mi['tracking_id'] ?? null;
$rid = $mi['partner_referral_id'] ?? ($mi['referral_id'] ?? null);
$foundId = $mi['merchant_id'] ?? null;
// Only accept if it's not the partner's own ID
if (((!empty($tid) && strcasecmp($tid, $track) === 0) || ($ridCandidate && $rid && strcasecmp($rid, $ridCandidate) === 0)) && $foundId && strcasecmp($foundId, $config['partner_merchant_id']) !== 0) {
$merchantId = $foundId;
break;
}
}
}
}
// Try reading the partner referral directly (may include status/links); capture merchant_id if present
if (!$merchantId && $ridCandidate) {
$path = '/v2/customer/partner-referrals/' . urlencode($ridCandidate);
$resp4 = $client->api('GET', $path);
$syncDebug[] = ['ts' => time(), 'track' => $track, 'school' => $school, 'attempt' => 'referral-get', 'status' => $resp4['status'] ?? null, 'body' => $resp4['body'] ?? null];
if ($resp4['status'] >= 200 && $resp4['status'] < 300 && !empty($resp4['body'])) {
// naive scan for a merchant_id field anywhere in response
$stack = [$resp4['body']];
while (!$merchantId && $stack) {
$node = array_pop($stack);
if (is_array($node)) {
if (isset($node['merchant_id']) && $node['merchant_id']) {
$merchantId = $node['merchant_id'];
break;
}
foreach ($node as $v) { if (is_array($v)) { $stack[] = $v; } }
}
}
}
}
if ($merchantId) {
$entry['merchant_id'] = $merchantId;
$entry['status'] = 'connected';
$entry['connected_at'] = time();
$merchants[$school] = [
'merchant_id' => $merchantId,
'tracking_id' => $track,
'connected_at' => $entry['connected_at'],
];
$updated++;
} else {
// Mark as incomplete if no merchant ID found
$entry['status'] = 'incomplete_onboarding';
$entry['last_sync_at'] = time();
$entry['note'] = 'Merchant has not completed PayPal onboarding. Only partner ID is available.';
}
}
unset($entry);
$storage->set('pending_onboarding', $pending);
$storage->set('merchants', $merchants);
$storage->set('sync_debug', $syncDebug);
header('Location: ' . rtrim($config['base_url'], '/') . '/index.php?sync=' . $updated);
exit;
} catch (Throwable $e) {
http_response_code(500);
echo 'Sync failed: ' . htmlspecialchars($e->getMessage());
}