baseUrl = rtrim($baseUrl ?: Options::getBaseUrl(), '/'); $this->timeout = $timeout ?: Options::getInt(Options::TIMEOUT); } public function get(string $path, array $headers = []): AivieResponse { $this->maybeRefreshToken(); $url = $this->buildUrl($path); Logger::debug("GET {$url}"); $response = wp_remote_get($url, [ 'timeout' => $this->timeout, 'headers' => array_merge($this->getHeaders(), $headers), ]); return $this->parseResponse($response, 'GET', $url); } /** * @param array|object $payload JSON-serializable request body */ public function post(string $path, array|object $payload): AivieResponse { $this->maybeRefreshToken(); $url = $this->buildUrl($path); Logger::debug("POST {$url}", ObjectSerializer::toArray($payload)); $response = wp_remote_post($url, [ 'timeout' => $this->timeout, 'headers' => $this->getHeaders(), 'body' => wp_json_encode($payload), ]); return $this->parseResponse($response, 'POST', $url); } /** * @param array|object $payload JSON-serializable request body */ public function patch(string $path, array|object $payload): AivieResponse { $this->maybeRefreshToken(); $url = $this->buildUrl($path); Logger::debug("PATCH {$url}", ObjectSerializer::toArray($payload)); $response = wp_remote_request($url, [ 'method' => 'PATCH', 'timeout' => $this->timeout, 'headers' => $this->getHeaders(), 'body' => wp_json_encode($payload), ]); return $this->parseResponse($response, 'PATCH', $url); } private function getHeaders(): array { return [ 'Authorization' => 'Bearer ' . Options::getBearerToken(), 'Accept' => 'application/json', 'Content-Type' => 'application/json', 'User-Agent' => 'Aivie for WooCommerce/' . AIVIE_WC_VERSION . '; ' . get_site_url(), ]; } private function buildUrl(string $path): string { return trailingslashit($this->baseUrl) . ltrim($path, '/'); } private function maybeRefreshToken(): void { $refreshToken = Options::getRefreshToken(); if ('' === $refreshToken || '' === Options::getClientId()) { return; } // Attempt silent refresh. If it fails, mark credentials as expired. $url = $this->buildUrl('/oauth/v2/token'); $body = [ 'grant_type' => 'refresh_token', 'client_id' => Options::getClientId(), 'client_secret' => Options::getClientSecret(), 'refresh_token' => $refreshToken, ]; $response = wp_remote_post($url, [ 'timeout' => $this->timeout, 'headers' => ['Content-Type' => 'application/x-www-form-urlencoded'], 'body' => $body, ]); if (is_wp_error($response)) { Logger::error('Token refresh request failed.', [ 'url' => $url, 'message' => $response->get_error_message(), ]); set_transient('aivie_wc_last_auth_error', __('Token refresh failed. Please reconnect to Aivie.', 'aivie-for-woocommerce'), HOUR_IN_SECONDS); return; } $data = json_decode(wp_remote_retrieve_body($response), true); if (! empty($data['access_token'])) { Options::storeBearerToken($data['access_token'], $data['refresh_token'] ?? ''); Logger::info('Token refresh succeeded.', [ 'url' => $url, ]); } else { Logger::error('Token refresh returned invalid data.', [ 'url' => $url, 'http_code' => wp_remote_retrieve_response_code($response), ]); set_transient('aivie_wc_last_auth_error', __('Token refresh returned invalid data. Please reconnect to Aivie.', 'aivie-for-woocommerce'), HOUR_IN_SECONDS); } } private function parseResponse(mixed $response, string $method, string $url): AivieResponse { if (is_wp_error($response)) { Logger::error('Aivie API request failed before receiving a response.', [ 'method' => $method, 'url' => $url, 'message' => $response->get_error_message(), ]); return AivieResponse::fromWpError($response); } $parsed = AivieResponse::fromResponse($response); if ($parsed->isAuthError()) { Logger::error('Aivie API authentication failed.', [ 'method' => $method, 'url' => $url, 'http_code' => $parsed->httpCode, ]); set_transient( 'aivie_wc_last_auth_error', /* translators: %d is the HTTP status code returned by the Aivie API. */ sprintf(__('Aivie API authentication failed (%d). Check your credentials.', 'aivie-for-woocommerce'), $parsed->httpCode), HOUR_IN_SECONDS ); } elseif ($parsed->isConflict()) { Logger::debug('Aivie API request returned an error response.', [ 'method' => $method, 'url' => $url, 'http_code' => $parsed->httpCode, 'message' => $parsed->getSafeMessage(), ]); } elseif (! $parsed->success) { Logger::error('Aivie API request returned an error response.', [ 'method' => $method, 'url' => $url, 'http_code' => $parsed->httpCode, 'message' => $parsed->getSafeMessage(), ]); } else { Logger::debug('Aivie API request succeeded.', [ 'method' => $method, 'url' => $url, 'http_code' => $parsed->httpCode, ]); } return $parsed; } }