1
0
mirror of synced 2024-11-26 06:46:02 +03:00

refactor client workflow which resulted in correct behaviour for signatures

This commit is contained in:
Pavel 2020-10-06 12:47:38 +03:00
parent 405621bfdf
commit 402c20d184
16 changed files with 211 additions and 257 deletions

View File

@ -36,11 +36,9 @@ use RetailCrm\Interfaces\FileItemFactoryInterface;
use RetailCrm\Interfaces\RequestSignerInterface; use RetailCrm\Interfaces\RequestSignerInterface;
use RetailCrm\Interfaces\RequestTimestampProviderInterface; use RetailCrm\Interfaces\RequestTimestampProviderInterface;
use RetailCrm\Interfaces\TopRequestFactoryInterface; use RetailCrm\Interfaces\TopRequestFactoryInterface;
use RetailCrm\Interfaces\TopRequestProcessorInterface;
use RetailCrm\Service\RequestDataFilter; use RetailCrm\Service\RequestDataFilter;
use RetailCrm\Service\RequestSigner; use RetailCrm\Service\RequestSigner;
use RetailCrm\Service\RequestTimestampProvider; use RetailCrm\Service\RequestTimestampProvider;
use RetailCrm\Service\TopRequestProcessor;
use RuntimeException; use RuntimeException;
use Symfony\Component\Validator\Validation; use Symfony\Component\Validator\Validation;
use Symfony\Component\Validator\Validator\TraceableValidator; use Symfony\Component\Validator\Validator\TraceableValidator;
@ -230,16 +228,7 @@ class ContainerBuilder implements BuilderInterface
}); });
$container->set(RequestDataFilter::class, new RequestDataFilter()); $container->set(RequestDataFilter::class, new RequestDataFilter());
$container->set(RequestSignerInterface::class, function (ContainerInterface $container) { $container->set(RequestSignerInterface::class, function (ContainerInterface $container) {
return new RequestSigner( return new RequestSigner($container->get(RequestDataFilter::class));
$container->get(Constants::SERIALIZER),
$container->get(RequestDataFilter::class)
);
});
$container->set(TopRequestProcessorInterface::class, function (ContainerInterface $container) {
return (new TopRequestProcessor())
->setSigner($container->get(RequestSignerInterface::class))
->setValidator($container->get(Constants::VALIDATOR))
->setTimestampProvider($container->get(RequestTimestampProviderInterface::class));
}); });
$container->set(TopRequestFactoryInterface::class, function (ContainerInterface $container) { $container->set(TopRequestFactoryInterface::class, function (ContainerInterface $container) {
return (new TopRequestFactory()) return (new TopRequestFactory())
@ -247,7 +236,9 @@ class ContainerBuilder implements BuilderInterface
->setSerializer($container->get(Constants::SERIALIZER)) ->setSerializer($container->get(Constants::SERIALIZER))
->setStreamFactory($container->get(StreamFactoryInterface::class)) ->setStreamFactory($container->get(StreamFactoryInterface::class))
->setRequestFactory($container->get(RequestFactoryInterface::class)) ->setRequestFactory($container->get(RequestFactoryInterface::class))
->setUriFactory($container->get(UriFactoryInterface::class)); ->setUriFactory($container->get(UriFactoryInterface::class))
->setSigner($container->get(RequestSignerInterface::class))
->setTimestampProvider($container->get(RequestTimestampProviderInterface::class));
}); });
$container->set(ServiceLocator::class, function (ContainerInterface $container) { $container->set(ServiceLocator::class, function (ContainerInterface $container) {
$locator = new ServiceLocator(); $locator = new ServiceLocator();

View File

@ -15,14 +15,12 @@ namespace RetailCrm\Builder;
use RetailCrm\Component\Constants; use RetailCrm\Component\Constants;
use RetailCrm\Component\Environment; use RetailCrm\Component\Environment;
use RetailCrm\Component\ServiceLocator; use RetailCrm\Component\ServiceLocator;
use RetailCrm\Component\Storage\ProductSchemaStorage;
use RetailCrm\Factory\ProductSchemaStorageFactory; use RetailCrm\Factory\ProductSchemaStorageFactory;
use RetailCrm\Interfaces\AppDataInterface; use RetailCrm\Interfaces\AppDataInterface;
use RetailCrm\Interfaces\AuthenticatorInterface; use RetailCrm\Interfaces\AuthenticatorInterface;
use RetailCrm\Interfaces\BuilderInterface; use RetailCrm\Interfaces\BuilderInterface;
use RetailCrm\Interfaces\ContainerAwareInterface; use RetailCrm\Interfaces\ContainerAwareInterface;
use RetailCrm\Interfaces\TopRequestFactoryInterface; use RetailCrm\Interfaces\TopRequestFactoryInterface;
use RetailCrm\Interfaces\TopRequestProcessorInterface;
use RetailCrm\TopClient\TopClient; use RetailCrm\TopClient\TopClient;
use RetailCrm\Traits\ContainerAwareTrait; use RetailCrm\Traits\ContainerAwareTrait;
@ -90,7 +88,6 @@ class TopClientBuilder implements ContainerAwareInterface, BuilderInterface
$client->setLogger($this->container->get(Constants::LOGGER)); $client->setLogger($this->container->get(Constants::LOGGER));
$client->setRequestFactory($this->container->get(TopRequestFactoryInterface::class)); $client->setRequestFactory($this->container->get(TopRequestFactoryInterface::class));
$client->setServiceLocator($this->container->get(ServiceLocator::class)); $client->setServiceLocator($this->container->get(ServiceLocator::class));
$client->setProcessor($this->container->get(TopRequestProcessorInterface::class));
$client->setProductSchemaStorageFactory($this->container->get(ProductSchemaStorageFactory::class)); $client->setProductSchemaStorageFactory($this->container->get(ProductSchemaStorageFactory::class));
if (null !== $this->authenticator) { if (null !== $this->authenticator) {

View File

@ -29,43 +29,28 @@ use Throwable;
class TopApiException extends Exception class TopApiException extends Exception
{ {
/** /**
* @var string $subCode * @var ErrorResponseBody $error
*/ */
private $subCode; private $error;
/**
* @var string $requestId
*/
private $requestId;
/** /**
* TopApiException constructor. * TopApiException constructor.
* *
* @param \RetailCrm\Model\Response\ErrorResponseBody $responseBody * @param \RetailCrm\Model\Response\ErrorResponseBody $responseBody
* @param string|null $requestId * @param \Throwable|null $previous
* @param \Throwable|null $previous
*/ */
public function __construct(ErrorResponseBody $responseBody, ?string $requestId, Throwable $previous = null) public function __construct(ErrorResponseBody $responseBody, Throwable $previous = null)
{ {
parent::__construct($responseBody->msg, $responseBody->code, $previous); parent::__construct($responseBody->msg, $responseBody->code, $previous);
$this->subCode = $responseBody->subCode; $this->error = $responseBody;
$this->requestId = $requestId;
} }
/** /**
* @return string * @return \RetailCrm\Model\Response\ErrorResponseBody
*/ */
public function getSubCode(): ?string public function getError(): ErrorResponseBody
{ {
return $this->subCode; return $this->error;
}
/**
* @return string
*/
public function getRequestId(): ?string
{
return $this->requestId;
} }
} }

View File

@ -98,8 +98,9 @@ class ProductSchemaStorage
$error = new ErrorResponseBody(); $error = new ErrorResponseBody();
$error->msg = $response->responseData->result->errorMessage; $error->msg = $response->responseData->result->errorMessage;
$error->code = (int) $response->responseData->result->errorCode; $error->code = (int) $response->responseData->result->errorCode;
$error->requestId = $response->requestId;
throw new TopApiException($error, $response->requestId); throw new TopApiException($error);
} }
/** /**

View File

@ -19,11 +19,15 @@ use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\UriFactoryInterface; use Psr\Http\Message\UriFactoryInterface;
use RetailCrm\Component\Exception\FactoryException; use RetailCrm\Component\Exception\FactoryException;
use RetailCrm\Component\Exception\NotImplementedException;
use RetailCrm\Interfaces\AppDataInterface; use RetailCrm\Interfaces\AppDataInterface;
use RetailCrm\Interfaces\FileItemInterface; use RetailCrm\Interfaces\FileItemInterface;
use RetailCrm\Interfaces\RequestSignerInterface;
use RetailCrm\Interfaces\RequestTimestampProviderInterface;
use RetailCrm\Interfaces\TopRequestFactoryInterface; use RetailCrm\Interfaces\TopRequestFactoryInterface;
use RetailCrm\Model\Request\BaseRequest; use RetailCrm\Model\Request\BaseRequest;
use RetailCrm\Service\RequestDataFilter; use RetailCrm\Service\RequestDataFilter;
use RetailCrm\Service\TopRequestProcessor;
use UnexpectedValueException; use UnexpectedValueException;
/** /**
@ -64,6 +68,38 @@ class TopRequestFactory implements TopRequestFactoryInterface
*/ */
private $uriFactory; private $uriFactory;
/**
* @var \RetailCrm\Interfaces\RequestSignerInterface $signer
*/
private $signer;
/**
* @var RequestTimestampProviderInterface $timestampProvider
*/
private $timestampProvider;
/**
* @param \RetailCrm\Interfaces\RequestSignerInterface $signer
*
* @return \RetailCrm\Factory\TopRequestFactory
*/
public function setSigner(RequestSignerInterface $signer): TopRequestFactory
{
$this->signer = $signer;
return $this;
}
/**
* @param \RetailCrm\Interfaces\RequestTimestampProviderInterface $timestampProvider
*
* @return \RetailCrm\Factory\TopRequestFactory
*/
public function setTimestampProvider(RequestTimestampProviderInterface $timestampProvider): TopRequestFactory
{
$this->timestampProvider = $timestampProvider;
return $this;
}
/** /**
* @param \RetailCrm\Service\RequestDataFilter $filter * @param \RetailCrm\Service\RequestDataFilter $filter
* *
@ -119,6 +155,33 @@ class TopRequestFactory implements TopRequestFactoryInterface
return $this; return $this;
} }
/**
* @param \RetailCrm\Model\Request\BaseRequest $request
*
* @return array
* @throws \RetailCrm\Component\Exception\FactoryException
*/
public function getRequestArray(BaseRequest $request): array
{
$requestData = $this->serializer->toArray($request);
foreach ($requestData as $key => $value) {
if ($value instanceof FileItemInterface) {
continue;
}
$requestData[$key] = $this->castValue($value);
}
if (empty($requestData)) {
throw new FactoryException('Empty request data');
}
ksort($requestData);
return $requestData;
}
/** /**
* @param \RetailCrm\Model\Request\BaseRequest $request * @param \RetailCrm\Model\Request\BaseRequest $request
* @param \RetailCrm\Interfaces\AppDataInterface $appData * @param \RetailCrm\Interfaces\AppDataInterface $appData
@ -130,22 +193,20 @@ class TopRequestFactory implements TopRequestFactoryInterface
BaseRequest $request, BaseRequest $request,
AppDataInterface $appData AppDataInterface $appData
): RequestInterface { ): RequestInterface {
$requestData = $this->serializer->toArray($request); $request->appKey = $appData->getAppKey();
$requestHasBinaryData = $this->filter->hasBinaryFromRequestData($requestData); $this->timestampProvider->provide($request);
$requestData = $this->getRequestArray($request);
ksort($requestData); try {
$requestData['sign'] = $this->signer->generateSign($requestData, $appData, $request->signMethod);
if (empty($requestData)) { } catch (NotImplementedException $exception) {
throw new FactoryException('Empty request data'); throw new FactoryException(sprintf('Cannot sign request: %s', $exception->getMessage()));
} }
if ($requestHasBinaryData) { if ($this->filter->hasBinaryFromRequestData($requestData)) {
return $this->makeMultipartRequest($appData->getServiceUrl(), $requestData); return $this->makeMultipartRequest($appData->getServiceUrl(), $requestData);
} }
//TODO
// And how this call should process arrays? It will process them, yes.
// But in which format AliExpress TOP expects that? Should definitely check that.
$queryData = http_build_query($requestData); $queryData = http_build_query($requestData);
try { try {
@ -177,11 +238,7 @@ class TopRequestFactory implements TopRequestFactoryInterface
if ($value instanceof FileItemInterface) { if ($value instanceof FileItemInterface) {
$builder->addResource($param, $value->getStream(), ['filename' => $value->getFileName()]); $builder->addResource($param, $value->getStream(), ['filename' => $value->getFileName()]);
} else { } else {
$casted = $this->castValue($value); $builder->addResource($param, $value);
if (null !== $casted) {
$builder->addResource($param, $casted);
}
} }
} }
@ -203,6 +260,7 @@ class TopRequestFactory implements TopRequestFactoryInterface
* @param mixed $value * @param mixed $value
* *
* @return string|resource|null * @return string|resource|null
* @todo Arrays will be encoded to JSON. Is this correct? Press X to doubt.
*/ */
private function castValue($value) private function castValue($value)
{ {
@ -217,6 +275,9 @@ class TopRequestFactory implements TopRequestFactoryInterface
case 'double': case 'double':
case 'string': case 'string':
return (string) $value; return (string) $value;
case 'array':
case 'object':
return (string) $this->serializer->serialize($value, 'json');
default: default:
throw new UnexpectedValueException(sprintf('Got value with unsupported type: %s', $type)); throw new UnexpectedValueException(sprintf('Got value with unsupported type: %s', $type));
} }

View File

@ -13,8 +13,6 @@
namespace RetailCrm\Interfaces; namespace RetailCrm\Interfaces;
use RetailCrm\Model\Request\BaseRequest;
/** /**
* Interface RequestSignerInterface * Interface RequestSignerInterface
* *
@ -28,10 +26,14 @@ use RetailCrm\Model\Request\BaseRequest;
interface RequestSignerInterface interface RequestSignerInterface
{ {
/** /**
* Signs provided request. * Generate sign for provided request data.
* *
* @param \RetailCrm\Model\Request\BaseRequest $request * @param array $request
* @param \RetailCrm\Interfaces\AppDataInterface $appData * @param \RetailCrm\Interfaces\AppDataInterface $appData
* @param string $signMethod
*
* @return string
* @throws \RetailCrm\Component\Exception\NotImplementedException
*/ */
public function sign(BaseRequest $request, AppDataInterface $appData): void; public function generateSign(array $request, AppDataInterface $appData, string $signMethod): string;
} }

View File

@ -40,4 +40,11 @@ interface TopRequestFactoryInterface
BaseRequest $request, BaseRequest $request,
AppDataInterface $appData AppDataInterface $appData
): RequestInterface; ): RequestInterface;
/**
* @param \RetailCrm\Model\Request\BaseRequest $request
*
* @return array
*/
public function getRequestArray(BaseRequest $request): array;
} }

