Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PAYSHIP-3135] Refacto CapturePayPalOrderCommandHandler #1299

Open
wants to merge 5 commits into
base: prestashop/8.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
"ramsey/uuid": "^3.8",
"segmentio/analytics-php": "^1.5",
"sentry/sentry": "^1.0",
"webmozart/assert": "^1.0"
"webmozart/assert": "^1.0",
"ext-json": "*"
},
"require-dev": {
"phpunit/phpunit": "~5.7",
Expand Down
143 changes: 103 additions & 40 deletions src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@

namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\CommandHandler;

use Configuration;
use PrestaShop\Module\PrestashopCheckout\Context\PrestaShopContext;
use PrestaShop\Module\PrestashopCheckout\Customer\Exception\CustomerException;
use PrestaShop\Module\PrestashopCheckout\Customer\ValueObject\CustomerId;
use PrestaShop\Module\PrestashopCheckout\Event\EventDispatcherInterface;
use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException;
Expand All @@ -37,9 +37,11 @@
use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCapturePendingEvent;
use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\PayPalCaptureStatus;
use PrestaShop\Module\PrestashopCheckout\PayPal\PaymentToken\Event\PaymentTokenCreatedEvent;
use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalConfiguration;
use PrestaShop\Module\PrestashopCheckout\PayPalProcessorResponse;
use PrestaShop\Module\PrestashopCheckout\Repository\PayPalCustomerRepository;
use PrestaShop\Module\PrestashopCheckout\Repository\PayPalOrderRepository;
use Psr\Log\LoggerInterface;
use Psr\SimpleCache\CacheInterface;

class CapturePayPalOrderCommandHandler
Expand All @@ -66,35 +68,82 @@ class CapturePayPalOrderCommandHandler
* @var PayPalCustomerRepository
*/
private $payPalCustomerRepository;

/** @var PayPalConfiguration */
private $payPalConfiguration;

/** @var PayPalOrderRepository */
private $payPalOrderRepository;
/**
* @var PayPalOrderRepository
* @var LoggerInterface
*/
private $payPalOrderRepository;
private $logger;

public function __construct(
MaaslandHttpClient $maaslandHttpClient,
MaaslandHttpClient $maaslandHttpClient,
EventDispatcherInterface $eventDispatcher,
CacheInterface $orderPayPalCache,
PrestaShopContext $prestaShopContext,
CacheInterface $orderPayPalCache,
PrestaShopContext $prestaShopContext,
PayPalCustomerRepository $payPalCustomerRepository,
PayPalOrderRepository $payPalOrderRepository
LoggerInterface $logger,
PayPalConfiguration $payPalConfiguration,
PayPalOrderRepository $payPalOrderRepository
) {
$this->maaslandHttpClient = $maaslandHttpClient;
$this->eventDispatcher = $eventDispatcher;
$this->orderPayPalCache = $orderPayPalCache;
$this->prestaShopContext = $prestaShopContext;
$this->payPalCustomerRepository = $payPalCustomerRepository;
$this->payPalConfiguration = $payPalConfiguration;
$this->payPalOrderRepository = $payPalOrderRepository;
$this->logger = $logger;
}

public function handle(CapturePayPalOrderCommand $capturePayPalOrderCommand)
{
$merchantId = Configuration::get('PS_CHECKOUT_PAYPAL_ID_MERCHANT', null, null, $this->prestaShopContext->getShopId());
$merchantId = $this->payPalConfiguration->getMerchantId();

$capturePayload = $this->buildCapturePayload($capturePayPalOrderCommand);

$orderPayPal = $this->captureOrder($capturePayload);

if (empty($orderPayPal)) {
throw new \Exception('Order PayPal not found');
}

if (isset($orderPayPal['payment_source'][$capturePayPalOrderCommand->getFundingSource()]['attributes']['vault'])) {
$vault = $orderPayPal['payment_source'][$capturePayPalOrderCommand->getFundingSource()]['attributes']['vault'];
$vault = $this->savePrestaShopPayPalCustomerRelationship($vault);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me this part of overwriting $vault variable feels a bit off. Why not wrap it in try-catch?


if (isset($vault['id'])) {
$paymentToken = $this->createPaymentTokenEvent($capturePayPalOrderCommand, $orderPayPal, $vault, $merchantId);
}
}

return $this->processTheCapture($orderPayPal);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return $this->processTheCapture($orderPayPal);
return $this->processCapture($orderPayPal);

}

private function captureOrder($captureOrderPayload)
{
try {
$response = $this->maaslandHttpClient->captureOrder($captureOrderPayload);

$orderPayPal = json_decode($response->getBody(), true);
$payPalOrderFromCache = $this->orderPayPalCache->get($orderPayPal['id']);

return array_replace_recursive($payPalOrderFromCache, $orderPayPal);
} catch (\Exception $exception) {
$this->logger->error($exception->getMessage());
return [];
}
}

private function buildCapturePayload(CapturePayPalOrderCommand $capturePayPalOrderCommand)
{
$payload = [
'mode' => $capturePayPalOrderCommand->getFundingSource(),
'orderId' => $capturePayPalOrderCommand->getOrderId()->getValue(),
'payee' => ['merchant_id' => $merchantId],
'payee' => ['merchant_id' => $this->payPalConfiguration->getMerchantId()],
];

$order = $this->payPalOrderRepository->getPayPalOrderById($capturePayPalOrderCommand->getOrderId());
Expand All @@ -103,45 +152,55 @@ public function handle(CapturePayPalOrderCommand $capturePayPalOrderCommand)
$payload['vault'] = true;
}

$response = $this->maaslandHttpClient->captureOrder($payload);
return $payload;
}

