legacy = true === $legacy; } private static $paths = array( 'revoke' => '/connection-session/revoke', 'confirm' => '/connection-session/confirm', 'cleanup' => '/connection-session/cleanup', 'claim' => '/connection-session/claim', 'status' => '/connection-session/status', 'exchange' => '/connection-session/exchange', ); public function claim($session, $secret, $callback_url) { return $this->request('claim', array('session' => $session, 'bootstrap_secret' => $secret, 'callback_url' => $callback_url), $callback_url); } public function status(array $state) { return $this->request('status', array('session' => $state['session'], 'bootstrap_secret' => $state['bootstrap_secret'], 'callback_url' => $state['callback_url']), $state['callback_url']); } public function exchange(array $state) { return $this->request('exchange', array('session' => $state['session'], 'bootstrap_secret' => $state['bootstrap_secret'], 'callback_url' => $state['callback_url']), $state['callback_url']); } public function revoke(array $state) { return $this->operation('revoke', $state); } public function confirm(array $state) { return $this->operation('confirm', $state); } public function cleanup(array $state) { return $this->operation('cleanup', $state); } private function operation($operation, array $state) { return $this->request($operation, array('session' => $state['session'], 'bootstrap_secret' => $state['bootstrap_secret'], 'callback_url' => $state['callback_url']), $state['callback_url']); } private function request($operation, array $payload, $callback_url) { $url = self::endpoint($operation, $this->legacy); $session = $payload['session']; $body = wp_json_encode($payload, JSON_UNESCAPED_SLASHES); unset($payload); if (!is_string($url) || !is_string($body)) return array('kind' => 'ambiguous'); try { $response = wp_safe_remote_post($url, array( 'body' => $body, 'headers' => array('Content-Type' => 'application/json'), 'timeout' => 10, 'redirection' => 0, 'sslverify' => true, 'limit_response_size' => self::LIMIT, 'reject_unsafe_urls' => true, 'data_format' => 'body', 'cookies' => array(), )); } catch (Throwable $error) { unset($error, $body, $url); return array('kind' => 'ambiguous'); } unset($body, $url); if (is_wp_error($response)) return array('kind' => 'ambiguous'); $status = wp_remote_retrieve_response_code($response); if (400 === $status || 401 === $status) return array('kind' => 'rejected'); if (429 === $status) return self::retry($response, 60, $session); if (500 === $status || 503 === $status) return self::retry($response, 60, $session); if (404 === $status && 'WOOCOMMERCE_CONNECTOR_DISABLED' === Akkedli_V2_Strict_JSON::failure_code(wp_remote_retrieve_body($response))) return array('kind' => 'paused'); if (200 !== $status) return array('kind' => 'ambiguous'); $type = wp_remote_retrieve_header($response, 'content-type'); $raw = wp_remote_retrieve_body($response); if (!is_string($type) || !preg_match('/^application\/json(?:;\s*charset=utf-8)?$/Di', $type) || !is_string($raw) || strlen($raw) >= self::LIMIT) return array('kind' => 'ambiguous'); if ($this->legacy) return Akkedli_Legacy_Connection_State::parse_response($raw, $operation); if ('exchange' === $operation) { $value = Akkedli_V2_Credential_Response::parse($raw, $callback_url, self::LIMIT); return is_array($value) ? array_merge(array('kind' => 'success'), $value) : array('kind' => 'ambiguous'); } $value = Akkedli_V2_Strict_JSON::decode($raw, array('success', 'data'), array('state', 'expected_store_url', 'expires_at')); $states = array('proof_pending', 'proof_processing', 'proof_verified', 'failed_retryable', 'failed_terminal', 'exchanged', 'expired'); if (!is_array($value) || true !== $value['success'] || !is_string($value['data']['state']) || !in_array($value['data']['state'], $states, true) || !is_string($value['data']['expected_store_url']) || !is_string($value['data']['expires_at'])) return array('kind' => 'ambiguous'); return array_merge(array('kind' => 'success'), $value['data']); } private static function retry($response, $fallback, $session) { $header = wp_remote_retrieve_header($response, 'retry-after'); $delay = is_string($header) && preg_match('/^[1-9]\d{0,2}$/D', $header) ? min(600, (int) $header) : $fallback; Akkedli_V2_Session_Retry::defer($session, $delay); return array('kind' => 'retry', 'retry_after' => $delay); } private static function endpoint($operation, $legacy = false) { if (!isset(self::$paths[$operation])) return false; $suffix = '/api/connectors/woocommerce/enroll'; if (substr(Akkedli_V2_Enrollment::URL, -strlen($suffix)) !== $suffix) return false; $path = $legacy ? str_replace('/connection-session/', '/legacy-session/', self::$paths[$operation]) : self::$paths[$operation]; return substr(Akkedli_V2_Enrollment::URL, 0, -strlen('/enroll')) . $path; } }