clientId = $config['client_id'] ?? ''; $this->clientSecret = $config['client_secret'] ?? ''; $this->env = $config['env'] ?? 'sandbox'; $this->baseApi = $this->env === 'live' ? 'https://api-m.paypal.com' : 'https://api-m.sandbox.paypal.com'; if (!$this->clientId || !$this->clientSecret) { throw new RuntimeException('Missing PayPal client_id/client_secret in config.php'); } } public function getAccessToken(): string { $url = $this->baseApi . '/v1/oauth2/token'; $headers = [ 'Accept: application/json', 'Accept-Language: en_US', ]; $body = 'grant_type=client_credentials'; $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => $headers, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $body, CURLOPT_RETURNTRANSFER => true, CURLOPT_USERPWD => $this->clientId . ':' . $this->clientSecret, ]); $resp = curl_exec($ch); if ($resp === false) { throw new RuntimeException('OAuth token request failed: ' . curl_error($ch)); } $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $data = json_decode($resp, true); if ($code < 200 || $code >= 300 || !isset($data['access_token'])) { throw new RuntimeException('OAuth token response error (' . $code . '): ' . $resp); } return $data['access_token']; } public function api(string $method, string $path, array $payload = null, array $headers = []): array { $url = $this->baseApi . $path; $accessToken = $this->getAccessToken(); $defaultHeaders = [ 'Content-Type: application/json', 'Authorization: Bearer ' . $accessToken, ]; $allHeaders = array_merge($defaultHeaders, $headers); $ch = curl_init($url); $opts = [ CURLOPT_HTTPHEADER => $allHeaders, CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => strtoupper($method), ]; if ($payload !== null) { $opts[CURLOPT_POSTFIELDS] = json_encode($payload); } curl_setopt_array($ch, $opts); $resp = curl_exec($ch); if ($resp === false) { throw new RuntimeException('API request failed: ' . curl_error($ch)); } $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $data = json_decode($resp, true); if ($data === null && $resp !== '' && $resp !== 'null') { // not JSON but body present $data = ['raw' => $resp]; } return ['status' => $code, 'body' => $data]; } public function getBaseApi(): string { return $this->baseApi; } }