Initial Commit
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2020 "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 Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Interface ApiClientInterface
|
||||
* @package YooKassa\Client
|
||||
*/
|
||||
interface ApiClientInterface
|
||||
{
|
||||
/**
|
||||
* @param $path
|
||||
* @param $method
|
||||
* @param $queryParams
|
||||
* @param $httpBody
|
||||
* @param $headers
|
||||
* @return mixed
|
||||
*/
|
||||
public function call($path, $method, $queryParams, $httpBody = null, $headers = array());
|
||||
|
||||
/**
|
||||
* @param LoggerInterface|null $logger
|
||||
*/
|
||||
public function setLogger($logger);
|
||||
|
||||
/**
|
||||
* @return UserAgent
|
||||
*/
|
||||
public function getUserAgent();
|
||||
|
||||
/**
|
||||
* @param $shopId
|
||||
* @return mixed
|
||||
*/
|
||||
public function setShopId($shopId);
|
||||
|
||||
/**
|
||||
* @param $shopPassword
|
||||
* @return mixed
|
||||
*/
|
||||
public function setShopPassword($shopPassword);
|
||||
|
||||
/**
|
||||
* @param $bearerToken
|
||||
* @return mixed
|
||||
*/
|
||||
public function setBearerToken($bearerToken);
|
||||
|
||||
/**
|
||||
* @param array $config
|
||||
*/
|
||||
public function setConfig($config);
|
||||
}
|
||||
@@ -0,0 +1,410 @@
|
||||
<?php
|
||||
/**
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2020 "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 Exception;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use YooKassa\Common\Exceptions\ApiConnectionException;
|
||||
use YooKassa\Common\Exceptions\ApiException;
|
||||
use YooKassa\Common\Exceptions\AuthorizeException;
|
||||
use YooKassa\Common\Exceptions\BadApiRequestException;
|
||||
use YooKassa\Common\Exceptions\ExtensionNotFoundException;
|
||||
use YooKassa\Common\Exceptions\ForbiddenException;
|
||||
use YooKassa\Common\Exceptions\InternalServerError;
|
||||
use YooKassa\Common\Exceptions\JsonException;
|
||||
use YooKassa\Common\Exceptions\NotFoundException;
|
||||
use YooKassa\Common\Exceptions\ResponseProcessingException;
|
||||
use YooKassa\Common\Exceptions\TooManyRequestsException;
|
||||
use YooKassa\Common\Exceptions\UnauthorizedException;
|
||||
use YooKassa\Common\LoggerWrapper;
|
||||
use YooKassa\Common\ResponseObject;
|
||||
use YooKassa\Helpers\Config\ConfigurationLoader;
|
||||
use YooKassa\Helpers\Config\ConfigurationLoaderInterface;
|
||||
|
||||
class BaseClient
|
||||
{
|
||||
const PAYMENTS_PATH = '/payments';
|
||||
const REFUNDS_PATH = '/refunds';
|
||||
const WEBHOOKS_PATH = '/webhooks';
|
||||
const RECEIPTS_PATH = '/receipts';
|
||||
const ME_PATH = '/me';
|
||||
|
||||
/**
|
||||
* Имя HTTP заголовка, используемого для передачи idempotence key
|
||||
*/
|
||||
const IDEMPOTENCY_KEY_HEADER = 'Idempotence-Key';
|
||||
|
||||
/**
|
||||
* Значение по умолчанию времени ожидания между запросами при отправке повторного запроса в случае получения
|
||||
* ответа с HTTP статусом 202
|
||||
*/
|
||||
const DEFAULT_DELAY = 1800;
|
||||
|
||||
/**
|
||||
* Значение по умолчанию количества попыток получения информации от API если пришёл ответ с HTTP статусом 202
|
||||
*/
|
||||
const DEFAULT_TRIES_COUNT = 3;
|
||||
|
||||
/**
|
||||
* Значение по умолчанию количества попыток получения информации от API если пришёл ответ с HTTP статусом 202
|
||||
*/
|
||||
const DEFAULT_ATTEMPTS_COUNT = 3;
|
||||
|
||||
/**
|
||||
* @var null|ApiClientInterface
|
||||
*/
|
||||
protected $apiClient;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $login;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $password;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* Время через которое будут осуществляться повторные запросы
|
||||
* Значение по умолчанию - 1800 миллисекунд.
|
||||
* @var int значение в миллисекундах
|
||||
*/
|
||||
protected $timeout;
|
||||
|
||||
/**
|
||||
* Количество повторных запросов при ответе API статусом 202
|
||||
* Значение по умолчанию 3
|
||||
* @var int
|
||||
*/
|
||||
protected $attempts;
|
||||
|
||||
/**
|
||||
* @var LoggerInterface|null
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param ApiClientInterface|null $apiClient
|
||||
* @param ConfigurationLoaderInterface|null $configLoader
|
||||
*/
|
||||
public function __construct(ApiClientInterface $apiClient = null, ConfigurationLoaderInterface $configLoader = null)
|
||||
{
|
||||
if ($apiClient === null) {
|
||||
$apiClient = new CurlClient();
|
||||
}
|
||||
|
||||
if ($configLoader === null) {
|
||||
$configLoader = new ConfigurationLoader();
|
||||
}
|
||||
$config = $configLoader->load()->getConfig();
|
||||
$this->setConfig($config);
|
||||
$apiClient->setConfig($config);
|
||||
|
||||
$this->attempts = self::DEFAULT_ATTEMPTS_COUNT;
|
||||
$this->apiClient = $apiClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $login
|
||||
* @param $password
|
||||
*
|
||||
* @return static $this
|
||||
*/
|
||||
public function setAuth($login, $password)
|
||||
{
|
||||
$this->login = $login;
|
||||
$this->password = $password;
|
||||
|
||||
$this->apiClient
|
||||
->setBearerToken(null)
|
||||
->setShopId($this->login)
|
||||
->setShopPassword($this->password);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $token
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAuthToken($token)
|
||||
{
|
||||
$this->apiClient
|
||||
->setShopId(null)
|
||||
->setShopPassword(null)
|
||||
->setBearerToken($token);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ApiClientInterface
|
||||
*/
|
||||
public function getApiClient()
|
||||
{
|
||||
return $this->apiClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ApiClientInterface $apiClient
|
||||
*
|
||||
* @return static $this
|
||||
*/
|
||||
public function setApiClient(ApiClientInterface $apiClient)
|
||||
{
|
||||
$this->apiClient = $apiClient;
|
||||
$this->apiClient->setConfig($this->config);
|
||||
$this->apiClient->setLogger($this->logger);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает логгер приложения
|
||||
*
|
||||
* @param null|callable|object|LoggerInterface $value Инстанс логгера
|
||||
*/
|
||||
public function setLogger($value)
|
||||
{
|
||||
if ($value === null || $value instanceof LoggerInterface) {
|
||||
$this->logger = $value;
|
||||
} else {
|
||||
$this->logger = new LoggerWrapper($value);
|
||||
}
|
||||
if ($this->apiClient !== null) {
|
||||
$this->apiClient->setLogger($this->logger);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getConfig()
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $config
|
||||
*/
|
||||
public function setConfig($config)
|
||||
{
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Установка значение задержки между повторными запросами
|
||||
*
|
||||
* @param int $timeout
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function setRetryTimeout($timeout)
|
||||
{
|
||||
$this->timeout = $timeout;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Установка значения количества попыток повторных запросов при статусе 202
|
||||
*
|
||||
* @param int $attempts
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function setMaxRequestAttempts($attempts)
|
||||
{
|
||||
$this->attempts = $attempts;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $serializedData
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function encodeData($serializedData)
|
||||
{
|
||||
if ($serializedData === array()) {
|
||||
return '{}';
|
||||
}
|
||||
|
||||
if (defined('JSON_UNESCAPED_UNICODE') && defined('JSON_UNESCAPED_SLASHES')) {
|
||||
$encoded = json_encode($serializedData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
} else {
|
||||
$encoded = self::_unescaped(json_encode($serializedData));
|
||||
}
|
||||
|
||||
if ($encoded === false) {
|
||||
$errorCode = json_last_error();
|
||||
throw new JsonException("Failed serialize json.", $errorCode);
|
||||
}
|
||||
|
||||
return $encoded;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $json
|
||||
* @return string|false
|
||||
*/
|
||||
private static function _unescaped($json)
|
||||
{
|
||||
if ($json === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$json = str_replace('\\/', '/', $json);
|
||||
|
||||
return preg_replace_callback('/\\\\u(\w{4})/', function ($matches) {
|
||||
return html_entity_decode('&#x' . $matches[1] . ';', ENT_COMPAT, 'UTF-8');
|
||||
}, $json);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ResponseObject $response
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function decodeData(ResponseObject $response)
|
||||
{
|
||||
$resultArray = json_decode($response->getBody(), true);
|
||||
if ($resultArray === null) {
|
||||
throw new JsonException('Failed to decode response', json_last_error());
|
||||
}
|
||||
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ResponseObject $response
|
||||
*
|
||||
* @throws ApiException
|
||||
* @throws BadApiRequestException
|
||||
* @throws ForbiddenException
|
||||
* @throws InternalServerError
|
||||
* @throws NotFoundException
|
||||
* @throws ResponseProcessingException
|
||||
* @throws TooManyRequestsException
|
||||
* @throws UnauthorizedException
|
||||
*/
|
||||
protected function handleError(ResponseObject $response)
|
||||
{
|
||||
switch ($response->getCode()) {
|
||||
case BadApiRequestException::HTTP_CODE:
|
||||
throw new BadApiRequestException($response->getHeaders(), $response->getBody());
|
||||
break;
|
||||
case ForbiddenException::HTTP_CODE:
|
||||
throw new ForbiddenException($response->getHeaders(), $response->getBody());
|
||||
break;
|
||||
case UnauthorizedException::HTTP_CODE:
|
||||
throw new UnauthorizedException($response->getHeaders(), $response->getBody());
|
||||
break;
|
||||
case InternalServerError::HTTP_CODE:
|
||||
throw new InternalServerError($response->getHeaders(), $response->getBody());
|
||||
break;
|
||||
case NotFoundException::HTTP_CODE:
|
||||
throw new NotFoundException($response->getHeaders(), $response->getBody());
|
||||
break;
|
||||
case TooManyRequestsException::HTTP_CODE:
|
||||
throw new TooManyRequestsException($response->getHeaders(), $response->getBody());
|
||||
break;
|
||||
case ResponseProcessingException::HTTP_CODE:
|
||||
throw new ResponseProcessingException($response->getHeaders(), $response->getBody());
|
||||
break;
|
||||
default:
|
||||
if ($response->getCode() > 399) {
|
||||
throw new ApiException(
|
||||
'Unexpected response error code',
|
||||
$response->getCode(),
|
||||
$response->getHeaders(),
|
||||
$response->getBody()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Задержка между повторными запросами
|
||||
*
|
||||
* @param $response
|
||||
*/
|
||||
protected function delay($response)
|
||||
{
|
||||
$timeout = $this->timeout;
|
||||
$responseData = $this->decodeData($response);
|
||||
if ($timeout) {
|
||||
$delay = $timeout;
|
||||
} else {
|
||||
if (isset($responseData['retry_after'])) {
|
||||
$delay = $responseData['retry_after'];
|
||||
} else {
|
||||
$delay = self::DEFAULT_DELAY;
|
||||
}
|
||||
}
|
||||
usleep($delay * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Выполнение запроса и обработка 202 статуса
|
||||
*
|
||||
* @param $path
|
||||
* @param $method
|
||||
* @param $queryParams
|
||||
* @param null $httpBody
|
||||
* @param array $headers
|
||||
*
|
||||
* @return mixed|ResponseObject
|
||||
* @throws ApiException
|
||||
* @throws AuthorizeException
|
||||
* @throws ApiConnectionException
|
||||
* @throws ExtensionNotFoundException
|
||||
*/
|
||||
protected function execute($path, $method, $queryParams, $httpBody = null, $headers = array())
|
||||
{
|
||||
$attempts = $this->attempts;
|
||||
$response = $this->apiClient->call($path, $method, $queryParams, $httpBody, $headers);
|
||||
|
||||
while (in_array($response->getCode(), array(202, 500)) && $attempts > 0) {
|
||||
$this->delay($response);
|
||||
$attempts--;
|
||||
$response = $this->apiClient->call($path, $method, $queryParams, $httpBody, $headers);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,527 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2020 "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 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;
|
||||
|
||||
/**
|
||||
* Class CurlClient
|
||||
* @package YooKassa\Client
|
||||
*/
|
||||
class CurlClient implements ApiClientInterface
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $shopId;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $shopPassword;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $bearerToken;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $timeout = 80;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $connectionTimeout = 30;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $proxy;
|
||||
|
||||
/** @var UserAgent */
|
||||
private $userAgent;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $keepAlive = true;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $defaultHeaders = array(
|
||||
'Content-Type' => 'application/json',
|
||||
'Accept' => 'application/json',
|
||||
);
|
||||
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
private $curl;
|
||||
|
||||
/**
|
||||
* @var LoggerInterface|null
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* CurlClient constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->userAgent = new UserAgent();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LoggerInterface|null $logger
|
||||
*/
|
||||
public function setLogger($logger)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*
|
||||
* @param $path
|
||||
* @param $method
|
||||
* @param $queryParams
|
||||
* @param null $httpBody
|
||||
* @param array $headers
|
||||
*
|
||||
* @return ResponseObject
|
||||
* @throws ApiConnectionException
|
||||
* @throws ApiException
|
||||
* @throws AuthorizeException
|
||||
* @throws ExtensionNotFoundException
|
||||
*/
|
||||
public function call($path, $method, $queryParams, $httpBody = null, $headers = array())
|
||||
{
|
||||
$headers = $this->prepareHeaders($headers);
|
||||
|
||||
$this->logRequestParams($path, $method, $queryParams, $httpBody, $headers);
|
||||
|
||||
$url = $this->prepareUrl($path, $queryParams);
|
||||
|
||||
$this->prepareCurl($method, $httpBody, $this->implodeHeaders($headers), $url);
|
||||
|
||||
list($httpHeaders, $httpBody, $responseInfo) = $this->sendRequest();
|
||||
|
||||
if (!$this->keepAlive) {
|
||||
$this->closeCurlConnection();
|
||||
}
|
||||
|
||||
$this->logResponse($httpBody, $responseInfo, $httpHeaders);
|
||||
|
||||
return new ResponseObject(array(
|
||||
'code' => $responseInfo['http_code'],
|
||||
'headers' => $httpHeaders,
|
||||
'body' => $httpBody,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $optionName
|
||||
* @param $optionValue
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function setCurlOption($optionName, $optionValue)
|
||||
{
|
||||
return curl_setopt($this->curl, $optionName, $optionValue);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return resource
|
||||
* @throws ExtensionNotFoundException
|
||||
*/
|
||||
private function initCurl()
|
||||
{
|
||||
if (!extension_loaded('curl')) {
|
||||
throw new ExtensionNotFoundException('curl');
|
||||
}
|
||||
|
||||
if (!$this->curl || !$this->keepAlive) {
|
||||
$this->curl = curl_init();
|
||||
}
|
||||
|
||||
return $this->curl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close connection
|
||||
*/
|
||||
public function closeCurlConnection()
|
||||
{
|
||||
if ($this->curl !== null) {
|
||||
curl_close($this->curl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @throws ApiConnectionException
|
||||
*/
|
||||
public function sendRequest()
|
||||
{
|
||||
$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 ($response === false) {
|
||||
$this->handleCurlError($curlError, $curlErrno);
|
||||
}
|
||||
|
||||
return array($httpHeaders, $httpBody, $responseInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $method
|
||||
* @param $httpBody
|
||||
*/
|
||||
public function setBody($method, $httpBody)
|
||||
{
|
||||
|
||||
$this->setCurlOption(CURLOPT_CUSTOMREQUEST, $method);
|
||||
if(!empty($httpBody)) {
|
||||
$this->setCurlOption(CURLOPT_POSTFIELDS, $httpBody);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $shopId
|
||||
*
|
||||
* @return CurlClient
|
||||
*/
|
||||
public function setShopId($shopId)
|
||||
{
|
||||
$this->shopId = $shopId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $shopPassword
|
||||
*
|
||||
* @return CurlClient
|
||||
*/
|
||||
public function setShopPassword($shopPassword)
|
||||
{
|
||||
$this->shopPassword = $shopPassword;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getTimeout()
|
||||
{
|
||||
return $this->timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $timeout
|
||||
*/
|
||||
public function setTimeout($timeout)
|
||||
{
|
||||
$this->timeout = $timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getConnectionTimeout()
|
||||
{
|
||||
return $this->connectionTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $connectionTimeout
|
||||
*/
|
||||
public function setConnectionTimeout($connectionTimeout)
|
||||
{
|
||||
$this->connectionTimeout = $connectionTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @since 1.0.14
|
||||
*/
|
||||
public function getProxy()
|
||||
{
|
||||
return $this->proxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $proxy
|
||||
*
|
||||
* @since 1.0.14
|
||||
*/
|
||||
public function setProxy($proxy)
|
||||
{
|
||||
$this->proxy = $proxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getConfig()
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function setConfig($config)
|
||||
{
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return UserAgent
|
||||
*/
|
||||
public function getUserAgent()
|
||||
{
|
||||
return $this->userAgent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $bearerToken
|
||||
*
|
||||
* @return static $this
|
||||
*/
|
||||
public function setBearerToken($bearerToken)
|
||||
{
|
||||
$this->bearerToken = $bearerToken;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $keepAlive
|
||||
*
|
||||
* @return CurlClient
|
||||
*/
|
||||
public function setKeepAlive($keepAlive)
|
||||
{
|
||||
$this->keepAlive = $keepAlive;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $error
|
||||
* @param int $errno
|
||||
*
|
||||
* @throws ApiConnectionException
|
||||
*/
|
||||
private function handleCurlError($error, $errno)
|
||||
{
|
||||
switch ($errno) {
|
||||
case CURLE_COULDNT_CONNECT:
|
||||
case CURLE_COULDNT_RESOLVE_HOST:
|
||||
case CURLE_OPERATION_TIMEOUTED:
|
||||
$msg = 'Could not connect to YooKassa API. Please check your internet connection and try again.';
|
||||
break;
|
||||
case CURLE_SSL_CACERT:
|
||||
case CURLE_SSL_PEER_CERTIFICATE:
|
||||
$msg = 'Could not verify SSL certificate.';
|
||||
break;
|
||||
default:
|
||||
$msg = 'Unexpected error communicating.';
|
||||
}
|
||||
$msg .= "\n\n(Network error [errno $errno]: $error)";
|
||||
throw new ApiConnectionException($msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
private function getUrl()
|
||||
{
|
||||
$config = $this->config;
|
||||
|
||||
return $config['url'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $headers
|
||||
*
|
||||
* @return array
|
||||
* @throws AuthorizeException
|
||||
*/
|
||||
private function prepareHeaders($headers)
|
||||
{
|
||||
$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;
|
||||
} else if ($this->bearerToken) {
|
||||
$headers['Authorization'] = 'Bearer ' . $this->bearerToken;
|
||||
}
|
||||
|
||||
if (empty($headers['Authorization'])) {
|
||||
throw new AuthorizeException('Authorization headers not set');
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $headers
|
||||
* @return array
|
||||
*/
|
||||
private function implodeHeaders($headers)
|
||||
{
|
||||
return array_map(function ($key, $value) { return $key . ':' . $value; }, array_keys($headers), $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $path
|
||||
* @param $method
|
||||
* @param $queryParams
|
||||
* @param $httpBody
|
||||
* @param $headers
|
||||
*/
|
||||
private function logRequestParams($path, $method, $queryParams, $httpBody, $headers)
|
||||
{
|
||||
if ($this->logger !== null) {
|
||||
$message = 'Send request: ' . $method . ' ' . $path;
|
||||
$context = array();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $path
|
||||
* @param $queryParams
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function prepareUrl($path, $queryParams)
|
||||
{
|
||||
$url = $this->getUrl() . $path;
|
||||
|
||||
if (!empty($queryParams)) {
|
||||
$url = $url . '?' . http_build_query($queryParams);
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $httpBody
|
||||
* @param $responseInfo
|
||||
* @param $httpHeaders
|
||||
*/
|
||||
private function logResponse($httpBody, $responseInfo, $httpHeaders)
|
||||
{
|
||||
if ($this->logger !== null) {
|
||||
$message = 'Response with code ' . $responseInfo['http_code'] . ' received.';
|
||||
$context = array();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $method
|
||||
* @param $httpBody
|
||||
* @param $headers
|
||||
* @param $url
|
||||
* @throws ExtensionNotFoundException
|
||||
*/
|
||||
private function prepareCurl($method, $httpBody, $headers, $url)
|
||||
{
|
||||
$this->initCurl();
|
||||
|
||||
$this->setCurlOption(CURLOPT_URL, $url);
|
||||
|
||||
$this->setCurlOption(CURLOPT_RETURNTRANSFER, true);
|
||||
|
||||
$this->setCurlOption(CURLOPT_HEADER, true);
|
||||
|
||||
$this->setCurlOption(CURLOPT_BINARYTRANSFER, 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2020 "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 YooKassa\Client;
|
||||
|
||||
/**
|
||||
* Class UserAgent
|
||||
* @package YooKassa\Client
|
||||
*/
|
||||
class UserAgent
|
||||
{
|
||||
const HEADER = 'YM-User-Agent';
|
||||
const VERSION_DELIMITER = '/';
|
||||
const PART_DELIMITER = ' ';
|
||||
|
||||
private $_os = null;
|
||||
private $_php = null;
|
||||
private $_framework = null;
|
||||
private $_cms = null;
|
||||
private $_module = null;
|
||||
private $_sdk = null;
|
||||
|
||||
/**
|
||||
* UserAgent constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
if ($os = $this->defineOs()) {
|
||||
$this->setOs($os['name'], $os['version']);
|
||||
}
|
||||
if ($php = $this->definePhp()) {
|
||||
$this->setPhp($php['name'], $php['version']);
|
||||
}
|
||||
$this->setSdk('YooKassa.PHP', Client::SDK_VERSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getHeaderString()
|
||||
{
|
||||
$result = array();
|
||||
|
||||
$result[] = $this->getOs();
|
||||
$result[] = $this->getPhp();
|
||||
|
||||
if ($string = $this->getFramework()) {
|
||||
$result[] = $string;
|
||||
}
|
||||
if ($string = $this->getCms()) {
|
||||
$result[] = $string;
|
||||
}
|
||||
if ($string = $this->getModule()) {
|
||||
$result[] = $string;
|
||||
}
|
||||
|
||||
$result[] = $this->getSdk();
|
||||
|
||||
return implode(self::PART_DELIMITER, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getOs()
|
||||
{
|
||||
return $this->_os;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param string $version
|
||||
*/
|
||||
private function setOs($name, $version)
|
||||
{
|
||||
$this->_os = $this->createVersion($name, $version);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPhp()
|
||||
{
|
||||
return $this->_php;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param string $version
|
||||
*/
|
||||
private function setPhp($name, $version)
|
||||
{
|
||||
$this->_php = $this->createVersion($name, $version);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getFramework()
|
||||
{
|
||||
return $this->_framework;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param string $version
|
||||
*/
|
||||
public function setFramework($name, $version)
|
||||
{
|
||||
$this->_framework = $this->createVersion($name, $version);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null
|
||||
*/
|
||||
public function getCms()
|
||||
{
|
||||
return $this->_cms;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param string $version
|
||||
*/
|
||||
public function setCms($name, $version)
|
||||
{
|
||||
$this->_cms = $this->createVersion($name, $version);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getModule()
|
||||
{
|
||||
return $this->_module;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param string $version
|
||||
*/
|
||||
public function setModule($name, $version)
|
||||
{
|
||||
$this->_module = $this->createVersion($name, $version);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSdk()
|
||||
{
|
||||
return $this->_sdk;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param string $version
|
||||
*/
|
||||
private function setSdk($name, $version)
|
||||
{
|
||||
$this->_sdk = $this->createVersion($name, $version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Попытка определить систему
|
||||
* @return array
|
||||
*/
|
||||
private function defineOs()
|
||||
{
|
||||
if (strtolower(substr(PHP_OS, 0, 5)) === 'linux') {
|
||||
if ($result = $this->parseSimpleLinuxRelease()) {
|
||||
return $result;
|
||||
} elseif ($result = $this->parseSmartLinuxRelease()) {
|
||||
return $result;
|
||||
}
|
||||
} else {
|
||||
return array( 'name' => php_uname('s'), 'version' => php_uname('r') );
|
||||
}
|
||||
|
||||
return array( 'name' => 'Undefined', 'version' => '0.0.0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает информацию о версии системы
|
||||
* Используется сложный вариант
|
||||
* @return array|null
|
||||
*/
|
||||
private function parseSmartLinuxRelease()
|
||||
{
|
||||
$vars = array();
|
||||
|
||||
if ($files = glob('/etc/*elease')) {
|
||||
foreach ($files as $file) {
|
||||
if (is_file($file)) {
|
||||
$lines = array_filter(array_map(array($this, 'callbackSmartLinux'), file($file)));
|
||||
if (is_array($lines)) {
|
||||
foreach ($lines as $line) {
|
||||
$vars[strtoupper($line[0])] = trim($line[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($vars['NAME']) && !empty($vars['VERSION_ID'])) {
|
||||
return array('name' => $vars['NAME'], 'version' => $vars['VERSION_ID']);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $line
|
||||
* @return array|bool
|
||||
*/
|
||||
private static function callbackSmartLinux($line)
|
||||
{
|
||||
$parts = explode('=', $line);
|
||||
if (count($parts) !== 2) {
|
||||
return false;
|
||||
}
|
||||
$parts[1] = trim(str_replace(array('"', "'"), '', $parts[1]));
|
||||
return $parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает информацию о версии системы
|
||||
* Используется простой вариант
|
||||
* @return array|null
|
||||
*/
|
||||
private function parseSimpleLinuxRelease()
|
||||
{
|
||||
$vars = array();
|
||||
|
||||
if ($files = glob('/etc/*elease')) {
|
||||
foreach ($files as $file) {
|
||||
if (is_file($file)) {
|
||||
$data = array_map(array($this, 'callbackSimpleLinux'), file($file));
|
||||
if (!empty($data)) {
|
||||
$array = array_shift($data);
|
||||
if (!empty($array) && is_array($array)) {
|
||||
$vars = array_merge($vars, $array);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !empty($vars['name']) && !empty($vars['version']) ? $vars : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $line
|
||||
* @return array
|
||||
*/
|
||||
private static function callbackSimpleLinux($line)
|
||||
{
|
||||
$parse = array();
|
||||
preg_match('/(.+) release (.+) (.+)/iu', $line, $parts);
|
||||
if (!empty($parts[1])) {
|
||||
$parse['name'] = str_replace(' ', '.', trim($parts[1]));
|
||||
}
|
||||
if (!empty($parts[2])) {
|
||||
$parse['version'] = trim($parts[2]);
|
||||
}
|
||||
return $parse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Определение версии PHP
|
||||
* @return array
|
||||
*/
|
||||
private function definePhp()
|
||||
{
|
||||
return array(
|
||||
'name' => 'PHP',
|
||||
'version' => PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Создание строки версии компонента
|
||||
* @param string $name
|
||||
* @param string $version
|
||||
* @return string
|
||||
*/
|
||||
public function createVersion($name, $version)
|
||||
{
|
||||
return str_replace(array(self::PART_DELIMITER, self::VERSION_DELIMITER), '.', trim($name))
|
||||
. self::VERSION_DELIMITER
|
||||
. str_replace(array(self::PART_DELIMITER, self::VERSION_DELIMITER), '.', trim($version));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user