/merchant-integrations?tracking_id= if (!$merchantId) { $merchantId = null; $lastResp = null; // poll up to ~15s total for ($i = 0; $i < 15; $i++) { $path = '/v1/customer/partners/' . urlencode($config['partner_merchant_id']) . '/merchant-integrations?tracking_id=' . urlencode($track); $resp = $client->api('GET', $path); $lastResp = $resp; if ($resp['status'] >= 200 && $resp['status'] < 300) { if (!empty($resp['body']['merchant_id'])) { $foundId = $resp['body']['merchant_id']; // CRITICAL: Don't accept the partner's own ID as merchant ID // This means the merchant hasn't completed onboarding yet if (strcasecmp($foundId, $config['partner_merchant_id']) !== 0) { $merchantId = $foundId; break; } } } usleep(1000000); } if (!$merchantId) { $debug = $storage->get('return_debug', []); $debug[] = [ 'ts' => time(), 'query' => $_GET, 'last_status' => $lastResp['status'] ?? null, 'last_body' => $lastResp['body'] ?? null, 'track' => $track, 'school' => $school, 'reason' => 'Merchant onboarding not completed or partner ID returned' ]; $storage->set('return_debug', $debug); } } // Fallback by partner_referral_id if available and still missing if (!$merchantId) { // Load pending entry for additional clues $pending = $storage->get('pending_onboarding', []); $entry = $pending[$track] ?? []; $prefFromStore = $entry['partner_referral_id'] ?? $entry['referral_token'] ?? null; $pref = $pref ?: $prefFromStore; } if (!$merchantId && $pref) { // Try list integrations and filter by partner_referral_id $path = '/v1/customer/partners/' . urlencode($config['partner_merchant_id']) . '/merchant-integrations?onboarded=true'; $resp = $client->api('GET', $path); if ($resp['status'] >= 200 && $resp['status'] < 300 && !empty($resp['body']['merchant_integrations'])) { foreach ($resp['body']['merchant_integrations'] as $mi) { $rid = $mi['partner_referral_id'] ?? ($mi['referral_id'] ?? null); $foundId = $mi['merchant_id'] ?? null; // Only accept if it's a different merchant ID (not the partner's own) if ($rid && strcasecmp($rid, $pref) === 0 && $foundId && strcasecmp($foundId, $config['partner_merchant_id']) !== 0) { $merchantId = $foundId; break; } } // If still not found, but the list contains exactly one new integration with our tracking id, take it if (!$merchantId) { foreach ($resp['body']['merchant_integrations'] as $mi) { $foundId = $mi['merchant_id'] ?? null; if (!empty($mi['tracking_id']) && strcasecmp($mi['tracking_id'], $track) === 0 && $foundId && strcasecmp($foundId, $config['partner_merchant_id']) !== 0) { $merchantId = $foundId; break; } } } } } // As a last resort, if merchants.json already contains the school mapping, use it if (!$merchantId) { $merchants = $storage->get('merchants', []); if (!empty($merchants[$school]['merchant_id'])) { $existingId = $merchants[$school]['merchant_id']; // Only use if it's not the partner's own ID if (strcasecmp($existingId, $config['partner_merchant_id']) !== 0) { $merchantId = $existingId; } } } if (!$merchantId) { // Mark as incomplete onboarding $pending = $storage->get('pending_onboarding', []); if (isset($pending[$track])) { $pending[$track]['status'] = 'incomplete_onboarding'; $pending[$track]['last_return_at'] = time(); $pending[$track]['note'] = 'Merchant did not complete PayPal onboarding. Partner ID was returned instead.'; $storage->set('pending_onboarding', $pending); } // Not available yet; merchant needs to complete onboarding header('Location: ' . rtrim($config['base_url'], '/') . '/index.php?onboard=incomplete'); exit; } // Persist school -> merchant mapping $merchants = $storage->get('merchants', []); $merchants[$school] = [ 'merchant_id' => $merchantId, 'tracking_id' => $track, 'connected_at' => time(), ]; $storage->set('merchants', $merchants); // Also annotate the matching pending record so it's visible where you looked $pending = $storage->get('pending_onboarding', []); if (!isset($pending[$track])) { // if for some reason it's missing, recreate a minimal record $pending[$track] = [ 'school' => $school, 'created_at' => time(), ]; } $pending[$track]['merchant_id'] = $merchantId; $pending[$track]['status'] = 'connected'; $pending[$track]['connected_at'] = time(); // Store the raw return query for troubleshooting $pending[$track]['last_return_query'] = $_GET; $storage->set('pending_onboarding', $pending); header('Location: ' . rtrim($config['base_url'], '/') . '/index.php?onboard=ok'); exit;