Commit 4d893cf7 authored by Ángel Campos's avatar Ángel Campos Committed by GitHub

[PW-3136]Processing generic payment details requests (#835)

* Processing generic payment details requests
Co-authored-by: default avatarmarcoss <marcos.silvagarcia@adyen.com>
parent a865b74f
<?php
/**
* ######
* ######
* ############ ####( ###### #####. ###### ############ ############
* ############# #####( ###### #####. ###### ############# #############
* ###### #####( ###### #####. ###### ##### ###### ##### ######
* ###### ###### #####( ###### #####. ###### ##### ##### ##### ######
* ###### ###### #####( ###### #####. ###### ##### ##### ######
* ############# ############# ############# ############# ##### ######
* ############ ############ ############# ############ ##### ######
* ######
* #############
* ############
*
* Adyen Payment module (https://www.adyen.com/)
*
* Copyright (c) 2020 Adyen BV (https://www.adyen.com/)
* See LICENSE.txt for license details.
*
* Author: Adyen <magento@adyen.com>
*/
namespace Adyen\Payment\Helper;
use Adyen\Payment\Logger\AdyenLogger;
use Magento\Sales\Api\Data\OrderInterface;
use Magento\Sales\Api\Data\OrderPaymentInterface;
use Magento\Sales\Model\Order;
class PaymentResponseHandler
{
const AUTHORISED = 'Authorised';
const REFUSED = 'Refused';
const REDIRECT_SHOPPER = 'RedirectShopper';
const IDENTIFY_SHOPPER = 'IdentifyShopper';
const CHALLENGE_SHOPPER = 'ChallengeShopper';
const RECEIVED = 'Received';
const PENDING = 'Pending';
const PRESENT_TO_SHOPPER = 'PresentToShopper';
const ERROR = 'Error';
const CANCELLED = 'Cancelled';
private $adyenLogger;
public function __construct(
AdyenLogger $adyenLogger
) {
$this->adyenLogger = $adyenLogger;
}
public function formatPaymentResponse($resultCode, $action = null, $additionalData = null)
{
switch ($resultCode) {
case self::AUTHORISED:
case self::REFUSED:
case self::ERROR:
return [
"isFinal" => true,
"resultCode" => $resultCode,
];
case self::REDIRECT_SHOPPER:
case self::IDENTIFY_SHOPPER:
case self::CHALLENGE_SHOPPER:
case self::PENDING:
return [
"isFinal" => false,
"resultCode" => $resultCode,
"action" => $action
];
case self::PRESENT_TO_SHOPPER:
return [
"isFinal" => true,
"resultCode" => $resultCode,
"action" => $action
];
case self::RECEIVED:
return [
"isFinal" => true,
"resultCode" => $resultCode,
"additionalData" => $additionalData
];
default:
return [
"isFinal" => true,
"resultCode" => self::ERROR,
];
}
}
/**
* @param $paymentsResponse
* @param OrderPaymentInterface $payment
* @param OrderInterface|null $order
* @return bool
*/
public function handlePaymentResponse($paymentsResponse, $payment, $order = null)
{
if (empty($paymentsResponse)) {
$this->adyenLogger->error("Payment details call failed, paymentsResponse is empty");
return false;
}
switch ($paymentsResponse['resultCode']) {
case self::PRESENT_TO_SHOPPER:
case self::PENDING:
case self::RECEIVED:
$payment->setAdditionalInformation("paymentsResponse", $paymentsResponse);
break;
//We don't need to handle these resultCodes
case self::AUTHORISED:
case self::REDIRECT_SHOPPER:
case self::IDENTIFY_SHOPPER:
case self::CHALLENGE_SHOPPER:
break;
//These resultCodes cancel the order and log an error
case self::REFUSED:
case self::ERROR:
default:
if (!$order->canCancel()) {
$order->setState(Order::STATE_NEW);
}
//TODO check if order gets cancelled
$order->cancel();
$this->adyenLogger->error(
sprintf("Payment details call failed for action, resultCode is %s Raw API responds: %s",
$paymentsResponse['resultCode'],
print_r($paymentsResponse, true)
));
return false;
}
return true;
}
}
...@@ -23,12 +23,15 @@ ...@@ -23,12 +23,15 @@
namespace Adyen\Payment\Model; namespace Adyen\Payment\Model;
use Adyen\AdyenException;
use Adyen\Payment\Api\AdyenPaymentDetailsInterface; use Adyen\Payment\Api\AdyenPaymentDetailsInterface;
use Adyen\Payment\Helper\Data; use Adyen\Payment\Helper\Data;
use Adyen\Payment\Helper\PaymentResponseHandler;
use Adyen\Payment\Helper\Vault; use Adyen\Payment\Helper\Vault;
use Adyen\Payment\Logger\AdyenLogger; use Adyen\Payment\Logger\AdyenLogger;
use Magento\Checkout\Model\Session; use Magento\Checkout\Model\Session;
use Magento\Sales\Model\OrderFactory; use Magento\Framework\Exception\LocalizedException;
use Magento\Sales\Api\OrderRepositoryInterface;
class AdyenPaymentDetails implements AdyenPaymentDetailsInterface class AdyenPaymentDetails implements AdyenPaymentDetailsInterface
{ {
...@@ -42,11 +45,6 @@ class AdyenPaymentDetails implements AdyenPaymentDetailsInterface ...@@ -42,11 +45,6 @@ class AdyenPaymentDetails implements AdyenPaymentDetailsInterface
*/ */
private $adyenHelper; private $adyenHelper;
/**
* @var OrderFactory
*/
private $orderFactory;
/** /**
* @var AdyenLogger * @var AdyenLogger
*/ */
...@@ -57,27 +55,40 @@ class AdyenPaymentDetails implements AdyenPaymentDetailsInterface ...@@ -57,27 +55,40 @@ class AdyenPaymentDetails implements AdyenPaymentDetailsInterface
*/ */
private $vaultHelper; private $vaultHelper;
/**
* @var OrderRepositoryInterface
*/
private $orderRepository;
/**
* @var PaymentResponseHandler
*/
private $paymentResponseHandler;
/** /**
* AdyenPaymentDetails constructor. * AdyenPaymentDetails constructor.
* *
* @param Session $checkoutSession * @param Session $checkoutSession
* @param Data $adyenHelper * @param Data $adyenHelper
* @param OrderFactory $orderFactory
* @param AdyenLogger $adyenLogger * @param AdyenLogger $adyenLogger
* @param Vault $vaultHelper * @param Vault $vaultHelper
* @param OrderRepositoryInterface $orderRepository
* @param PaymentResponseHandler $paymentResponseHandler
*/ */
public function __construct( public function __construct(
Session $checkoutSession, Session $checkoutSession,
Data $adyenHelper, Data $adyenHelper,
OrderFactory $orderFactory,
AdyenLogger $adyenLogger, AdyenLogger $adyenLogger,
Vault $vaultHelper Vault $vaultHelper,
OrderRepositoryInterface $orderRepository,
PaymentResponseHandler $paymentResponseHandler
) { ) {
$this->checkoutSession = $checkoutSession; $this->checkoutSession = $checkoutSession;
$this->adyenHelper = $adyenHelper; $this->adyenHelper = $adyenHelper;
$this->orderFactory = $orderFactory;
$this->adyenLogger = $adyenLogger; $this->adyenLogger = $adyenLogger;
$this->vaultHelper = $vaultHelper; $this->vaultHelper = $vaultHelper;
$this->orderRepository = $orderRepository;
$this->paymentResponseHandler = $paymentResponseHandler;
} }
/** /**
...@@ -92,128 +103,54 @@ class AdyenPaymentDetails implements AdyenPaymentDetailsInterface ...@@ -92,128 +103,54 @@ class AdyenPaymentDetails implements AdyenPaymentDetailsInterface
// Validate JSON that has just been parsed if it was in a valid format // Validate JSON that has just been parsed if it was in a valid format
if (json_last_error() !== JSON_ERROR_NONE) { if (json_last_error() !== JSON_ERROR_NONE) {
throw new \Magento\Framework\Exception\LocalizedException( throw new LocalizedException(__('Payment details call failed because the request was not a valid JSON'));
__('3D secure 2.0 failed because the request was not a valid JSON')
);
} }
//Get order from payload and remove orderId from the array
if (empty($payload['orderId'])) { if (empty($payload['orderId'])) {
$order = $this->getOrder(); throw new LocalizedException
// In the next major release remove support for retrieving order from session and throw exception instead (__('Payment details call failed because of a missing order ID'));
//throw new \Magento\Framework\Exception\LocalizedException
//(__('3D secure 2.0 failed because of a missing order id'));
} else { } else {
// Create order by order id $order = $this->orderRepository->get($payload['orderId']);
$order = $this->orderFactory->create()->load($payload['orderId']); //TODO send state.data from frontend so no unsetting is necessary
// don't send orderId to adyen. Improve that orderId and state.data are separated in payload
unset($payload['orderId']); unset($payload['orderId']);
} }
$payment = $order->getPayment(); $payment = $order->getPayment();
// Init payments/details request
$result = [];
if ($paymentData = $payment->getAdditionalInformation("adyenPaymentData")) { if ($paymentData = $payment->getAdditionalInformation("adyenPaymentData")) {
// Add payment data into the request object // Add payment data into the request object
$request = [ $payload["paymentData"] = $paymentData;
"paymentData" => $paymentData
];
// unset payment data from additional information // unset payment data from additional information
$payment->unsAdditionalInformation("adyenPaymentData"); $payment->unsAdditionalInformation("adyenPaymentData");
} else { } else {
$this->adyenLogger->error("3D secure 2.0 failed, payment data not found"); $message = "Payment details call failed, payment data not found";
throw new \Magento\Framework\Exception\LocalizedException( $this->adyenLogger->error($message);
__('3D secure 2.0 failed, payment data not found') throw new LocalizedException(__($message));
);
} }
// Depends on the component's response we send a fingerprint or the challenge result
if (!empty($payload['details']['threeds2.fingerprint'])) {
$request['details']['threeds2.fingerprint'] = $payload['details']['threeds2.fingerprint'];
} elseif (!empty($payload['details']['threeds2.challengeResult'])) {
$request['details']['threeds2.challengeResult'] = $payload['details']['threeds2.challengeResult'];
} elseif (!empty($payload)) {
$request = $payload;
}
// Send the request // Send the request
try { try {
$client = $this->adyenHelper->initializeAdyenClient($order->getStoreId()); $client = $this->adyenHelper->initializeAdyenClient($order->getStoreId());
$service = $this->adyenHelper->createAdyenCheckoutService($client); $service = $this->adyenHelper->createAdyenCheckoutService($client);
$paymentDetails = $service->paymentsDetails($payload);
$result = $service->paymentsDetails($request); } catch (AdyenException $e) {
} catch (\Adyen\AdyenException $e) { $this->adyenLogger->error("Payment details call failed: " . $e->getMessage());
$this->adyenLogger->error("3D secure 2.0 failed" . $e->getMessage()); throw new LocalizedException(__('Payment details call failed'));
throw new \Magento\Framework\Exception\LocalizedException(__('3D secure 2.0 failed'));
} }
// Check if result is challenge shopper, if yes return the token //TODO test this with payments that return additionalData
if (!empty($result['resultCode']) && //TODO check for Authorized result code and move to the handler
$result['resultCode'] === 'ChallengeShopper' && if (!empty($paymentDetails['additionalData'])) {
!empty($result['authentication']['threeds2.challengeToken']) $this->vaultHelper->saveRecurringDetails($payment, $paymentDetails['additionalData']);
) {
return $this->adyenHelper->buildThreeDS2ProcessResponseJson(
$result['resultCode'],
$result['authentication']['threeds2.challengeToken']
);
} }
//Fallback for 3DS in case of redirect
if (!empty($result['resultCode']) &&
$result['resultCode'] === 'RedirectShopper'
) {
$response['type'] = $result['resultCode'];
$response['action']= $result['action'];
return json_encode($response); //TODO check if order save is necessary to save additionalData
}
// Save the payments response because we are going to need it during the place order flow if (!$this->paymentResponseHandler->handlePaymentResponse($paymentDetails, $payment, $order)) {
$payment->setAdditionalInformation("paymentsResponse", $result);
$this->vaultHelper->saveRecurringDetails($payment, $result['additionalData']);
// To actually save the additional info changes into the quote
$order->save();
$response = [];
if ($result['resultCode'] != 'Authorised') {
$this->checkoutSession->restoreQuote(); $this->checkoutSession->restoreQuote();
throw new LocalizedException(__('The payment is REFUSED.'));
// Always cancel the order if the paymenth has failed
if (!$order->canCancel()) {
$order->setState(\Magento\Sales\Model\Order::STATE_NEW);
}
$order->cancel()->save();
$this->adyenLogger->error(
sprintf("Payment details call failed for action or 3ds2 payment method, resultcode is %s Raw API responds: %s",
$result['resultCode'],
print_r($result, true)
));
throw new \Magento\Framework\Exception\LocalizedException(__('The payment is REFUSED.'));
} }
return json_encode($this->paymentResponseHandler->formatPaymentResponse($paymentDetails['resultCode']));
$response['result'] = $result['resultCode'];
return json_encode($response);
}
/**
* Get order object
*
* @return \Magento\Sales\Model\Order
* @deprecated Will be removed in 7.0.0
*/
protected function getOrder()
{
$incrementId = $this->checkoutSession->getLastRealOrderId();
$order = $this->orderFactory->create()->loadByIncrementId($incrementId);
return $order;
} }
} }
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