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()); }