View File

@ -1,45 +0,0 @@
<?php
/**
* PHP version 7.3
*
* @category TopRequestProcessorInterface
* @package RetailCrm\Interfaces
* @author RetailCRM <integration@retailcrm.ru>
* @license MIT https://mit-license.org
* @link http://retailcrm.ru
* @see http://help.retailcrm.ru
*/
namespace RetailCrm\Interfaces;
use Psr\Http\Message\RequestInterface;
use RetailCrm\Model\Request\BaseRequest;
/**
* Interface TopRequestProcessorInterface
*
* @category TopRequestProcessorInterface
* @package RetailCrm\Interfaces
* @author RetailDriver LLC <integration@retailcrm.ru>
* @license MIT https://mit-license.org
* @link http://retailcrm.ru
* @see https://help.retailcrm.ru
*/
interface TopRequestProcessorInterface
{
/**
* Modifies request in order to prepare it for TOP API (timestamp, signature, etc).
*
* @param \RetailCrm\Model\Request\BaseRequest $request
* @param \RetailCrm\Interfaces\AppDataInterface $appData
*
* @return void
* @throws \RetailCrm\Component\Exception\FactoryException
* @throws \RetailCrm\Component\Exception\ValidationException
*/
public function process(
BaseRequest $request,
AppDataInterface $appData
): void;
}

