We will work on Apr 26th (Saturday) and will be off from Apr 30th (Wednesday) until May 2nd (Friday) for public holiday in our country

Commit 6bdeaaa5 authored by Attila Kiss's avatar Attila Kiss Committed by GitHub

Merge pull request #730 from Adyen/develop

Release 6.1.1
parents 21350d08 c3a7e85c
* @msilvagarcia @cyattilakiss @Aleffio @AlexandrosMor @rikterbeek @acampos1916
* @cyattilakiss @AlexandrosMor @msilvagarcia @acampos1916 @Aleffio @rikterbeek
**Magento version**: x.y.z
**Plugin version**: x.y.z
**Description**
<!--
- Please provide a description of the issue. In case of bug report, please provide the necessary steps to reproduce.
- For merchant specific requests, please use https://support.adyen.com
-->
\ No newline at end of file
......@@ -29,4 +29,18 @@ class Boleto extends AbstractInfo
* @var string
*/
protected $_template = 'Adyen_Payment::info/adyen_boleto.phtml';
/**
* @param $data string
* @return string
*/
public function getPaymentActionData($data)
{
$paymentAction = $this->getMethod()->getInfoInstance()->getAdditionalInformation('action');
if (empty($paymentAction[$data])) {
return '';
}
return $paymentAction[$data];
}
}
......@@ -107,7 +107,14 @@ class Result extends \Magento\Framework\App\Action\Action
$session->getQuote()->setIsActive(false)->save();
$this->_redirect('checkout/onepage/success', ['_query' => ['utm_nooverride' => '1']]);
} else {
$this->_cancel($response);
$this->_adyenLogger->addAdyenResult(
sprintf(
'Payment for order %s was unsuccessful, ' .
'it will be cancelled when the OFFER_CLOSED notification has been processed.',
$this->_order->getIncrementId()
)
);
$this->restoreCart($response);
$failReturnPath = $this->_adyenHelper->getAdyenAbstractConfigData('return_path');
$this->_redirect($failReturnPath);
}
......@@ -121,17 +128,13 @@ class Result extends \Magento\Framework\App\Action\Action
/**
* @param $response
*/
protected function _cancel($response)
protected function restoreCart($response)
{
$session = $this->_session;
// restore the quote
$session->restoreQuote();
$order = $this->_order;
$this->_adyenHelper->cancelOrder($order);
if (isset($response['authResult']) && $response['authResult'] == \Adyen\Payment\Model\Notification::CANCELLED) {
$this->messageManager->addError(__('You have cancelled the order. Please try again'));
} else {
......@@ -270,17 +273,13 @@ class Result extends \Magento\Framework\App\Action\Action
$this->_adyenLogger->addAdyenResult('Do nothing wait for the notification');
break;
case Notification::CANCELLED:
$this->_adyenLogger->addAdyenResult('Cancel or Hold the order');
case Notification::ERROR:
$this->_adyenLogger->addAdyenResult('Cancel or Hold the order on OFFER_CLOSED notification');
$result = false;
break;
case Notification::REFUSED:
// if refused there will be a AUTHORIZATION : FALSE notification send only exception is idea
$this->_adyenLogger->addAdyenResult('Cancel or Hold the order');
$result = false;
break;
case Notification::ERROR:
//attempt to hold/cancel
$this->_adyenLogger->addAdyenResult('Cancel or Hold the order');
$this->_adyenLogger->addAdyenResult('Cancel or Hold the order on AUTHORISATION success = false notification');
$result = false;
break;
default:
......
<?php
/**
* ######
* ######
* ############ ####( ###### #####. ###### ############ ############
* ############# #####( ###### #####. ###### ############# #############
* ###### #####( ###### #####. ###### ##### ###### ##### ######
* ###### ###### #####( ###### #####. ###### ##### ##### ##### ######
* ###### ###### #####( ###### #####. ###### ##### ##### ######
* ############# ############# ############# ############# ##### ######
* ############ ############ ############# ############ ##### ######
* ######
* #############
* ############
*
* Adyen Payment module (https://www.adyen.com/)
*
* Copyright (c) 2020 Adyen NV (https://www.adyen.com/)
* See LICENSE.txt for license details.
*
* Author: Adyen <magento@adyen.com>
*/
namespace Adyen\Payment\Gateway\Data\Order;
use Magento\Sales\Api\Data\OrderAddressInterface;
class AddressAdapter extends \Magento\Payment\Gateway\Data\Order\AddressAdapter
{
/**
* @var OrderAddressInterface
*/
private $address;
public function __construct(OrderAddressInterface $address)
{
$this->address = $address;
parent::__construct($address);
}
/**
* Get street line 3
*
* @return string
*/
public function getStreetLine3()
{
$street = $this->address->getStreet();
return isset($street[2]) ? $street[2] : '';
}
/**
* Get street line 4
*
* @return string
*/
public function getStreetLine4()
{
$street = $this->address->getStreet();
return isset($street[3]) ? $street[3] : '';
}
}
......@@ -64,14 +64,8 @@ class TransactionAuthorization implements ClientInterface
public function placeRequest(\Magento\Payment\Gateway\Http\TransferInterface $transferObject)
{
$request = $transferObject->getBody();
$headers = $transferObject->getHeaders();
$requestOptions = [];
if (!empty($headers['idempotencyKey'])) {
$requestOptions['idempotencyKey'] = $headers['idempotencyKey'];
}
// call lib
$service = new \Adyen\Service\Payment($this->client);
......
......@@ -63,7 +63,6 @@ class TransactionPayment implements ClientInterface
public function placeRequest(\Magento\Payment\Gateway\Http\TransferInterface $transferObject)
{
$request = $transferObject->getBody();
$headers = $transferObject->getHeaders();
// If the payments call is already done return the request
if (!empty($request['resultCode'])) {
......@@ -77,10 +76,6 @@ class TransactionPayment implements ClientInterface
$requestOptions = [];
if (!empty($headers['idempotencyKey'])) {
$requestOptions['idempotencyKey'] = $headers['idempotencyKey'];
}
$request = $this->applicationInfo->addMerchantApplicationIntoRequest($request);
try {
......
......@@ -156,8 +156,8 @@ class CheckoutDataBuilder implements BuilderInterface
$requestBody['shopperName']['firstName'] = $payment->getAdditionalInformation("firstname");
}
if ($payment->getAdditionalInformation("lastName")) {
$requestBody['shopperName']['lastName'] = $payment->getAdditionalInformation("lastName");
if ($payment->getAdditionalInformation("lastname")) {
$requestBody['shopperName']['lastName'] = $payment->getAdditionalInformation("lastname");
}
if ($payment->getMethod() == \Adyen\Payment\Model\Ui\AdyenBoletoConfigProvider::CODE) {
......
......@@ -63,9 +63,7 @@ class PaymentDataBuilder implements BuilderInterface
$paymentMethod = $payment->getMethod();
$request['body'] = $this->adyenRequestsHelper->buildPaymentData([], $amount, $currencyCode, $reference, $paymentMethod);
$request['headers'] = $this->adyenRequestsHelper->addIdempotencyKey([], $paymentMethod, $reference);
return $request;
}
}
......@@ -23,43 +23,79 @@
namespace Adyen\Payment\Gateway\Response;
use Adyen\Payment\Helper\Data;
use Adyen\Payment\Logger\AdyenLogger;
use Magento\Vault\Api\Data\PaymentTokenFactoryInterface;
use Magento\Payment\Gateway\Response\HandlerInterface;
use Magento\Payment\Model\InfoInterface;
use Magento\Vault\Model\PaymentTokenManagement;
use Magento\Payment\Gateway\Data\PaymentDataObject;
use Magento\Vault\Api\PaymentTokenRepositoryInterface;
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 = array(
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 PaymentTokenInterfaceFactory
* @var PaymentTokenFactoryInterface
*/
protected $paymentTokenFactory;
/**
* @var \Adyen\Payment\Logger\AdyenLogger
* @var AdyenLogger
*/
private $adyenLogger;
/**
* @var \Adyen\Payment\Helper\Data
* @var Data
*/
private $adyenHelper;
/**
* @var PaymentTokenManagement
*/
private $paymentTokenManagement;
/**
* @var
*/
private $paymentTokenRepository;
/**
* VaultDetailsHandler constructor.
*
* @param PaymentTokenFactoryInterface $paymentTokenFactory
* @param \Adyen\Payment\Logger\AdyenLogger $adyenLogger
* @param \Adyen\Payment\Helper\Data $adyenHelper
* @param AdyenLogger $adyenLogger
* @param Data $adyenHelper
* @param PaymentTokenManagement $paymentTokenManagement
* @param PaymentTokenRepositoryInterface $paymentTokenRepository
*/
public function __construct(
PaymentTokenFactoryInterface $paymentTokenFactory,
\Adyen\Payment\Logger\AdyenLogger $adyenLogger,
\Adyen\Payment\Helper\Data $adyenHelper
AdyenLogger $adyenLogger,
Data $adyenHelper,
PaymentTokenManagement $paymentTokenManagement,
PaymentTokenRepositoryInterface $paymentTokenRepository
) {
$this->adyenLogger = $adyenLogger;
$this->adyenHelper = $adyenHelper;
$this->paymentTokenFactory = $paymentTokenFactory;
$this->paymentTokenManagement = $paymentTokenManagement;
$this->paymentTokenRepository = $paymentTokenRepository;
}
/**
......@@ -67,18 +103,23 @@ class VaultDetailsHandler implements HandlerInterface
*/
public function handle(array $handlingSubject, array $response)
{
$payment = \Magento\Payment\Gateway\Helper\SubjectReader::readPayment($handlingSubject);
/** @var PaymentDataObject $orderPayment */
$orderPayment = \Magento\Payment\Gateway\Helper\SubjectReader::readPayment($handlingSubject);
/** @var \Adyen\Payment\Api\Data\OrderPaymentInterface $payment */
$payment = $payment->getPayment();
$payment = $orderPayment->getPayment();
if ($this->adyenHelper->isCreditCardVaultEnabled($payment->getOrder()->getStoreId())) {
// add vault payment token entity to extension attributes
$paymentToken = $this->getVaultPaymentToken($response);
$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())
);
}
}
}
......@@ -87,77 +128,69 @@ class VaultDetailsHandler implements HandlerInterface
* Get vault payment token entity
*
* @param array $response
* @param $payment
* @return PaymentTokenInterface|null
*/
private function getVaultPaymentToken(array $response)
private function getVaultPaymentToken(array $response, $payment)
{
$paymentToken = null;
if (!empty($response['additionalData'])) {
$additionalData = $response['additionalData'];
if (empty($response['additionalData'])) {
return null;
}
if (empty($additionalData['recurring.recurringDetailReference'])) {
$this->adyenLogger->error(
'Missing Token in Result please enable in ' .
'Settings -> API URLs and Response menu in the Adyen Customer Area Recurring details setting'
);
return null;
}
$token = $additionalData['recurring.recurringDetailReference'];
$additionalData = $response['additionalData'];
$paymentToken = null;
if (empty($additionalData['cardSummary'])) {
$this->adyenLogger->error(
'Missing cardSummary in Result please login to the adyen portal ' .
'and go to Settings -> API URLs and Response and enable the Card summary property'
);
foreach (self::ADDITIONAL_DATA_ERRORS as $key => $errorMsg) {
if (empty($additionalData[$key])) {
$this->adyenLogger->error($errorMsg);
return null;
}
$cardSummary = $additionalData['cardSummary'];
}
if (empty($additionalData['expiryDate'])) {
$this->adyenLogger->error(
'Missing expiryDate in Result please login to the adyen portal and go to ' .
'Settings -> API URLs and Response and enable the Expiry date property'
);
return null;
}
$expirationDate = $additionalData['expiryDate'];
try {
if (empty($additionalData['paymentMethod'])) {
$this->adyenLogger->error(
'Missing paymentMethod in Result please login to the adyen portal and go to ' .
'Settings -> API URLs and Response and enable the Variant property'
);
return null;
}
// Check if paymentToken exists already
$paymentToken = $this->paymentTokenManagement->getByGatewayToken($additionalData[self::RECURRING_DETAIL_REFERENCE],
$payment->getMethodInstance()->getCode(), $payment->getOrder()->getCustomerId());
$cardType = $additionalData['paymentMethod'];
$paymentTokenSaveRequired = false;
try {
// In case the payment token does not exist, create it based on the additionalData
if (is_null($paymentToken)) {
/** @var PaymentTokenInterface $paymentToken */
$paymentToken = $this->paymentTokenFactory->create(
PaymentTokenFactoryInterface::TOKEN_TYPE_CREDIT_CARD
);
if (strpos($cardType, "paywithgoogle") !== false && !empty($additionalData['paymentMethodVariant'])) {
$cardType = $additionalData['paymentMethodVariant'];
$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);
}
$paymentToken->setGatewayToken($token);
$paymentToken->setExpiresAt($this->getExpirationDate($expirationDate));
$details = [
'type' => $cardType,
'maskedCC' => $cardSummary,
'expirationDate' => $expirationDate
];
$paymentToken->setTokenDetails(json_encode($details));
} catch (\Exception $e) {
$this->adyenLogger->error(print_r($e, true));
} 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;
......
......@@ -487,26 +487,15 @@ class Requests extends AbstractHelper
// Parse address into street and house number where possible
$address = $this->adyenHelper->getStreetFromString($address->getStreetFull());
} else {
$address = $this->adyenHelper->getStreetFromString(implode(' ', [$address->getStreetLine1(), $address->getStreetLine2()]));
$address = $this->adyenHelper->getStreetFromString(
implode(' ', [
$address->getStreetLine1(),
$address->getStreetLine2(),
$address->getStreetLine3(),
$address->getStreetLine4()
]));
}
return $address;
}
/**
* Only adds idempotency key if payment method is adyen_hpp for now
*
* @param array $request
* @param $paymentMethod
* @param $idempotencyKey
* @return array
*/
public function addIdempotencyKey($request = [], $paymentMethod, $idempotencyKey)
{
if (!empty($paymentMethod) && $paymentMethod == 'adyen_hpp') {
$request['idempotencyKey'] = $idempotencyKey;
}
return $request;
}
}
......@@ -331,6 +331,34 @@ class Cron
}
}
/**
* @param $notification
* @return bool
*/
private function shouldSkipProcessingNotification($notification)
{
// OFFER_CLOSED notifications needs to be at least 10 minutes old to be processed
$offerClosedMinDate = new \DateTime();
$offerClosedMinDate->modify('-10 minutes');
// Remove OFFER_CLOSED notifications arrived in the last 10 minutes from the list to process to ensure it
// won't close any order which has an AUTHORISED notification arrived a bit later than the OFFER_CLOSED one.
$createdAt = \DateTime::createFromFormat('Y-m-d H:i:s', $notification['created_at']);
// To get the difference between $offerClosedMinDate and $createdAt, $offerClosedMinDate time in seconds is
// deducted from $createdAt time in seconds, divided by 60 and rounded down to integer
$minutesUntilProcessing = floor(($createdAt->getTimestamp() - $offerClosedMinDate->getTimestamp()) / 60);
if ($notification['event_code'] == Notification::OFFER_CLOSED && $minutesUntilProcessing > 0) {
$this->_adyenLogger->addAdyenNotificationCronjob(
sprintf('OFFER_CLOSED notification %s skipped! Wait %s minute(s) before processing.',
$notification->getEntityId(), $minutesUntilProcessing)
);
return true;
}
return false;
}
public function execute()
{
// needed for Magento < 2.2.0 https://github.com/magento/magento2/pull/8413
......@@ -341,22 +369,19 @@ class Cron
$this->_order = null;
// execute notifications from 2 minute or earlier because order could not yet been created by magento
$dateStart = new \DateTime();
$dateStart->modify('-5 day');
$dateEnd = new \DateTime();
$dateEnd->modify('-1 minute');
$dateRange = ['from' => $dateStart, 'to' => $dateEnd, 'datetime' => true];
// create collection
$notifications = $this->_notificationFactory->create();
$notifications->addFieldToFilter('done', 0);
$notifications->addFieldToFilter('processing', 0);
$notifications->addFieldToFilter('created_at', $dateRange);
$notifications->addFieldToFilter('error_count', ['lt' => Notification::MAX_ERROR_COUNT]);
$notifications->notificationsToProcessFilter();
// Loop thorugh notifications to set processing to true if notifiaction should not be skipped
foreach ($notifications as $notification) {
// set Cron processing to true
// Check if notification should be processed or not
if ($this->shouldSkipProcessingNotification($notification)) {
// Remove notification from collection which will be processed
$notifications->removeItemByKey($notification->getId());
continue;
}
// set notification processing to true
$this->_updateNotification($notification, true, false);
}
......@@ -459,7 +484,7 @@ class Cron
);
}
//Trigger admin notice for unsuccessful REFUND notifications
if ($this->_eventCode == Notification::REFUND){
if ($this->_eventCode == Notification::REFUND) {
$this->addRefundFailedNotice();
}
} else {
......@@ -882,7 +907,6 @@ class Cron
*/
protected function _processNotification()
{
$this->_adyenLogger->addAdyenNotificationCronjob('Processing the notification');
$_paymentCode = $this->_paymentMethodCode();
......@@ -1143,10 +1167,8 @@ class Cron
// Populate billing agreement data
$billingAgreement->parseRecurringContractData($contractDetail);
if ($billingAgreement->isValid()) {
if (!$this->agreementResourceModel->getOrderRelation($billingAgreement->getAgreementId(),
$this->_order->getId())) {
// save into sales_billing_agreement_order
$billingAgreement->addOrderRelation($this->_order);
......
......@@ -46,4 +46,30 @@ class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\Ab
$this->addFieldToFilter('created_at', $dateRange);
return $this;
}
/**
* Filter the notifications table to get non processed or done notifications without 5 or more errors older than
* 2 minutes but not older than 5 days, ordered by created_at and event_code columns
*
* @return $this
*/
public function notificationsToProcessFilter()
{
// execute notifications from 2 minute or earlier because order could not yet been created by magento
$dateStart = new \DateTime();
$dateStart->modify('-5 day');
$dateEnd = new \DateTime();
$dateEnd->modify('-1 minute');
$dateRange = ['from' => $dateStart, 'to' => $dateEnd, 'datetime' => true];
$this->addFieldToFilter('done', 0);
$this->addFieldToFilter('processing', 0);
$this->addFieldToFilter('created_at', $dateRange);
$this->addFieldToFilter('error_count', ['lt' => \Adyen\Payment\Model\Notification::MAX_ERROR_COUNT]);
// Process the notifications in ascending order by creation date and event_code
$this->getSelect()->order('created_at ASC')->order('event_code ASC');
return $this;
}
}
......@@ -2,7 +2,7 @@
"name": "adyen/module-payment",
"description": "Official Magento2 Plugin to connect to Payment Service Provider Adyen.",
"type": "magento2-module",
"version": "6.1.0",
"version": "6.1.1",
"license": [
"OSL-3.0",
"AFL-3.0"
......
......@@ -914,6 +914,7 @@
</virtualType>
<preference for="Magento\Paypal\Model\Billing\Agreement" type="Adyen\Payment\Model\Billing\Agreement" />
<preference for="Magento\Payment\Gateway\Data\Order\AddressAdapter" type="Adyen\Payment\Gateway\Data\Order\AddressAdapter"/>
<type name="Adyen\Payment\Logger\Handler\AdyenDebug">
<arguments>
<argument name="filesystem" xsi:type="object">Magento\Framework\Filesystem\Driver\File</argument>
......
......@@ -73,9 +73,9 @@ $_isDemoMode = $block->isDemoMode();
</div>
<?php endif; ?>
<?php if($_info->getAdditionalInformation('expirationDate') != ""): ?>
<?php if(!empty($block->getPaymentActionData('expiresAt'))): ?>
<div>
<?php echo __('Expiration Date: %1', $_info->getAdditionalInformation('expirationDate')) ?>
<?php echo __('Expiration Date: %1', $block->getPaymentActionData('expiresAt')) ?>
</div>
<?php endif; ?>
......@@ -85,9 +85,9 @@ $_isDemoMode = $block->isDemoMode();
</div>
<?php endif; ?>
<?php if($_info->getAdditionalInformation('url') != ""): ?>
<?php if(!empty($block->getPaymentActionData('downloadUrl'))): ?>
<div>
<a target="_blank" href="<?php echo $_info->getAdditionalInformation('url'); ?>"><?php echo __('PDF Url'); ?></a>
<a target="_blank" href="<?php echo $block->getPaymentActionData('downloadUrl'); ?>"><?php echo __('PDF Url'); ?></a>
</div>
<?php endif; ?>
......
......@@ -33,5 +33,5 @@ $_info = $this->getInfo();
<dt class="title"><?php echo $_info->getAdditionalInformation('boleto_type'); ?></dt>
<dt class="title"><?php echo $_info->getAdditionalInformation('firstname'); ?></dt>
<dt class="title"><?php echo $_info->getAdditionalInformation('lastname'); ?></dt>
<dt class="title"><a target="_blank" href="<?php echo $this->getMethod()->getInfoInstance()->getAdditionalInformation('url'); ?>"><?php echo __("Click here to download Boleto PDF."); ?></a></dt>
<dt class="title"><a target="_blank" href="<?php echo $this->getPaymentActionData('downloadUrl'); ?>"><?php echo __("Click here to download Boleto PDF."); ?></a></dt>
</dl>
......@@ -78,6 +78,7 @@ define(
var isValid = ko.observable(false);
return Component.extend({
isPlaceOrderActionAllowed: ko.observable(quote.billingAddress() != null),
defaults: {
template: 'Adyen_Payment/payment/oneclick-form',
recurringDetailReference: '',
......@@ -195,7 +196,7 @@ define(
isButtonActive: function () {
return self.isActive() && this.getCode() == self.isChecked() && self.isBillingAgreementChecked() && this.placeOrderAllowed();
return self.isActive() && this.getCode() == self.isChecked() && self.isBillingAgreementChecked() && this.placeOrderAllowed() && self.isPlaceOrderActionAllowed();
},
/**
* Custom place order function
......@@ -528,7 +529,6 @@ define(
if (quote.paymentMethod().method == paymentMethod()) {
return recurringDetailReference();
}
return null;
}),
placeOrderHandler: null,
......
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