$orderPayPal = json_decode($response->getBody(), true);
private function savePrestaShopPayPalCustomerRelationship($vault)
{
try {
$payPalCustomerId = new PayPalCustomerId($vault['customer']['id']);
$customerId = new CustomerId($this->prestaShopContext->getCustomerId());
$this->payPalCustomerRepository->save($customerId, $payPalCustomerId);

return $vault;
} catch (CustomerException $exception) {
$this->logger->error($exception->getMessage());
return [];
} catch (\InvalidArgumentException $exception) {
return [];
} catch (\Exception $exception) {
$this->logger->error(sprintf('An error occurs during process : %s', $exception->getMessage()));
return [];
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you decide to keep catch clauses here, then you don't need a return at each one. Just one at the end of the function

}

$payPalOrderFromCache = $this->orderPayPalCache->get($orderPayPal['id']);
private function createPaymentTokenEvent($capturePayPalOrderCommand, $orderPayPal, $vault, $merchantId)
{
$paymentToken = $vault;
$paymentToken['metadata'] = [
'order_id' => $orderPayPal['id'],
];

$orderPayPal = array_replace_recursive($payPalOrderFromCache, $orderPayPal);
$paymentSource = $orderPayPal['payment_source'];
unset($paymentSource[$capturePayPalOrderCommand->getFundingSource()]['attributes']['vault']);
$paymentSource[$capturePayPalOrderCommand->getFundingSource()]['verification_status'] = $paymentToken['status'];

$capturePayPal = $orderPayPal['purchase_units'][0]['payments']['captures'][0];
$paymentToken['payment_source'] = $paymentSource;

if (isset($orderPayPal['payment_source'][$capturePayPalOrderCommand->getFundingSource()]['attributes']['vault'])) {
$vault = $orderPayPal['payment_source'][$capturePayPalOrderCommand->getFundingSource()]['attributes']['vault'];
if (isset($vault['customer']['id'])) {
try {
$payPalCustomerId = new PayPalCustomerId($vault['customer']['id']);
$customerId = new CustomerId($this->prestaShopContext->getCustomerId());
$this->payPalCustomerRepository->save($customerId, $payPalCustomerId);
} catch (\Exception $exception) {
}
}
$this->eventDispatcher->dispatch(new PaymentTokenCreatedEvent(
$paymentToken,
$merchantId
));

if (isset($vault['id'])) {
$resource = $vault;
$resource['metadata'] = [
'order_id' => $orderPayPal['id'],
];
$paymentSource = $orderPayPal['payment_source'];
unset($paymentSource[$capturePayPalOrderCommand->getFundingSource()]['attributes']['vault']);
$resource['payment_source'] = $paymentSource;
$resource['payment_source'][$capturePayPalOrderCommand->getFundingSource()]['verification_status'] = $resource['status'];

$this->eventDispatcher->dispatch(new PaymentTokenCreatedEvent(
$resource,
$merchantId
));
}
}
return $paymentToken;
}

private function processTheCapture($orderPayPal)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private function processTheCapture($orderPayPal)
private function processCapture($orderPayPal)

{
$capturePayPal = $orderPayPal['purchase_units'][0]['payments']['captures'][0];

if ($orderPayPal['status'] === PayPalOrderStatus::COMPLETED) {
$this->logger->info('');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty log

$this->eventDispatcher->dispatch(new PayPalOrderCompletedEvent($orderPayPal['id'], $orderPayPal));
}

Expand All @@ -150,6 +209,7 @@ public function handle(CapturePayPalOrderCommand $capturePayPalOrderCommand)
}

if ($capturePayPal['status'] === PayPalCaptureStatus::COMPLETED) {
$this->logger->info('Capture payment completed');
$this->eventDispatcher->dispatch(new PayPalCaptureCompletedEvent($capturePayPal['id'], $orderPayPal['id'], $capturePayPal));
}

Expand All @@ -170,9 +230,12 @@ public function handle(CapturePayPalOrderCommand $capturePayPalOrderCommand)
isset($capturePayPal['processor_response']['cvv_code']) ? $capturePayPal['processor_response']['cvv_code'] : null,
isset($capturePayPal['processor_response']['response_code']) ? $capturePayPal['processor_response']['response_code'] : null
);