View File

@ -36,7 +36,6 @@ class SolutionOrderGet extends BaseRequest
* *
* @JMS\Type("RetailCrm\Model\Request\AliExpress\Data\OrderQuery") * @JMS\Type("RetailCrm\Model\Request\AliExpress\Data\OrderQuery")
* @JMS\SerializedName("param0") * @JMS\SerializedName("param0")
* @todo Should be marshaled to JSON before building request? Check that.
*/ */
public $param0; public $param0;

View File

@ -49,4 +49,20 @@ class ErrorResponseBody
* @JMS\SerializedName("sub_code") * @JMS\SerializedName("sub_code")
*/ */
public $subCode; public $subCode;
/**
* @var string $subMsg
*
* @JMS\Type("string")
* @JMS\SerializedName("sub_msg")
*/
public $subMsg;
/**
* @var string $requestId
*
* @JMS\Type("string")
* @JMS\SerializedName("request_id")
*/
public $requestId;
} }

View File

@ -12,12 +12,10 @@
*/ */
namespace RetailCrm\Service; namespace RetailCrm\Service;
use JMS\Serializer\SerializerInterface;
use RetailCrm\Component\Exception\NotImplementedException; use RetailCrm\Component\Exception\NotImplementedException;
use RetailCrm\Interfaces\AppDataInterface; use RetailCrm\Interfaces\AppDataInterface;
use RetailCrm\Interfaces\RequestSignerInterface; use RetailCrm\Interfaces\RequestSignerInterface;
use RetailCrm\Model\Enum\AvailableSignMethods; use RetailCrm\Model\Enum\AvailableSignMethods;
use RetailCrm\Model\Request\BaseRequest;
/** /**
* Class RequestSigner * Class RequestSigner
@ -28,22 +26,9 @@ use RetailCrm\Model\Request\BaseRequest;
* @license MIT https://mit-license.org * @license MIT https://mit-license.org
* @link http://retailcrm.ru * @link http://retailcrm.ru
* @see https://help.retailcrm.ru * @see https://help.retailcrm.ru
*
*TODO
* AliExpress TOP API won't accept signature generated by this component (it returns 'Invalid signature' error message).
* But I used incorrect session token (it can be found in the .env.dist file) - maybe, that's the problem.
* I cannot obtain session token via authorization URL (it says I don't have redirect URL, but it's present in the URL).
* This NEEDS to be checked, and if the problem remains even with the correct session token, it must be fixed.
* Request signing is a vital part of this library. If it doesn't work properly, then this library suddenly
* turns into pile of garbage.
*/ */
class RequestSigner implements RequestSignerInterface class RequestSigner implements RequestSignerInterface
{ {
/**
* @var SerializerInterface|\JMS\Serializer\Serializer $serializer
*/
private $serializer;
/** /**
* @var RequestDataFilter $filter * @var RequestDataFilter $filter
*/ */
@ -52,22 +37,22 @@ class RequestSigner implements RequestSignerInterface
/** /**
* RequestSigner constructor. * RequestSigner constructor.
* *
* @param \JMS\Serializer\SerializerInterface $serializer
* @param \RetailCrm\Service\RequestDataFilter $filter * @param \RetailCrm\Service\RequestDataFilter $filter
*/ */
public function __construct(SerializerInterface $serializer, RequestDataFilter $filter) public function __construct(RequestDataFilter $filter)
{ {
$this->filter = $filter; $this->filter = $filter;
$this->serializer = $serializer;
} }
/** /**
* @param BaseRequest $request * @param array $request
* @param \RetailCrm\Interfaces\AppDataInterface $appData * @param \RetailCrm\Interfaces\AppDataInterface $appData
* @param string $signMethod
* *
* @return string
* @throws \RetailCrm\Component\Exception\NotImplementedException * @throws \RetailCrm\Component\Exception\NotImplementedException
*/ */
public function sign(BaseRequest $request, AppDataInterface $appData): void public function generateSign(array $request, AppDataInterface $appData, string $signMethod): string
{ {
$stringToBeSigned = ''; $stringToBeSigned = '';
$params = $this->getDataForSigning($request); $params = $this->getDataForSigning($request);
@ -76,33 +61,27 @@ class RequestSigner implements RequestSignerInterface
$stringToBeSigned .= $param . $value; $stringToBeSigned .= $param . $value;
} }
switch ($request->signMethod) { switch ($signMethod) {
case AvailableSignMethods::MD5: case AvailableSignMethods::MD5:
$stringToBeSigned = $appData->getAppSecret() . $stringToBeSigned . $appData->getAppSecret(); $stringToBeSigned = $appData->getAppSecret() . $stringToBeSigned . $appData->getAppSecret();
$request->sign = strtoupper(md5($stringToBeSigned)); return strtoupper(md5($stringToBeSigned));
break;
case AvailableSignMethods::HMAC_MD5: case AvailableSignMethods::HMAC_MD5:
$request->sign = strtoupper(hash_hmac('md5', $stringToBeSigned, $appData->getAppSecret())); return strtoupper(hash_hmac('md5', $stringToBeSigned, $appData->getAppSecret()));
break;
default: default:
throw new NotImplementedException(sprintf('Invalid signing method: %s', $request->signMethod)); throw new NotImplementedException(sprintf('Invalid signing method: %s', $signMethod));
break;
} }
} }
/** /**
* @param \RetailCrm\Model\Request\BaseRequest $request * @param array $request
* *
* @return array * @return array
*/ */
private function getDataForSigning(BaseRequest $request): array private function getDataForSigning(array $request): array
{ {
$params = $this->filter->filterBinaryFromRequestData($this->serializer->toArray($request)); $params = $this->filter->filterBinaryFromRequestData($request);
$params = array_filter(array_filter($params, static function ($val) {
return !is_array($val);
}));
unset($params['sign'], $params['session']); unset($params['sign']);
ksort($params); ksort($params);
return $params; return $params;

View File

@ -1,81 +0,0 @@
<?php
/**
* PHP version 7.3
*
* @category TopRequestProcessor
* @package RetailCrm\Service
* @author RetailCRM <integration@retailcrm.ru>
* @license MIT
* @link http://retailcrm.ru
* @see http://help.retailcrm.ru
*/
namespace RetailCrm\Service;
use RetailCrm\Interfaces\AppDataInterface;
use RetailCrm\Interfaces\RequestSignerInterface;
use RetailCrm\Interfaces\RequestTimestampProviderInterface;
use RetailCrm\Interfaces\TopRequestProcessorInterface;
use RetailCrm\Model\Request\BaseRequest;
use RetailCrm\Traits\ValidatorAwareTrait;
/**
* Class TopRequestProcessor
*
* @category TopRequestProcessor
* @package RetailCrm\Service
* @author RetailDriver LLC <integration@retailcrm.ru>
* @license MIT
* @link http://retailcrm.ru
* @see https://help.retailcrm.ru
*/
class TopRequestProcessor implements TopRequestProcessorInterface
{
use ValidatorAwareTrait;
/**
* @var \RetailCrm\Interfaces\RequestSignerInterface $signer
*/
private $signer;
/**
* @var RequestTimestampProviderInterface $timestampProvider
*/
private $timestampProvider;
/**
* @param \RetailCrm\Interfaces\RequestSignerInterface $signer
*
* @return TopRequestProcessor
*/
public function setSigner(RequestSignerInterface $signer): TopRequestProcessor
{
$this->signer = $signer;
return $this;
}
/**
* @param \RetailCrm\Interfaces\RequestTimestampProviderInterface $timestampProvider
*
* @return TopRequestProcessor
*/
public function setTimestampProvider(RequestTimestampProviderInterface $timestampProvider): TopRequestProcessor
{
$this->timestampProvider = $timestampProvider;
return $this;
}
/**
* @inheritDoc
*/
public function process(
BaseRequest $request,
AppDataInterface $appData
): void {
$request->appKey = $appData->getAppKey();
$this->timestampProvider->provide($request);
$this->signer->sign($request, $appData);
$this->validate($request);
}
}

View File

@ -30,7 +30,6 @@ use RetailCrm\Interfaces\AuthenticatorInterface;
use RetailCrm\Interfaces\BuilderInterface; use RetailCrm\Interfaces\BuilderInterface;
use RetailCrm\Interfaces\TopClientInterface; use RetailCrm\Interfaces\TopClientInterface;
use RetailCrm\Interfaces\TopRequestFactoryInterface; use RetailCrm\Interfaces\TopRequestFactoryInterface;
use RetailCrm\Interfaces\TopRequestProcessorInterface;
use RetailCrm\Model\Request\BaseRequest; use RetailCrm\Model\Request\BaseRequest;
use RetailCrm\Model\Response\BaseResponse; use RetailCrm\Model\Response\BaseResponse;
use RetailCrm\Model\Response\TopResponseInterface; use RetailCrm\Model\Response\TopResponseInterface;
@ -80,11 +79,6 @@ class TopClient implements TopClientInterface
*/ */
protected $serviceLocator; protected $serviceLocator;
/**
* @var TopRequestProcessorInterface $processor
*/
protected $processor;
/** /**
* @var \RetailCrm\Interfaces\AuthenticatorInterface $authenticator * @var \RetailCrm\Interfaces\AuthenticatorInterface $authenticator
*/ */
@ -155,17 +149,6 @@ class TopClient implements TopClientInterface
$this->serviceLocator = $serviceLocator; $this->serviceLocator = $serviceLocator;
} }
/**
* @param \RetailCrm\Interfaces\TopRequestProcessorInterface $processor
*
* @return TopClient
*/
public function setProcessor(TopRequestProcessorInterface $processor): TopClient
{
$this->processor = $processor;
return $this;
}
/** /**
* @param \RetailCrm\Interfaces\AuthenticatorInterface $authenticator * @param \RetailCrm\Interfaces\AuthenticatorInterface $authenticator
* *
@ -269,8 +252,6 @@ class TopClient implements TopClientInterface
throw new TopClientException(sprintf('TopClient only supports JSON mode, got `%s` mode', $request->format)); throw new TopClientException(sprintf('TopClient only supports JSON mode, got `%s` mode', $request->format));
} }
$this->processor->process($request, $this->appData);
$httpRequest = $this->requestFactory->fromModel($request, $this->appData); $httpRequest = $this->requestFactory->fromModel($request, $this->appData);
try { try {
@ -292,10 +273,19 @@ class TopClient implements TopClientInterface
} }
if (null !== $response->errorResponse) { if (null !== $response->errorResponse) {
throw new TopApiException($response->errorResponse, $response->requestId); if ($this->debugLogging()) {
$this->logger->debug(sprintf(
'<AliExpress TOP Client> Request %s (%s): got error response %s',
$request->getMethod(),
$httpRequest->getUri()->__toString(),
$bodyData
));
}
throw new TopApiException($response->errorResponse);
} }
if (null !== $this->logger && !($this->logger instanceof NullLogger) && $this->env->isDebug()) { if ($this->debugLogging()) {
$this->logger->debug(sprintf( $this->logger->debug(sprintf(
'<AliExpress TOP Client> Request %s (%s): got response %s', '<AliExpress TOP Client> Request %s (%s): got response %s',
$request->getMethod(), $request->getMethod(),
@ -329,6 +319,14 @@ class TopClient implements TopClientInterface
return $this->sendRequest($request); return $this->sendRequest($request);
} }
/**
* @return bool
*/
protected function debugLogging(): bool
{
return null !== $this->logger && !($this->logger instanceof NullLogger) && $this->env->isDebug();
}
/** /**
* Returns body stream data (it should work like that in order to keep compatibility with some implementations). * Returns body stream data (it should work like that in order to keep compatibility with some implementations).
* *

View File

@ -0,0 +1,39 @@
<?php
/**
* PHP version 7.3
*
* @category AuthorizationUriBuilderTest
* @package RetailCrm\Tests\Builder
* @author RetailCRM <integration@retailcrm.ru>
* @license http://retailcrm.ru Proprietary
* @link http://retailcrm.ru
* @see http://help.retailcrm.ru
*/
namespace RetailCrm\Tests\Builder;
use RetailCrm\Builder\AuthorizationUriBuilder;
use RetailCrm\Test\TestCase;
/**
* Class AuthorizationUriBuilderTest
*
* @category AuthorizationUriBuilderTest
* @package RetailCrm\Tests\Builder
* @author RetailDriver LLC <integration@retailcrm.ru>
* @license https://retailcrm.ru Proprietary
* @link http://retailcrm.ru
* @see https://help.retailcrm.ru
*/
class AuthorizationUriBuilderTest extends TestCase
{
public function testBuild()
{
$appData = $this->getEnvAppData();
$builder = new AuthorizationUriBuilder($appData->getAppKey(), $appData->getRedirectUri());
$result = $builder->build();
self::assertNotFalse(strpos($result, $appData->getAppKey()));
self::assertNotFalse(strpos($result, urlencode($appData->getRedirectUri())));
}
}

