diff --git a/src/Api/Payment/Client/OldPaymentClient.php b/src/Api/Payment/Client/OldPaymentClient.php index 031705987..8a4719df2 100755 --- a/src/Api/Payment/Client/OldPaymentClient.php +++ b/src/Api/Payment/Client/OldPaymentClient.php @@ -20,27 +20,188 @@ namespace PrestaShop\Module\PrestashopCheckout\Api\Payment\Client; -use PrestaShop\Module\PrestashopCheckout\Http\PsrClientAdapter; -use Psr\Http\Message\RequestInterface; +use Context; +use GuzzleHttp\Event\Emitter; +use GuzzleHttp\HandlerStack; +use GuzzleHttp\Subscriber\Log\Formatter; +use GuzzleHttp\Subscriber\Log\LogSubscriber; +use GuzzleLogMiddleware\LogMiddleware; +use Link; +use Module; +use PrestaShop\Module\PrestashopCheckout\Api\GenericClient; +use PrestaShop\Module\PrestashopCheckout\Environment\PaymentEnv; +use PrestaShop\Module\PrestashopCheckout\Exception\HttpTimeoutException; +use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; +use PrestaShop\Module\PrestashopCheckout\Logger\LoggerConfiguration; +use PrestaShop\Module\PrestashopCheckout\Routing\Router; +use PrestaShop\Module\PrestashopCheckout\ShopContext; +use PrestaShop\Module\PrestashopCheckout\Version\Version; +use Prestashop\ModuleLibGuzzleAdapter\ClientFactory; +use Ps_checkout; +use Psr\Log\LoggerInterface; /** * Construct the client used to make call to maasland */ class OldPaymentClient extends GenericClient { - /** @var PsrClientAdapter */ - private $client; + /** + * @param Link $link + * @param object|null $client + */ + public function __construct(Link $link, $client = null) + { + parent::__construct(); + + $this->setLink($link); + + // Client can be provided for tests + if (null === $client) { + /** @var Ps_checkout $module */ + $module = Module::getInstanceByName('ps_checkout'); + + /** @var Version $version */ + $version = $module->getService('ps_checkout.module.version'); + + /** @var LoggerConfiguration $loggerConfiguration */ + $loggerConfiguration = $module->getService('ps_checkout.logger.configuration'); + + /** @var LoggerInterface $logger */ + $logger = $module->getService('ps_checkout.logger'); - public function __construct(PaymentClientConfigurationBuilder $configurationBuilder) + /** @var Router $router */ + $router = $module->getService('ps_checkout.prestashop.router'); + + $clientConfiguration = [ + 'base_url' => (new PaymentEnv())->getPaymentApiUrl(), + 'verify' => $this->getVerify(), + 'timeout' => $this->timeout, + 'exceptions' => $this->catchExceptions, + 'headers' => [ + 'Content-Type' => 'application/vnd.checkout.v1+json', // api version to use (psl side) + 'Accept' => 'application/json', + 'Authorization' => 'Bearer ' . $this->token, // Token we get from PsAccounts + 'Shop-Id' => $this->shopUid, // Shop UUID we get from PsAccounts + 'Hook-Url' => $router->getDispatchWebhookLink((int) Context::getContext()->shop->id), + 'Bn-Code' => (new ShopContext())->getBnCode(), + 'Module-Version' => $version->getSemVersion(), // version of the module + 'Prestashop-Version' => _PS_VERSION_, // prestashop version + ], + ]; + + if ( + $loggerConfiguration->isHttpEnabled() + && defined('\GuzzleHttp\ClientInterface::MAJOR_VERSION') + && class_exists(HandlerStack::class) + && class_exists(LogMiddleware::class) + ) { + $handlerStack = HandlerStack::create(); + $handlerStack->push(new LogMiddleware($logger)); + $clientConfiguration['handler'] = $handlerStack; + } elseif ( + $loggerConfiguration->isHttpEnabled() + && defined('\GuzzleHttp\ClientInterface::VERSION') + && class_exists(Emitter::class) + && class_exists(LogSubscriber::class) + && class_exists(Formatter::class) + ) { + $emitter = new Emitter(); + $emitter->attach(new LogSubscriber( + $logger, + Formatter::DEBUG + )); + + $clientConfiguration['emitter'] = $emitter; + } + + $client = (new ClientFactory())->getClient($clientConfiguration); + } + + $this->setClient($client); + } + + /** + * @param array $options + * + * @return array + * + * @throws HttpTimeoutException + */ + protected function post(array $options = []) { - $this->client = new PsrClientAdapter($configurationBuilder->build()); + $delay = isset($options['delay']) ? (int) $options['delay'] : 2; + $retries = isset($options['retries']) ? (int) $options['retries'] : 2; + unset($options['delay'], $options['retries']); + + return $this->postWithRetry($options, $delay, $retries); } /** - * {@inheritdoc} + * @param array $options + * @param int $delay + * @param int $retries + * + * @return array + * + * @throws HttpTimeoutException + * @throws PsCheckoutException */ - public function sendRequest(RequestInterface $request) + private function postWithRetry(array $options, $delay = 2, $retries = 2) { - return $this->client->sendRequest($request); + try { + $response = parent::post($options); + + if ($response['httpCode'] === 401 || false !== strpos($response['exceptionMessage'], 'Unauthorized')) { + throw new PsCheckoutException('Unauthorized', PsCheckoutException::PSCHECKOUT_HTTP_UNAUTHORIZED); + } + + if (false !== $response['status']) { + return $response; + } + + if ( + isset($response['exceptionCode']) + && $response['exceptionCode'] === PsCheckoutException::PSCHECKOUT_HTTP_EXCEPTION + && false !== strpos($response['exceptionMessage'], 'cURL error 28') + ) { + throw new HttpTimeoutException($response['exceptionMessage'], PsCheckoutException::PSL_TIMEOUT); + } elseif ( + isset($response['exceptionCode']) + && $response['exceptionCode'] === PsCheckoutException::PSCHECKOUT_HTTP_EXCEPTION + ) { + throw new PsCheckoutException($response['exceptionMessage'], PsCheckoutException::PSCHECKOUT_HTTP_EXCEPTION); + } + + if ( + isset($response['body']['message']) + && ($response['body']['message'] === 'Error: ETIMEDOUT' || $response['body']['message'] === 'Error: ESOCKETTIMEDOUT') + ) { + throw new HttpTimeoutException($response['body']['message'], PsCheckoutException::PSL_TIMEOUT); + } + } catch (HttpTimeoutException $exception) { + if ($this->isRouteRetryable() && $retries > 0) { + sleep($delay); + + return $this->postWithRetry($options, $delay, $retries - 1); + } + + throw $exception; + } + + return $response; + } + + /** + * @return bool + */ + private function isRouteRetryable() + { + switch ($this->getRoute()) { + case '/payments/order/capture': + case '/payments/order/refund': + return false; + } + + return true; } }