Skip to content

Commit

Permalink
Implement asynchronous POS cloud for in-person payments (#2565)
Browse files Browse the repository at this point in the history
* [ECP-8912] Make Pos payments flow async (#2498)

* ECP-8912/Make-POS-payments-flow-async

* refactor

* Make POS async

* pos async

* pos async

* fix TypeError: Magento\Payment\Gateway\Data\PaymentDataObject::__construct(): Argument #1 ($order) must be of type Magento\Payment\Gateway\Data\OrderAdapterInterface, Magento\Sales\Model\Order\Interceptor given, called in /var/www/html/vendor/adyen/module-payment/Model/Api/AdyenPosCloud.php on line 54 and defined in /var/www/html/app/code/Magento/Payment/Gateway/Data/PaymentDataObject.php:26

* tuning backend of async

* fix FE call

* fixes

* clean up

* Refactor SubmitQuoteObserver

* Refactor adyen-pos-cloud-method.js

* put the correct status

* Add test coverage for AdyenPosCloud & GuestAdyenPosCloud

* Add test coverage for PaymentPosCloudHandlerTest

* Add test coverage for SetOrderStateAfterPaymentObserverTest

* Comments refactor

* rename handleFaildResponse to handleFailedResponse

* Terminal API does not return Authorised result code.

* Update Plugin/GuestPaymentInformationResetOrderId.php

* Update Plugin/PaymentInformationResetOrderId.php

* Update events.xml

---------

Co-authored-by: Peter Ojo <[email protected]>
Co-authored-by: Can Demiralp <[email protected]>

* [ECP-9064] Make asynchronous POS flow configurable (#2566)

* [ECP-9064] Define new configuration field and getter for payment action

* [ECP-9064] Pass payment_action config to frontend

* [ECP-9064] Use POS payment action value on frontend and observers logic

* [ECP-9064] Use constant definition for payment action field

* [ECP-9064] Fix unit tests

* [ECP-9064] Fix unit tests

* [ECP-9064] Formatting

* [ECP-9064] Update unit tests

---------

Co-authored-by: Can Demiralp <[email protected]>

* Update the tooltip and the label of the configuration field

* Update unit tests

---------

Co-authored-by: hossam-adyen <[email protected]>
Co-authored-by: Peter Ojo <[email protected]>
Co-authored-by: Can Demiralp <[email protected]>
Co-authored-by: Rok Popov Ledinski <[email protected]>
  • Loading branch information
5 people authored Apr 18, 2024
1 parent 2274f78 commit cbf8bb5
Show file tree
Hide file tree
Showing 27 changed files with 832 additions and 62 deletions.
20 changes: 20 additions & 0 deletions Api/AdyenPosCloudInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php
/**
*
* Adyen Payment module (https://www.adyen.com/)
*
* Copyright (c) 2024 Adyen N.V. (https://www.adyen.com/)
* See LICENSE.txt for license details.
*
* Author: Adyen <[email protected]>
*/

namespace Adyen\Payment\Api;
interface AdyenPosCloudInterface
{
/**
* @param int $orderId
* @return void
*/
public function pay(int $orderId): void;
}
21 changes: 21 additions & 0 deletions Api/GuestAdyenPosCloudInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php
/**
*
* Adyen Payment module (https://www.adyen.com/)
*
* Copyright (c) 2024 Adyen N.V. (https://www.adyen.com/)
* See LICENSE.txt for license details.
*
* Author: Adyen <[email protected]>
*/

namespace Adyen\Payment\Api;

interface GuestAdyenPosCloudInterface
{
/**
* @param string $cartId
* @return void
*/
public function payByCart(string $cartId): void;
}
31 changes: 28 additions & 3 deletions Gateway/Response/PaymentPosCloudHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,32 @@
namespace Adyen\Payment\Gateway\Response;

use Adyen\AdyenException;
use Adyen\Payment\Helper\PaymentResponseHandler;
use Adyen\Payment\Helper\Quote;
use Adyen\Payment\Helper\Vault;
use Adyen\Payment\Logger\AdyenLogger;
use Adyen\Payment\Model\Order\Payment;
use Magento\Sales\Model\Order;
use Magento\Sales\Model\Order\StatusResolver;
use Magento\Payment\Gateway\Helper\SubjectReader;
use Magento\Payment\Gateway\Response\HandlerInterface;

class PaymentPosCloudHandler implements HandlerInterface
{
private AdyenLogger $adyenLogger;
private Vault $vaultHelper;
private StatusResolver $statusResolver;
private Quote $quoteHelper;

public function __construct(
AdyenLogger $adyenLogger,
Vault $vaultHelper
Vault $vaultHelper,
StatusResolver $statusResolver,
Quote $quoteHelper
) {
$this->adyenLogger = $adyenLogger;
$this->vaultHelper = $vaultHelper;
$this->statusResolver = $statusResolver;
$this->quoteHelper = $quoteHelper;
}

public function handle(array $handlingSubject, array $response)
Expand All @@ -44,9 +53,11 @@ public function handle(array $handlingSubject, array $response)

// do not send order confirmation mail
$payment->getOrder()->setCanSendNewEmailFlag(false);
$resultCode = null;

if (!empty($paymentResponse) && isset($paymentResponse['Response']['Result'])) {
$payment->setAdditionalInformation('resultCode', $paymentResponse['Response']['Result']);
$resultCode = $paymentResponse['Response']['Result'];
$payment->setAdditionalInformation('resultCode', $resultCode);
}

if (!empty($paymentResponse['Response']['AdditionalResponse']))
Expand Down Expand Up @@ -81,5 +92,19 @@ public function handle(array $handlingSubject, array $response)
// do not close transaction so you can do a cancel() and void
$payment->setIsTransactionClosed(false);
$payment->setShouldCloseParentTransaction(false);

if ($resultCode === PaymentResponseHandler::POS_SUCCESS) {
$order = $payment->getOrder();
$status = $this->statusResolver->getOrderStatusByState(
$payment->getOrder(),
Order::STATE_NEW
);
$order->setState(Order::STATE_NEW);
$order->setStatus($status);
$message = __("Pos payment authorized");
$order->addCommentToStatusHistory($message, $status);
$order->save();
$this->quoteHelper->disableQuote($order->getQuoteId());
}
}
}
6 changes: 6 additions & 0 deletions Helper/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class Config
const XML_MOTO_MERCHANT_ACCOUNTS = 'moto_merchant_accounts';
const XML_CONFIGURATION_MODE = 'configuration_mode';
const XML_ADYEN_POS_CLOUD = 'adyen_pos_cloud';
const XML_PAYMENT_ACTION = 'payment_action';
const XML_WEBHOOK_NOTIFICATION_PROCESSOR = 'webhook_notification_processor';
const AUTO_CAPTURE_OPENINVOICE = 'auto';
const XML_RECURRING_CONFIGURATION = 'recurring_configuration';
Expand Down Expand Up @@ -465,6 +466,11 @@ public function getAdyenPosCloudConfigData(string $field, int $storeId = null, b
return $this->getConfigData($field, self::XML_ADYEN_POS_CLOUD, $storeId, $flag);
}

public function getAdyenPosCloudPaymentAction(int $storeId): string
{
return $this->getAdyenPosCloudConfigData(self::XML_PAYMENT_ACTION, $storeId);
}

public function useQueueProcessor($storeId = null): bool
{
return $this->getConfigData(
Expand Down
54 changes: 54 additions & 0 deletions Model/Api/AdyenPosCloud.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php
/**
*
* Adyen Payment module (https://www.adyen.com/)
*
* Copyright (c) 2024 Adyen N.V. (https://www.adyen.com/)
* See LICENSE.txt for license details.
*
* Author: Adyen <[email protected]>
*/

namespace Adyen\Payment\Model\Api;

use Adyen\Payment\Api\AdyenPosCloudInterface;
use Adyen\Payment\Logger\AdyenLogger;
use Adyen\Payment\Model\Sales\OrderRepository;
use Magento\Payment\Gateway\Command\CommandPoolInterface;
use Magento\Sales\Api\Data\OrderInterface;
use Magento\Payment\Gateway\Data\PaymentDataObjectFactoryInterface;

class AdyenPosCloud implements AdyenPosCloudInterface
{
private CommandPoolInterface $commandPool;
protected AdyenLogger $adyenLogger;
protected OrderRepository $orderRepository;
protected PaymentDataObjectFactoryInterface $paymentDataObjectFactory;

public function __construct(
CommandPoolInterface $commandPool,
OrderRepository $orderRepository,
PaymentDataObjectFactoryInterface $paymentDataObjectFactory,
AdyenLogger $adyenLogger
)
{
$this->commandPool = $commandPool;
$this->orderRepository = $orderRepository;
$this->paymentDataObjectFactory = $paymentDataObjectFactory;
$this->adyenLogger = $adyenLogger;
}

public function pay(int $orderId): void
{
$order = $this->orderRepository->get($orderId);
$this->execute($order);
}

protected function execute(OrderInterface $order): void
{
$payment = $order->getPayment();
$paymentDataObject = $this->paymentDataObjectFactory->create($payment);
$posCommand = $this->commandPool->get('authorize');
$posCommand->execute(['payment' => $paymentDataObject]);
}
}
57 changes: 57 additions & 0 deletions Model/Api/GuestAdyenPosCloud.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php
/**
*
* Adyen Payment module (https://www.adyen.com/)
*
* Copyright (c) 2024 Adyen N.V. (https://www.adyen.com/)
* See LICENSE.txt for license details.
*
* Author: Adyen <[email protected]>
*/

namespace Adyen\Payment\Model\Api;

use Adyen\Payment\Api\GuestAdyenPosCloudInterface;
use Adyen\Payment\Logger\AdyenLogger;
use Adyen\Payment\Model\Sales\OrderRepository;
use Magento\Payment\Gateway\Command\CommandPoolInterface;
use Magento\Payment\Gateway\Data\PaymentDataObjectFactoryInterface;
use Magento\Quote\Model\QuoteIdMaskFactory;


class GuestAdyenPosCloud extends AdyenPosCloud implements GuestAdyenPosCloudInterface
{
protected AdyenLogger $adyenLogger;
protected OrderRepository $orderRepository;
protected PaymentDataObjectFactoryInterface $paymentDataObjectFactory;
private QuoteIdMaskFactory $quoteIdMaskFactory;

public function __construct(
CommandPoolInterface $commandPool,
OrderRepository $orderRepository,
PaymentDataObjectFactoryInterface $paymentDataObjectFactory,
AdyenLogger $adyenLogger,
QuoteIdMaskFactory $quoteIdMaskFactory
)
{
parent::__construct(
$commandPool,
$orderRepository,
$paymentDataObjectFactory,
$adyenLogger
);
$this->quoteIdMaskFactory = $quoteIdMaskFactory;
}

/**
* @param string $cartId
* @return void
*/
public function payByCart(string $cartId): void
{
$quoteIdMask = $this->quoteIdMaskFactory->create()->load($cartId, 'masked_id');
$quoteId = $quoteIdMask->getQuoteId();
$order = $this->orderRepository->getOrderByQuoteId($quoteId);
$this->execute($order);
}
}
29 changes: 29 additions & 0 deletions Model/Config/Source/PaymentAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php
/**
*
* Adyen Payment module (https://www.adyen.com/)
*
* Copyright (c) 2024 Adyen N.V. (https://www.adyen.com/)
* See LICENSE.txt for license details.
*
* Author: Adyen <[email protected]>
*/

namespace Adyen\Payment\Model\Config\Source;

use Magento\Framework\Data\OptionSourceInterface;
use Magento\Payment\Model\MethodInterface;

class PaymentAction implements OptionSourceInterface
{
/**
* @return array
*/
public function toOptionArray()
{
return [
['value' => MethodInterface::ACTION_AUTHORIZE, 'label' => __('After payment')],
['value' => MethodInterface::ACTION_ORDER, 'label' => __('Before payment')],
];
}
}
60 changes: 45 additions & 15 deletions Model/Ui/AdyenPosCloudConfigProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,50 +16,76 @@
use Adyen\Payment\Helper\ConnectedTerminals;
use Magento\Checkout\Model\ConfigProviderInterface;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Serialize\SerializerInterface;
use Magento\Framework\UrlInterface;
use Magento\Store\Model\StoreManager;

class AdyenPosCloudConfigProvider implements ConfigProviderInterface
{
const CODE = 'adyen_pos_cloud';

/** @var RequestInterface */
protected $request;
/**
* @var RequestInterface
*/
protected RequestInterface $request;

/** @var UrlInterface */
protected $urlBuilder;
/**
* @var UrlInterface
*/
protected UrlInterface $urlBuilder;

/** @var ConnectedTerminals */
protected $connectedTerminalsHelper;
/**
* @var ConnectedTerminals
*/
protected ConnectedTerminals $connectedTerminalsHelper;

/** @var Config */
protected $configHelper;
/**
* @var Config
*/
protected Config $configHelper;

/**
* @var SerializerInterface
*/
private $serializer;
private SerializerInterface $serializer;

/**
* @var StoreManager
*/
private StoreManager $storeManager;

/**
* @param RequestInterface $request
* @param UrlInterface $urlBuilder
* @param ConnectedTerminals $connectedTerminalsHelper
* @param SerializerInterface $serializer
* @param Config $configHelper
* @param StoreManager $storeManager
*/
public function __construct(
RequestInterface $request,
UrlInterface $urlBuilder,
ConnectedTerminals $connectedTerminalsHelper,
SerializerInterface $serializer,
Config $configHelper
Config $configHelper,
StoreManager $storeManager
) {
$this->request = $request;
$this->urlBuilder = $urlBuilder;
$this->connectedTerminalsHelper = $connectedTerminalsHelper;
$this->configHelper = $configHelper;
$this->serializer = $serializer;
$this->storeManager = $storeManager;
}

/**
* Set configuration for POS Terminal payment method
*
* @return array
* @throws NoSuchEntityException
*/
public function getConfig()
public function getConfig(): array
{
// set to active
$config = [
Expand All @@ -74,16 +100,20 @@ public function getConfig()
]
];

if ($this->configHelper->getAdyenPosCloudConfigData("active", null, true)) {
$storeId = $this->storeManager->getStore()->getId();

$config['payment']['adyenPos']['paymentAction'] = $this->configHelper->getAdyenPosCloudPaymentAction($storeId);

if ($this->configHelper->getAdyenPosCloudConfigData("active", $storeId, true)) {
$config['payment']['adyenPos']['fundingSourceOptions'] = $this->getFundingSourceOptions();
}

// has installments by default false
$config['payment']['adyenPos']['hasInstallments'] = false;

// get Installments
$installmentsEnabled = $this->configHelper->getAdyenPosCloudConfigData('enable_installments');
$installments = $this->configHelper->getAdyenPosCloudConfigData('installments');
$installmentsEnabled = $this->configHelper->getAdyenPosCloudConfigData('enable_installments', $storeId);
$installments = $this->configHelper->getAdyenPosCloudConfigData('installments', $storeId);

if ($installmentsEnabled && $installments) {
$config['payment']['adyenPos']['installments'] = $this->serializer->unserialize($installments);
Expand All @@ -100,7 +130,7 @@ public function getConfig()
*
* @return RequestInterface
*/
protected function getRequest()
protected function getRequest(): RequestInterface
{
return $this->request;
}
Expand Down
Loading

0 comments on commit cbf8bb5

Please sign in to comment.