View File

@ -16,6 +16,7 @@ use RetailCrm\Component\AppData;
use RetailCrm\Component\Constants; use RetailCrm\Component\Constants;
use RetailCrm\Interfaces\AppDataInterface; use RetailCrm\Interfaces\AppDataInterface;
use RetailCrm\Interfaces\RequestSignerInterface; use RetailCrm\Interfaces\RequestSignerInterface;
use RetailCrm\Interfaces\TopRequestFactoryInterface;
use RetailCrm\Model\Enum\AvailableSignMethods; use RetailCrm\Model\Enum\AvailableSignMethods;
use RetailCrm\Test\TestCase; use RetailCrm\Test\TestCase;
use RetailCrm\Test\TestSignerRequest; use RetailCrm\Test\TestSignerRequest;
@ -35,43 +36,46 @@ class RequestSignerTest extends TestCase
/** /**
* @dataProvider signDataProvider * @dataProvider signDataProvider
* *
* @param \RetailCrm\Test\TestSignerRequest $request * @param array $request
* @param \RetailCrm\Interfaces\AppDataInterface $appData * @param \RetailCrm\Interfaces\AppDataInterface $appData
* @param string $expectedHash * @param string $expectedHash
*
* @throws \RetailCrm\Component\Exception\NotImplementedException
*/ */
public function testSign(TestSignerRequest $request, AppDataInterface $appData, string $expectedHash): void public function testSign(array $request, AppDataInterface $appData, string $expectedHash): void
{ {
/** @var RequestSignerInterface $signer */ /** @var RequestSignerInterface $signer */
$signer = $this->getContainer()->get(RequestSignerInterface::class); $signer = $this->getContainer()->get(RequestSignerInterface::class);
$signer->sign($request, $appData);
self::assertEquals($expectedHash, $request->sign); self::assertEquals($expectedHash, $signer->generateSign($request, $appData, $request['sign_method']));
} }
public function signDataProvider(): array public function signDataProvider(): array
{ {
/** @var TopRequestFactoryInterface $factory */
$factory = $this->getContainer()->get(TopRequestFactoryInterface::class);
$appData = $this->getAppData(); $appData = $this->getAppData();
return [ return [
[ [
$this->getTestRequest(AvailableSignMethods::MD5), $factory->getRequestArray($this->getTestRequest(AvailableSignMethods::MD5)),
$appData, $appData,
'468BF7C95925C187D0DFD7D042072EB4' '4BC79C5FAA1B5E254E95A97E65BACEAB'
], ],
[ [
$this->getTestRequest(AvailableSignMethods::HMAC_MD5), $factory->getRequestArray($this->getTestRequest(AvailableSignMethods::HMAC_MD5)),
$appData, $appData,
'5EF5C76D5C158BFFA9F35BAAA712A879' '497FA7FCAD98F4F335EFAE2451F8291D'
], ],
[ [
$this->getTestRequest(AvailableSignMethods::MD5, true), $factory->getRequestArray($this->getTestRequest(AvailableSignMethods::MD5, true)),
$appData, $appData,
'468BF7C95925C187D0DFD7D042072EB4' '4BC79C5FAA1B5E254E95A97E65BACEAB'
], ],
[ [
$this->getTestRequest(AvailableSignMethods::HMAC_MD5, true), $factory->getRequestArray($this->getTestRequest(AvailableSignMethods::HMAC_MD5, true)),
$appData, $appData,
'5EF5C76D5C158BFFA9F35BAAA712A879' '497FA7FCAD98F4F335EFAE2451F8291D'
] ]
]; ];
} }

