We will be off from 27/1 (Monday) to 31/1 (Friday) (GMT +7) for our Tet Holiday (Lunar New Year) in our country

Commit 7965a329 authored by Marcos Garcia's avatar Marcos Garcia Committed by GitHub

Fix for Vault with 3DS2 Credit Cards (#814)

The VaultDetailsHandler object is not called when processing 3DS2 payments. To make it so, this commit extracts the logic of storing the info to Vault and reuses that both in the Handler and in the 3DS2 Process.
parent f38243bf
......@@ -23,224 +23,37 @@
namespace Adyen\Payment\Gateway\Response;
use Adyen\Payment\Helper\Data;
use Adyen\Payment\Logger\AdyenLogger;
use Magento\Sales\Api\Data\OrderPaymentExtensionInterface;
use Magento\Vault\Api\Data\PaymentTokenFactoryInterface;
use Magento\Payment\Gateway\Response\HandlerInterface;
use Magento\Payment\Model\InfoInterface;
use Magento\Vault\Api\Data\PaymentTokenInterface;
use Magento\Vault\Model\PaymentTokenManagement;
use Adyen\Payment\Helper\Vault;
use Magento\Payment\Gateway\Data\PaymentDataObject;
use Magento\Vault\Api\PaymentTokenRepositoryInterface;
use Magento\Payment\Gateway\Helper\SubjectReader;
use Magento\Payment\Gateway\Response\HandlerInterface;
class VaultDetailsHandler implements HandlerInterface
{
const RECURRING_DETAIL_REFERENCE = 'recurring.recurringDetailReference';
const CARD_SUMMARY = 'cardSummary';
const EXPIRY_DATE = 'expiryDate';
const PAYMENT_METHOD = 'paymentMethod';
const ADDITIONAL_DATA_ERRORS = [
self::RECURRING_DETAIL_REFERENCE => 'Missing Token in Result please enable in ' .
'Settings -> API URLs and Response menu in the Adyen Customer Area Recurring details setting',
self::CARD_SUMMARY => 'Missing cardSummary in Result please login to the adyen portal ' .
'and go to Settings -> API URLs and Response and enable the Card summary property',
self::EXPIRY_DATE => 'Missing expiryDate in Result please login to the adyen portal and go to ' .
'Settings -> API URLs and Response and enable the Expiry date property',
self::PAYMENT_METHOD => 'Missing paymentMethod in Result please login to the adyen portal and go to ' .
'Settings -> API URLs and Response and enable the Variant property'
];
/**
* @var PaymentTokenFactoryInterface
*/
protected $paymentTokenFactory;
/**
* @var AdyenLogger
*/
private $adyenLogger;
/**
* @var Data
*/
private $adyenHelper;
/**
* @var PaymentTokenManagement
*/
private $paymentTokenManagement;
/**
* @var
* @var Vault
*/
private $paymentTokenRepository;
private $vaultHelper;
/**
* VaultDetailsHandler constructor.
*
* @param PaymentTokenFactoryInterface $paymentTokenFactory
* @param AdyenLogger $adyenLogger
* @param Data $adyenHelper
* @param PaymentTokenManagement $paymentTokenManagement
* @param PaymentTokenRepositoryInterface $paymentTokenRepository
* @param Vault $vaultHelper
*/
public function __construct(
PaymentTokenFactoryInterface $paymentTokenFactory,
AdyenLogger $adyenLogger,
Data $adyenHelper,
PaymentTokenManagement $paymentTokenManagement,
PaymentTokenRepositoryInterface $paymentTokenRepository
) {
$this->adyenLogger = $adyenLogger;
$this->adyenHelper = $adyenHelper;
$this->paymentTokenFactory = $paymentTokenFactory;
$this->paymentTokenManagement = $paymentTokenManagement;
$this->paymentTokenRepository = $paymentTokenRepository;
public function __construct(Vault $vaultHelper)
{
$this->vaultHelper = $vaultHelper;
}
/**
* @inheritdoc
*/
public function handle(array $handlingSubject, array $response)
{
/** @var PaymentDataObject $orderPayment */
$orderPayment = \Magento\Payment\Gateway\Helper\SubjectReader::readPayment($handlingSubject);
$payment = $orderPayment->getPayment();
if ($this->adyenHelper->isCreditCardVaultEnabled($payment->getOrder()->getStoreId())) {
// add vault payment token entity to extension attributes
$paymentToken = $this->getVaultPaymentToken($response, $payment);
if (null !== $paymentToken) {
$extensionAttributes = $this->getExtensionAttributes($payment);
$extensionAttributes->setVaultPaymentToken($paymentToken);
} else {
$this->adyenLogger->error(
sprintf(
'Failure trying to save credit card token in vault for order %s',
$payment->getOrder()->getIncrementId()
)
);
}
}
}
/**
* Get vault payment token entity
*
* @param array $response
* @param $payment
* @return PaymentTokenInterface|null
*/
private function getVaultPaymentToken(array $response, $payment)
{
if (empty($response['additionalData'])) {
return null;
}
$additionalData = $response['additionalData'];
$paymentToken = null;
foreach (self::ADDITIONAL_DATA_ERRORS as $key => $errorMsg) {
if (empty($additionalData[$key])) {
$this->adyenLogger->error($errorMsg);
return null;
}
}
try {
// Check if paymentToken exists already
$paymentToken = $this->paymentTokenManagement->getByGatewayToken(
$additionalData[self::RECURRING_DETAIL_REFERENCE],
$payment->getMethodInstance()->getCode(),
$payment->getOrder()->getCustomerId()
);
$paymentTokenSaveRequired = false;
// In case the payment token does not exist, create it based on the additionalData
if ($paymentToken === null) {
/** @var PaymentTokenInterface $paymentToken */
$paymentToken = $this->paymentTokenFactory->create(
PaymentTokenFactoryInterface::TOKEN_TYPE_CREDIT_CARD
);
$paymentToken->setGatewayToken($additionalData[self::RECURRING_DETAIL_REFERENCE]);
if (strpos($additionalData[self::PAYMENT_METHOD], "paywithgoogle") !== false
&& !empty($additionalData['paymentMethodVariant'])) {
$additionalData[self::PAYMENT_METHOD] = $additionalData['paymentMethodVariant'];
$paymentToken->setIsVisible(false);
}
} else {
$paymentTokenSaveRequired = true;
}
$paymentToken->setExpiresAt($this->getExpirationDate($additionalData[self::EXPIRY_DATE]));
$details = [
'type' => $additionalData[self::PAYMENT_METHOD],
'maskedCC' => $additionalData[self::CARD_SUMMARY],
'expirationDate' => $additionalData[self::EXPIRY_DATE]
];
$paymentToken->setTokenDetails(json_encode($details));
// If the token is updated, it needs to be saved to keep the changes
if ($paymentTokenSaveRequired) {
$this->paymentTokenRepository->save($paymentToken);
}
} catch (\Exception $e) {
$this->adyenLogger->error(print_r($e, true));
}
return $paymentToken;
}
/**
* @param $expirationDate
* @return string
* @throws \Exception
*/
private function getExpirationDate($expirationDate)
{
$expirationDate = explode('/', $expirationDate);
//add leading zero to month
$month = sprintf('%02d', $expirationDate[0]);
$expDate = new \DateTime(
$expirationDate[1]
. '-'
. $month
. '-'
. '01'
. ' '
. '00:00:00',
new \DateTimeZone('UTC')
);
// add one month
$expDate->add(new \DateInterval('P1M'));
return $expDate->format('Y-m-d 00:00:00');
}
/**
* Get payment extension attributes
*
* @param InfoInterface $payment
* @return OrderPaymentExtensionInterface
*/
private function getExtensionAttributes(InfoInterface $payment)
{
$extensionAttributes = $payment->getExtensionAttributes();
if (null === $extensionAttributes) {
$extensionAttributes = $this->paymentExtensionFactory->create();
$payment->setExtensionAttributes($extensionAttributes);
return;
}
return $extensionAttributes;
/** @var PaymentDataObject $orderPayment */
$orderPayment = SubjectReader::readPayment($handlingSubject);
$this->vaultHelper->saveRecurringDetails($orderPayment->getPayment(), $response['additionalData']);
}
}
<?php
/**
* ######
* ######
* ############ ####( ###### #####. ###### ############ ############
* ############# #####( ###### #####. ###### ############# #############
* ###### #####( ###### #####. ###### ##### ###### ##### ######
* ###### ###### #####( ###### #####. ###### ##### ##### ##### ######
* ###### ###### #####( ###### #####. ###### ##### ##### ######
* ############# ############# ############# ############# ##### ######
* ############ ############ ############# ############ ##### ######
* ######
* #############
* ############
*
* Adyen Payment Module
*
* @author Adyen BV <support@adyen.com>
* @copyright (c) 2020 Adyen B.V.
* @license https://opensource.org/licenses/MIT MIT license
* This file is open source and available under the MIT license.
* See the LICENSE file for more info.
*/
namespace Adyen\Payment\Helper;
use Adyen\Payment\Logger\AdyenLogger;
use DateInterval;
use DateTime;
use DateTimeZone;
use Exception;
use Magento\Payment\Model\InfoInterface;
use Magento\Sales\Api\Data\OrderPaymentExtensionInterface;
use Magento\Vault\Api\Data\PaymentTokenFactoryInterface;
use Magento\Vault\Api\Data\PaymentTokenInterface;
use Magento\Vault\Api\PaymentTokenRepositoryInterface;
use Magento\Vault\Model\PaymentTokenManagement;
class Vault
{
const RECURRING_DETAIL_REFERENCE = 'recurring.recurringDetailReference';
const CARD_SUMMARY = 'cardSummary';
const EXPIRY_DATE = 'expiryDate';
const PAYMENT_METHOD = 'paymentMethod';
const ADDITIONAL_DATA_ERRORS = [
self::RECURRING_DETAIL_REFERENCE => 'Missing Token in Result please enable in ' .
'Settings -> API URLs and Response menu in the Adyen Customer Area Recurring details setting',
self::CARD_SUMMARY => 'Missing cardSummary in Result please login to the adyen portal ' .
'and go to Settings -> API URLs and Response and enable the Card summary property',
self::EXPIRY_DATE => 'Missing expiryDate in Result please login to the adyen portal and go to ' .
'Settings -> API URLs and Response and enable the Expiry date property',
self::PAYMENT_METHOD => 'Missing paymentMethod in Result please login to the adyen portal and go to ' .
'Settings -> API URLs and Response and enable the Variant property'
];
/**
* @var Data
*/
private $adyenHelper;
/**
* @var AdyenLogger
*/
private $adyenLogger;
/**
* @var PaymentTokenManagement
*/
private $paymentTokenManagement;
/**
* @var PaymentTokenFactoryInterface
*/
private $paymentTokenFactory;
/**
* @var PaymentTokenRepositoryInterface
*/
private $paymentTokenRepository;
public function __construct(
Data $adyenHelper,
AdyenLogger $adyenLogger,
PaymentTokenManagement $paymentTokenManagement,
PaymentTokenFactoryInterface $paymentTokenFactory
) {
$this->adyenHelper = $adyenHelper;
$this->adyenLogger = $adyenLogger;
$this->paymentTokenManagement = $paymentTokenManagement;
$this->paymentTokenFactory = $paymentTokenFactory;
}
public function saveRecurringDetails($payment, array $additionalData)
{
if (!$this->adyenHelper->isCreditCardVaultEnabled($payment->getOrder()->getStoreId())) {
return;
}
if(!$this->validateAdditionalData($additionalData)) {
return;
}
try {
$paymentToken = $this->getVaultPaymentToken($payment, $additionalData);
} catch (Exception $exception) {
$this->adyenLogger->error(print_r($exception, true));
return;
}
if (null !== $paymentToken) {
$extensionAttributes = $this->getExtensionAttributes($payment);
$extensionAttributes->setVaultPaymentToken($paymentToken);
} else {
$this->adyenLogger->error(
sprintf(
'Failure trying to save credit card token in vault for order %s',
$payment->getOrder()->getIncrementId()
)
);
}
}
/**
* @param $payment
* @param array $additionalData
* @return PaymentTokenInterface|null
* @throws Exception
*/
private function getVaultPaymentToken($payment, array $additionalData): PaymentTokenInterface
{
// Check if paymentToken exists already
$paymentToken = $this->paymentTokenManagement->getByGatewayToken(
$additionalData[self::RECURRING_DETAIL_REFERENCE],
$payment->getMethodInstance()->getCode(),
$payment->getOrder()->getCustomerId()
);
$paymentTokenSaveRequired = false;
// In case the payment token does not exist, create it based on the additionalData
if ($paymentToken === null) {
$paymentToken = $this->paymentTokenFactory->create(
PaymentTokenFactoryInterface::TOKEN_TYPE_CREDIT_CARD
);
$paymentToken->setGatewayToken($additionalData[self::RECURRING_DETAIL_REFERENCE]);
if (strpos($additionalData[self::PAYMENT_METHOD], "paywithgoogle") !== false
&& !empty($additionalData['paymentMethodVariant'])) {
$additionalData[self::PAYMENT_METHOD] = $additionalData['paymentMethodVariant'];
$paymentToken->setIsVisible(false);
}
} else {
$paymentTokenSaveRequired = true;
}
$paymentToken->setExpiresAt($this->getExpirationDate($additionalData[self::EXPIRY_DATE]));
$details = [
'type' => $additionalData[self::PAYMENT_METHOD],
'maskedCC' => $additionalData[self::CARD_SUMMARY],
'expirationDate' => $additionalData[self::EXPIRY_DATE]
];
$paymentToken->setTokenDetails(json_encode($details));
// If the token is updated, it needs to be saved to keep the changes
if ($paymentTokenSaveRequired) {
$this->paymentTokenRepository->save($paymentToken);
}
return $paymentToken;
}
/**
* @param array $additionalData
* @return bool
*/
private function validateAdditionalData(array $additionalData)
{
if (empty($additionalData)) {
return false;
}
foreach (self::ADDITIONAL_DATA_ERRORS as $key => $errorMsg) {
if (empty($additionalData[$key])) {
$this->adyenLogger->error($errorMsg);
return false;
}
}
return true;
}
/**
* @param $expirationDate
* @return string
* @throws Exception
*/
private function getExpirationDate($expirationDate)
{
$expirationDate = explode('/', $expirationDate);
$expDate = new DateTime(
//add leading zero to month
sprintf("%s-%02d-01 00:00:00", $expirationDate[1], $expirationDate[0]),
new DateTimeZone('UTC')
);
// add one month
$expDate->add(new DateInterval('P1M'));
return $expDate->format('Y-m-d H:i:s');
}
/**
* Get payment extension attributes
*
* @param InfoInterface $payment
* @return OrderPaymentExtensionInterface
*/
private function getExtensionAttributes(InfoInterface $payment)
{
$extensionAttributes = $payment->getExtensionAttributes();
if (null === $extensionAttributes) {
$extensionAttributes = $this->paymentExtensionFactory->create();
$payment->setExtensionAttributes($extensionAttributes);
}
return $extensionAttributes;
}
}
......@@ -24,47 +24,60 @@
namespace Adyen\Payment\Model;
use Adyen\Payment\Api\AdyenThreeDS2ProcessInterface;
use Adyen\Payment\Helper\Data;
use Adyen\Payment\Helper\Vault;
use Adyen\Payment\Logger\AdyenLogger;
use Magento\Checkout\Model\Session;
use Magento\Sales\Model\OrderFactory;
class AdyenThreeDS2Process implements AdyenThreeDS2ProcessInterface
{
/**
* @var \Magento\Checkout\Model\Session
* @var Session
*/
private $checkoutSession;
/**
* @var \Adyen\Payment\Helper\Data
* @var Data
*/
private $adyenHelper;
/**
* @var \Magento\Sales\Model\OrderFactory
* @var OrderFactory
*/
private $orderFactory;
/**
* @var \Adyen\Payment\Logger\AdyenLogger
* @var AdyenLogger
*/
private $adyenLogger;
/**
* @var Vault
*/
private $vaultHelper;
/**
* AdyenThreeDS2Process constructor.
*
* @param \Magento\Checkout\Model\Session $checkoutSession
* @param \Adyen\Payment\Helper\Data $adyenHelper
* @param \Magento\Sales\Model\OrderFactory $orderFactory
* @param \Adyen\Payment\Logger\AdyenLogger $adyenLogger
* @param Session $checkoutSession
* @param Data $adyenHelper
* @param OrderFactory $orderFactory
* @param AdyenLogger $adyenLogger
* @param Vault $vaultHelper
*/
public function __construct(
\Magento\Checkout\Model\Session $checkoutSession,
\Adyen\Payment\Helper\Data $adyenHelper,
\Magento\Sales\Model\OrderFactory $orderFactory,
\Adyen\Payment\Logger\AdyenLogger $adyenLogger
Session $checkoutSession,
Data $adyenHelper,
OrderFactory $orderFactory,
AdyenLogger $adyenLogger,
Vault $vaultHelper
) {
$this->checkoutSession = $checkoutSession;
$this->adyenHelper = $adyenHelper;
$this->orderFactory = $orderFactory;
$this->adyenLogger = $adyenLogger;
$this->vaultHelper = $vaultHelper;
}
/**
......@@ -160,6 +173,8 @@ class AdyenThreeDS2Process implements AdyenThreeDS2ProcessInterface
// Save the payments response because we are going to need it during the place order flow
$payment->setAdditionalInformation("paymentsResponse", $result);
$this->vaultHelper->saveRecurringDetails($payment, $result['additionalData']);
// To actually save the additional info changes into the quote
$order->save();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment