Server IP : 80.87.202.40 / Your IP : 216.73.216.169 Web Server : Apache System : Linux rospirotorg.ru 5.14.0-539.el9.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Dec 5 22:26:13 UTC 2024 x86_64 User : bitrix ( 600) PHP Version : 8.2.27 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /home/bitrix/ext_www/rospirotorg.ru/vendor/yoomoney/yookassa-sdk-php/lib/Client/ |
Upload File : |
<?php /* * The MIT License * * Copyright (c) 2025 "YooMoney", NBСO LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ namespace YooKassa\Client; use CurlHandle; use Psr\Log\LoggerInterface; use YooKassa\Common\Exceptions\ApiConnectionException; use YooKassa\Common\Exceptions\ApiException; use YooKassa\Common\Exceptions\AuthorizeException; use YooKassa\Common\Exceptions\ExtensionNotFoundException; use YooKassa\Common\ResponseObject; use YooKassa\Helpers\RawHeadersParser; /** * Класс, представляющий модель CurlClient. * * Класс клиента Curl запросов. * * @category Class * @package YooKassa * @author cms@yoomoney.ru * @link https://yookassa.ru/developers/api */ class CurlClient implements ApiClientInterface { /** @var array Настройки клиента */ private array $config = []; /** @var int|null shopId магазина */ private ?int $shopId = null; /** @var string|null Секретный ключ магазина */ private ?string $shopPassword = null; /** @var null|string OAuth токен */ private ?string $bearerToken = null; /** @var int Настройка параметра CURLOPT_TIMEOUT */ private int $timeout = 80; /** @var int Настройка параметра CURLOPT_CONNECTTIMEOUT */ private int $connectionTimeout = 30; /** @var null|string Настройка прокси-сервера, если нужен */ private ?string $proxy = null; /** @var UserAgent Строка user-agent для статистики */ private UserAgent $userAgent; /** @var bool Настройка удержания соединения */ private bool $keepAlive = true; /** @var array Заголовки по умолчанию */ private array $defaultHeaders = [ 'Content-Type' => 'application/json', 'Accept' => 'application/json', ]; /** @var CurlHandle|null Текущий ресурс для работы с curl */ private ?CurlHandle $curl = null; /** @var null|LoggerInterface Объект для логирования запросов */ private ?LoggerInterface $logger = null; /** * CurlClient constructor. */ public function __construct() { $this->userAgent = new UserAgent(); } public function setLogger(?LoggerInterface $logger): void { $this->logger = $logger; } /** * {@inheritdoc} * * @param string $path URL запроса * @param string $method HTTP метод * @param array $queryParams Массив GET параметров запроса * @param null|string $httpBody Тело запроса * @param array $headers Массив заголовков запроса * * @throws ApiConnectionException * @throws ApiException * @throws AuthorizeException * @throws ExtensionNotFoundException */ public function call(string $path, string $method, array $queryParams, ?string $httpBody = null, array $headers = []): ResponseObject { $headers = $this->prepareHeaders($headers); $this->logRequestParams($path, $method, $queryParams, $httpBody, $headers); $url = $this->prepareUrl($path, $queryParams); $this->prepareCurl($method, $url, $httpBody, $this->implodeHeaders($headers)); [$httpHeaders, $httpBody, $responseInfo] = $this->sendRequest(); if (!$this->keepAlive) { $this->closeCurlConnection(); } $this->logResponse($httpBody, $responseInfo, $httpHeaders); return new ResponseObject([ 'code' => $responseInfo['http_code'], 'headers' => $httpHeaders, 'body' => $httpBody, ]); } /** * Устанавливает параметры CURL. * * @param string $optionName Имя параметра * @param mixed $optionValue Значение параметра */ public function setCurlOption(string $optionName, mixed $optionValue): bool { return curl_setopt($this->curl, $optionName, $optionValue); } /** * Close connection. */ public function closeCurlConnection(): void { if (null !== $this->curl) { curl_close($this->curl); } } /** * Выполняет запрос, получает и возвращает обработанный ответ * * @throws ApiConnectionException */ public function sendRequest(): array { $response = curl_exec($this->curl); $httpHeaderSize = curl_getinfo($this->curl, CURLINFO_HEADER_SIZE); $httpHeaders = RawHeadersParser::parse(substr($response, 0, $httpHeaderSize)); $httpBody = substr($response, $httpHeaderSize); $responseInfo = curl_getinfo($this->curl); $curlError = curl_error($this->curl); $curlErrno = curl_errno($this->curl); if (false === $response) { $this->handleCurlError($curlError, $curlErrno); } return [$httpHeaders, $httpBody, $responseInfo]; } /** * Устанавливает тело запроса. * * @param string $method HTTP метод * @param string|null $httpBody Тело запроса */ public function setBody(string $method, ?string $httpBody = null): void { $this->setCurlOption(CURLOPT_CUSTOMREQUEST, $method); if (!empty($httpBody)) { $this->setCurlOption(CURLOPT_POSTFIELDS, $httpBody); } } /** * Устанавливает shopId магазина. * * @param mixed $shopId shopId магазина * * @return $this */ public function setShopId(mixed $shopId): self { $this->shopId = (int) $shopId; return $this; } /** * Устанавливает секретный ключ магазина. * * @param string|null $shopPassword Секретный ключ магазина * * @return $this */ public function setShopPassword(?string $shopPassword): self { $this->shopPassword = $shopPassword; return $this; } /** * Возвращает значение параметра CURLOPT_TIMEOUT. */ public function getTimeout(): int { return $this->timeout; } /** * Устанавливает значение параметра CURLOPT_TIMEOUT. * * @param int $timeout Максимальное количество секунд для выполнения функций cURL */ public function setTimeout(int $timeout): void { $this->timeout = $timeout; } /** * Возвращает значение параметра CURLOPT_CONNECTTIMEOUT. */ public function getConnectionTimeout(): int { return $this->connectionTimeout; } /** * Устанавливает значение параметра CURLOPT_CONNECTTIMEOUT. * * @param int $connectionTimeout Число секунд ожидания при попытке подключения */ public function setConnectionTimeout(int $connectionTimeout = 30): void { $this->connectionTimeout = $connectionTimeout; } /** * Возвращает настройки прокси. */ public function getProxy(): string { return $this->proxy; } /** * Устанавливает настройки прокси. * * @param string $proxy Прокси сервер */ public function setProxy(string $proxy): void { $this->proxy = $proxy; } /** * Возвращает настройки. */ public function getConfig(): array { return $this->config; } /** * Устанавливает настройки. * * @param array $config Настройки клиента */ public function setConfig(array $config): void { $this->config = $config; } /** * Возвращает UserAgent. */ public function getUserAgent(): UserAgent { return $this->userAgent; } /** * Устанавливает OAuth-токен магазина. * * @param null|string $bearerToken OAuth-токен магазина * * @return $this */ public function setBearerToken(?string $bearerToken): self { $this->bearerToken = $bearerToken; return $this; } /** * Устанавливает флаг сохранения соединения. * * @param bool $keepAlive Флаг сохранения настроек * * @return $this */ public function setKeepAlive(bool $keepAlive): self { $this->keepAlive = $keepAlive; return $this; } public function setAdvancedCurlOptions(): void { } /** * @return void * * @throws ExtensionNotFoundException */ private function initCurl(): void { if (!extension_loaded('curl')) { throw new ExtensionNotFoundException('curl'); } if (!$this->curl || !$this->keepAlive) { $this->curl = curl_init(); } } /** * @throws ApiConnectionException */ private function handleCurlError(string $error, int $errno): void { $msg = match ($errno) { CURLE_COULDNT_CONNECT, CURLE_COULDNT_RESOLVE_HOST, CURLE_OPERATION_TIMEOUTED => 'Could not connect to YooKassa API. Please check your internet connection and try again.', CURLE_SSL_CACERT, CURLE_SSL_PEER_CERTIFICATE => 'Could not verify SSL certificate.', default => 'Unexpected error communicating.', }; $msg .= sprintf("\n\n(Network error [errno %s]: %s)", $errno, $error); throw new ApiConnectionException($msg); } /** * Возвращает базовый URL для API Кассы * * @return string */ private function getUrl(): string { $config = $this->config; return (string) $config['url']; } /** * @throws AuthorizeException */ private function prepareHeaders(array $headers): array { $headers = array_merge($this->defaultHeaders, $headers); $headers[UserAgent::HEADER] = $this->getUserAgent()->getHeaderString(); if ($this->shopId && $this->shopPassword) { $encodedAuth = base64_encode($this->shopId . ':' . $this->shopPassword); $headers['Authorization'] = 'Basic ' . $encodedAuth; } elseif ($this->bearerToken) { $headers['Authorization'] = 'Bearer ' . $this->bearerToken; } if (empty($headers['Authorization'])) { throw new AuthorizeException('Authorization headers not set'); } return $headers; } private function implodeHeaders(array $headers): array { return array_map(static fn ($key, $value) => $key . ':' . $value, array_keys($headers), $headers); } private function logRequestParams(string $path, string $method, array $queryParams = [], ?string $httpBody = null, array $headers = []): void { if (null !== $this->logger) { $message = 'Send request: ' . $method . ' ' . $path; $context = []; if (!empty($queryParams)) { $context['_params'] = $queryParams; } if (!empty($httpBody)) { $data = json_decode($httpBody, true); if (JSON_ERROR_NONE !== json_last_error()) { $data = $httpBody; } $context['_body'] = $data; } if (!empty($headers)) { $context['_headers'] = $headers; } $this->logger->info($message, $context); } } private function prepareUrl(string $path, array $queryParams): string { $url = $this->getUrl() . $path; if (!empty($queryParams)) { $url .= '?' . http_build_query($queryParams); } return $url; } private function logResponse(string $httpBody, array $responseInfo, array $httpHeaders): void { if (null !== $this->logger) { $message = 'Response with code ' . $responseInfo['http_code'] . ' received.'; $context = []; if (!empty($httpBody)) { $data = json_decode($httpBody, true); if (JSON_ERROR_NONE !== json_last_error()) { $data = $httpBody; } $context['_body'] = $data; } if (!empty($httpHeaders)) { $context['_headers'] = $httpHeaders; } $this->logger->info($message, $context); } } /** * @throws ExtensionNotFoundException */ private function prepareCurl(string $method, string $url, ?string $httpBody = null, array $headers = []): void { $this->initCurl(); $this->setCurlOption(CURLOPT_URL, $url); $this->setCurlOption(CURLOPT_RETURNTRANSFER, true); $this->setCurlOption(CURLOPT_HEADER, true); if ($this->proxy) { $this->setCurlOption(CURLOPT_PROXY, $this->proxy); $this->setCurlOption(CURLOPT_HTTPPROXYTUNNEL, true); } $this->setBody($method, $httpBody); $this->setCurlOption(CURLOPT_HTTPHEADER, $headers); $this->setCurlOption(CURLOPT_CONNECTTIMEOUT, $this->connectionTimeout); $this->setCurlOption(CURLOPT_TIMEOUT, $this->timeout); $this->setAdvancedCurlOptions(); } }