View File

@ -12,11 +12,10 @@
*/ */
namespace RetailCrm\Tests\TopClient; namespace RetailCrm\Tests\TopClient;
use DateTime;
use Http\Message\RequestMatcher\CallbackRequestMatcher; use Http\Message\RequestMatcher\CallbackRequestMatcher;
use Psr\Http\Message\RequestInterface; use Psr\Http\Message\RequestInterface;
use RetailCrm\Builder\TopClientBuilder; use RetailCrm\Builder\TopClientBuilder;
use RetailCrm\Component\ConstraintViolationListTransformer;
use RetailCrm\Component\Exception\ValidationException;
use RetailCrm\Model\Entity\CategoryInfo; use RetailCrm\Model\Entity\CategoryInfo;
use RetailCrm\Model\Enum\FeedOperationTypes; use RetailCrm\Model\Enum\FeedOperationTypes;
use RetailCrm\Model\Enum\FeedStatuses; use RetailCrm\Model\Enum\FeedStatuses;
@ -41,7 +40,6 @@ use RetailCrm\Model\Response\AliExpress\Data\SolutionSellerCategoryTreeQueryResp
use RetailCrm\Model\Response\AliExpress\Data\SolutionSellerCategoryTreeQueryResponseDataChildrenCategoryList; use RetailCrm\Model\Response\AliExpress\Data\SolutionSellerCategoryTreeQueryResponseDataChildrenCategoryList;
use RetailCrm\Model\Response\AliExpress\PostproductRedefiningCategoryForecastResponse; use RetailCrm\Model\Response\AliExpress\PostproductRedefiningCategoryForecastResponse;
use RetailCrm\Model\Response\AliExpress\SolutionFeedListGetResponse; use RetailCrm\Model\Response\AliExpress\SolutionFeedListGetResponse;
use RetailCrm\Model\Response\AliExpress\SolutionProductSchemaGetResponse;
use RetailCrm\Model\Response\AliExpress\SolutionSellerCategoryTreeQueryResponse; use RetailCrm\Model\Response\AliExpress\SolutionSellerCategoryTreeQueryResponse;
use RetailCrm\Model\Response\ErrorResponseBody; use RetailCrm\Model\Response\ErrorResponseBody;
use RetailCrm\Model\Response\Taobao\HttpDnsGetResponse; use RetailCrm\Model\Response\Taobao\HttpDnsGetResponse;
@ -602,6 +600,9 @@ EOF;
->setAuthenticator($this->getEnvTokenAuthenticator()) ->setAuthenticator($this->getEnvTokenAuthenticator())
->build(); ->build();
$query = new OrderQuery(); $query = new OrderQuery();
$query->pageSize = 20;
$query->currentPage = 1;
$query->createDateStart = new DateTime();
$query->orderStatus = OrderStatuses::PLACE_ORDER_SUCCESS; $query->orderStatus = OrderStatuses::PLACE_ORDER_SUCCESS;
$request = new SolutionOrderGet(); $request = new SolutionOrderGet();
$request->param0 = $query; $request->param0 = $query;