-
Notifications
You must be signed in to change notification settings - Fork 42
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
base: prestashop/8.x
Are you sure you want to change the base?
Changes from all commits
30ffdc8
43e4c7e
c5b5c56
94fdfbb
e069c71
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -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; | ||||||
|
@@ -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 | ||||||
|
@@ -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); | ||||||
|
||||||
if (isset($vault['id'])) { | ||||||
$paymentToken = $this->createPaymentTokenEvent($capturePayPalOrderCommand, $orderPayPal, $vault, $merchantId); | ||||||
} | ||||||
} | ||||||
|
||||||
return $this->processTheCapture($orderPayPal); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
} | ||||||
|
||||||
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()); | ||||||
|
@@ -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 []; | ||||||
} | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
{ | ||||||
$capturePayPal = $orderPayPal['purchase_units'][0]['payments']['captures'][0]; | ||||||
|
||||||
if ($orderPayPal['status'] === PayPalOrderStatus::COMPLETED) { | ||||||
$this->logger->info(''); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Empty log |
||||||
$this->eventDispatcher->dispatch(new PayPalOrderCompletedEvent($orderPayPal['id'], $orderPayPal)); | ||||||
} | ||||||
|
||||||
|
@@ -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)); | ||||||
} | ||||||
|
||||||
|
@@ -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(''); | ||
} | ||
} |
There was a problem hiding this comment.
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?