refactor client workflow which resulted in correct behaviour for signatures
This commit is contained in:
parent
405621bfdf
commit
402c20d184
@ -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();
|
||||||
|
@ -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) {
|
||||||
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null !== $this->logger && !($this->logger instanceof NullLogger) && $this->env->isDebug()) {
|
throw new TopApiException($response->errorResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
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).
|
||||||
*
|
*
|
||||||
|
@ -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())));
|
||||||
|
}
|
||||||
|
}
|
@ -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'
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user