$payPalProcessorResponse->throwException();
} elseif (PayPalCaptureStatus::DECLINED === $capturePayPal['status'] || PayPalCaptureStatus::FAILED === $capturePayPal['status']) {
throw new PsCheckoutException('PayPal declined the capture', PsCheckoutException::PAYPAL_PAYMENT_CAPTURE_DECLINED);
}

return $capturePayPal;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php

declare(strict_types=1);

namespace Tests\Unit\PayPal\Order\CommandHandler;

use PHPUnit\Framework\TestCase;
use PrestaShop\Module\PrestashopCheckout\Context\PrestaShopContext;
use PrestaShop\Module\PrestashopCheckout\Http\MaaslandHttpClient;
use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Cache\PayPalOrderCache;
use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\CapturePayPalOrderCommand;
use PrestaShop\Module\PrestashopCheckout\PayPal\Order\CommandHandler\CapturePayPalOrderCommandHandler;
use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalConfiguration;
use PrestaShop\Module\PrestashopCheckout\Repository\PayPalCustomerRepository;
use PrestaShop\Module\PrestashopCheckout\Repository\PayPalOrderRepository;
use Psr\Cache\CacheItemInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;

class CapturePayPalOrderCommandHandlerTest extends TestCase
{
const PAYPAL_PAYMENT_SUCCESS = 'success';
const PAYPAL_PAYMENT_FAILURE = 'failure';
const CARD_PAYMENT_SUCCESS = 'success';
const CARD_PAYMENT_FAILURE = 'failure';

private $capturePayPalOrderCommandHandler;
private $responseMock;

protected function setUp()
{
$maaslandHttpClientMock = $this->createMock(MaaslandHttpClient::class);
$eventDispatcherMock = $this->createMock(EventDispatcher::class);
$prestashopContextMock = $this->createMock(PrestashopContext::class);
$paypalCustomerRepositoryMock = $this->createMock(PayPalCustomerRepository::class);
$paypalOrderRepositoryMock = $this->createMock(PayPalOrderRepository::class);
$paypalConfigurationMock = $this->createMock(PayPalConfiguration::class);
$loggerMock = $this->createMock(LoggerInterface::class);
$cacheMock = $this->createMock(PayPalOrderCache::class);

$this->responseMock = $this->createMock(ResponseInterface::class);

$this->capturePayPalOrderCommandHandler = new CapturePayPalOrderCommandHandler(
$maaslandHttpClientMock,
$eventDispatcherMock,
$cacheMock,
$prestashopContextMock,
$paypalCustomerRepositoryMock,
$paypalConfigurationMock,
$paypalOrderRepositoryMock,
$loggerMock
);

$maaslandHttpClientMock->method('captureOrder')->willReturn($this->responseMock);
$prestashopContextMock->method('getCustomerId')->willReturn(null);
$paypalConfigurationMock->method('getMerchantId')->willReturn(null);
$paypalOrderRepositoryMock->method('getPayPalOrderById')->willReturn(null);
}

/** @test */
public function payment_with_paypal_succeeds()
{
$this->responseMock->method('getBody')->willReturn('');
$capturePayPalOrderCommand = new CapturePayPalOrderCommand();
$expectedPaypalCapture = [];

$capturePayPal = $this->capturePayPalOrderCommandHandler->handle($capturePayPalOrderCommand);

$this->assertEquals($expectedPaypalCapture, $capturePayPal);
}

/** @test */
public function payment_with_paypal_fails()
{
$this->responseMock->method('getBody')->willReturn('');
}

/** @test */
public function payment_by_card_succeeds()
{
$this->responseMock->method('getBody')->willReturn('');
}

/** @test */
public function payment_by_card_fails()
{
$this->responseMock->method('getBody')->willReturn('');
}
}
Empty file.
Loading