|null $body */ public static function isTransient(?array $body): bool { return $body === null || in_array(data_get($body, 'error.code'), self::TRANSIENT_CODES, true); } /** * Whether a failed Meta Graph API response — status and body together — * represents a transient problem that must not disconnect a still-valid * token, as opposed to a confirmed rejection. */ public static function isTransientFailure(Response $response): bool { return $response->serverError() || $response->status() === 429 || self::isTransient(is_array($body = $response->json()) ? $body : null); } /** * Classify a failed Meta Graph "/me" verify call into the exception the * caller should throw: a rate-limit or other transient upstream problem * must not disconnect a still-valid token, but every other rejection — * including error codes other than 190, which Meta also uses to signal a * dead token — means the account genuinely needs to be reconnected. */ public static function classifyVerifyFailure(Response $response, string $label): PlatformUnavailableException|TokenExpiredException { if (self::isTransientFailure($response)) { return new PlatformUnavailableException( "{$label} API returned {$response->status()} during verification", $response->status(), ); } return new TokenExpiredException("{$label} access token is invalid or expired"); } }