Commit 18a51807 authored by Attila Kiss's avatar Attila Kiss Committed by GitHub

[PW-3131] Use generic component for alternative payment methods (#843)

* Remove methodlist

* Remove payment-details.js and move request into the service.js

* Rename retrieveAvailablePaymentMethods to getPaymentMethods

and return a promise instead of setting the methods when done

* Remove threeds2-js-utils.js and usage

* Add adyen-configuration.js

* Initialize checkout component and store it

* Use the new paymentMethods response from the component

* Move logic from CcAuthorizationDataBuilder to CheckoutDataBuilder

Remove CcAuthorizationDataBuilder.php

* require php api library version 8

Use checkout API v64 instead of 52
Use the new services from the library since v7

* Move logic from ThreeDS2DataBuilder to CheckoutDataBuilder

Remove ThreeDS2DataBuilder.php

* Simplify adyen-cc-method.js

Send the whole state.data in 1 request field instead of manually mapping

* Adjust adyen-payment-service to multi payment method structure

the onAdditionalData callback can be handled only from the main
adyenCheckout component therefore it cannot be created in one central
place but in each payment method renderer

* Adjust the AdyenCcDataAssignObserver to handle the full state data

* Update generic component to the latest bundle

* Fix 3DS1 response in CheckoutResponseValidator.php

We don't need to map fields and store each field individually

* Standardise cc and alternative payment methods observers

Validate the top level keys in state.data from the checkout component
Store additionalData fields in additionalInformation

* Use state.data validated array for the /payments request

Do not map each field in the request from additionalInformation but use
the validated state.data array as a base request

* Standardise alternative payment methods frontend

Do not create checkout components one by one but while iterating through
the paymentMethods list
Build state.data for payment methods which has no component created
Prefill component input fields with data the plugin already know
Remove unused code

* Remove line item itemId from checkout requests

Version 64 does not support it anymore

* Adjust personal details request structure to checkout version 64

Open invoice payment methods required a different request structure
before, in version 64 it has been standardised and enforced

* Make paymentMethods array observable

Update payment methods list when shipping address country is changed
Rerender payment methods list on the frontend when paymentMethods array
changes

* Update view/frontend/web/js/model/adyen-configuration.js
Co-authored-by: default avatarÁngel Campos <angel.campos@adyen.com>

* Fix code smells

* Fix removed dependency
Co-authored-by: default avatarÁngel Campos <angel.campos@adyen.com>
parent 8cf53d82
<?php
/**
* ######
* ######
* ############ ####( ###### #####. ###### ############ ############
* ############# #####( ###### #####. ###### ############# #############
* ###### #####( ###### #####. ###### ##### ###### ##### ######
* ###### ###### #####( ###### #####. ###### ##### ##### ##### ######
* ###### ###### #####( ###### #####. ###### ##### ##### ######
* ############# ############# ############# ############# ##### ######
* ############ ############ ############# ############ ##### ######
* ######
* #############
* ############
*
* Adyen Payment module (https://www.adyen.com/)
*
* Copyright (c) 2019 Adyen BV (https://www.adyen.com/)
* See LICENSE.txt for license details.
*
* Author: Adyen <magento@adyen.com>
*/
namespace Adyen\Payment\Gateway\Request;
use Magento\Payment\Gateway\Request\BuilderInterface;
use Adyen\Payment\Observer\AdyenCcDataAssignObserver;
class CcAuthorizationDataBuilder implements BuilderInterface
{
/**
* @param array $buildSubject
* @return mixed
*/
public function build(array $buildSubject)
{
/** @var \Magento\Payment\Gateway\Data\PaymentDataObject $paymentDataObject */
$paymentDataObject = \Magento\Payment\Gateway\Helper\SubjectReader::readPayment($buildSubject);
$payment = $paymentDataObject->getPayment();
$requestBody = [];
// If ccType is set use this. For bcmc you need bcmc otherwise it will fail
$requestBody['paymentMethod']['type'] = "scheme";
if ($cardNumber = $payment->getAdditionalInformation(AdyenCcDataAssignObserver::ENCRYPTED_CREDIT_CARD_NUMBER)) {
$requestBody['paymentMethod']['encryptedCardNumber'] = $cardNumber;
}
if ($expiryMonth = $payment->getAdditionalInformation(AdyenCcDataAssignObserver::ENCRYPTED_EXPIRY_MONTH)) {
$requestBody['paymentMethod']['encryptedExpiryMonth'] = $expiryMonth;
}
if ($expiryYear = $payment->getAdditionalInformation(AdyenCcDataAssignObserver::ENCRYPTED_EXPIRY_YEAR)) {
$requestBody['paymentMethod']['encryptedExpiryYear'] = $expiryYear;
}
if ($holderName = $payment->getAdditionalInformation(AdyenCcDataAssignObserver::HOLDER_NAME)) {
$requestBody['paymentMethod']['holderName'] = $holderName;
}
if ($securityCode = $payment->getAdditionalInformation(AdyenCcDataAssignObserver::ENCRYPTED_SECURITY_CODE)) {
$requestBody['paymentMethod']['encryptedSecurityCode'] = $securityCode;
}
// Remove from additional data
$payment->unsAdditionalInformation(AdyenCcDataAssignObserver::ENCRYPTED_CREDIT_CARD_NUMBER);
$payment->unsAdditionalInformation(AdyenCcDataAssignObserver::ENCRYPTED_EXPIRY_MONTH);
$payment->unsAdditionalInformation(AdyenCcDataAssignObserver::ENCRYPTED_EXPIRY_YEAR);
$payment->unsAdditionalInformation(AdyenCcDataAssignObserver::ENCRYPTED_SECURITY_CODE);
$payment->unsAdditionalInformation(AdyenCcDataAssignObserver::HOLDER_NAME);
// if installments is set and card type is credit card add it into the request
$numberOfInstallments = $payment->getAdditionalInformation(
AdyenCcDataAssignObserver::NUMBER_OF_INSTALLMENTS
) ?: 0;
$comboCardType = $payment->getAdditionalInformation(AdyenCcDataAssignObserver::COMBO_CARD_TYPE) ?: 'credit';
if ($numberOfInstallments > 0) {
$requestBody['installments']['value'] = $numberOfInstallments;
}
// if card type is debit then change the issuer type and unset the installments field
if ($comboCardType == 'debit') {
if ($selectedDebitBrand = $this->getSelectedDebitBrand($payment->getAdditionalInformation('cc_type'))) {
$requestBody['additionalData']['overwriteBrand'] = true;
$requestBody['selectedBrand'] = $selectedDebitBrand;
$requestBody['paymentMethod']['type'] = $selectedDebitBrand;
}
unset($requestBody['installments']);
}
$request['body'] = $requestBody;
return $request;
}
/**
* @param string $brand
* @return string
*/
private function getSelectedDebitBrand($brand)
{
if ($brand == 'VI') {
return 'electron';
}
if ($brand == 'MC') {
return 'maestro';
}
return null;
}
}
......@@ -23,6 +23,7 @@
namespace Adyen\Payment\Gateway\Request;
use Adyen\Payment\Observer\AdyenCcDataAssignObserver;
use Magento\Payment\Gateway\Request\BuilderInterface;
use Adyen\Payment\Observer\AdyenHppDataAssignObserver;
......@@ -77,22 +78,13 @@ class CheckoutDataBuilder implements BuilderInterface
$payment = $paymentDataObject->getPayment();
$order = $payment->getOrder();
$storeId = $order->getStoreId();
$requestBody = [];
// Initialize the request body with the validated state data
$requestBody = $payment->getAdditionalInformation(AdyenCcDataAssignObserver::STATE_DATA);
// do not send email
$order->setCanSendNewEmailFlag(false);
$requestBodyPaymentMethod['type'] = $payment->getAdditionalInformation(
AdyenHppDataAssignObserver::BRAND_CODE
);
// Additional data for payment methods with issuer list
if ($payment->getAdditionalInformation(AdyenHppDataAssignObserver::ISSUER_ID)) {
$requestBodyPaymentMethod['issuer'] = $payment->getAdditionalInformation(
AdyenHppDataAssignObserver::ISSUER_ID
);
}
$requestBody['returnUrl'] = $this->storeManager->getStore()->getBaseUrl(
\Magento\Framework\UrlInterface::URL_TYPE_LINK
) . 'adyen/process/result';
......@@ -110,46 +102,6 @@ class CheckoutDataBuilder implements BuilderInterface
$requestBody['bankAccount']['ownerName'] = $payment->getAdditionalInformation("bankAccountOwnerName");
}
// Additional data for open invoice payment
if ($payment->getAdditionalInformation("gender")) {
$order->setCustomerGender(
$this->gender->getMagentoGenderFromAdyenGender(
$payment->getAdditionalInformation("gender")
)
);
$requestBodyPaymentMethod['personalDetails']['gender'] = $payment->getAdditionalInformation("gender");
}
if ($payment->getAdditionalInformation("dob")) {
$order->setCustomerDob($payment->getAdditionalInformation("dob"));
$requestBodyPaymentMethod['personalDetails']['dateOfBirth'] = $this->adyenHelper->formatDate(
$payment->getAdditionalInformation("dob"),
'Y-m-d'
);
}
if ($payment->getAdditionalInformation("telephone")) {
$order->getBillingAddress()->setTelephone($payment->getAdditionalInformation("telephone"));
$requestBodyPaymentMethod['personalDetails']['telephoneNumber'] = $payment->getAdditionalInformation(
"telephone"
);
}
if ($payment->getAdditionalInformation("ssn")) {
$requestBodyPaymentMethod['personalDetails']['socialSecurityNumber'] =
$payment->getAdditionalInformation("ssn");
}
// Additional data for sepa direct debit
if ($payment->getAdditionalInformation("ownerName")) {
$requestBodyPaymentMethod['sepa.ownerName'] = $payment->getAdditionalInformation("ownerName");
}
if ($payment->getAdditionalInformation("ibanNumber")) {
$requestBodyPaymentMethod['sepa.ibanNumber'] = $payment->getAdditionalInformation("ibanNumber");
}
if ($this->adyenHelper->isPaymentMethodOpenInvoiceMethod(
$payment->getAdditionalInformation(AdyenHppDataAssignObserver::BRAND_CODE)
) || $this->adyenHelper->isPaymentMethodAfterpayTouchMethod(
......@@ -213,12 +165,55 @@ class CheckoutDataBuilder implements BuilderInterface
$order->setCanSendNewEmailFlag(true);
}
$requestBody['paymentMethod'] = $requestBodyPaymentMethod;
// if installments is set and card type is credit card add it into the request
$numberOfInstallments = $payment->getAdditionalInformation(
AdyenCcDataAssignObserver::NUMBER_OF_INSTALLMENTS
) ?: 0;
$comboCardType = $payment->getAdditionalInformation(AdyenCcDataAssignObserver::COMBO_CARD_TYPE) ?: 'credit';
if ($numberOfInstallments > 0) {
$requestBody['installments']['value'] = $numberOfInstallments;
}
// if card type is debit then change the issuer type and unset the installments field
if ($comboCardType == 'debit') {
if ($selectedDebitBrand = $this->getSelectedDebitBrand($payment->getAdditionalInformation('cc_type'))) {
$requestBody['additionalData']['overwriteBrand'] = true;
$requestBody['selectedBrand'] = $selectedDebitBrand;
$requestBody['paymentMethod']['type'] = $selectedDebitBrand;
}
unset($requestBody['installments']);
}
if ($this->adyenHelper->isCreditCardThreeDS2Enabled($storeId)) {
$requestBody['additionalData']['allow3DS2'] = true;
}
$requestBody['origin'] = $this->adyenHelper->getOrigin($storeId);
$requestBody['channel'] = 'web';
if (isset($requestBodyPaymentMethod)) {
$requestBody['paymentMethod'] = $requestBodyPaymentMethod;
}
$request['body'] = $requestBody;
return $request;
}
/**
* @param string $brand
* @return string
*/
private function getSelectedDebitBrand($brand)
{
if ($brand == 'VI') {
return 'electron';
}
if ($brand == 'MC') {
return 'maestro';
}
return null;
}
/**
* @param \Magento\Sales\Model\Order $order
*
......@@ -251,7 +246,6 @@ class CheckoutDataBuilder implements BuilderInterface
$formFields['lineItems'][] = [
'id' => $item->getId(),
'itemId' => $item->getId(),
'amountExcludingTax' => $formattedPriceExcludingTax,
'taxAmount' => $formattedTaxAmount,
'description' => $item->getName(),
......@@ -301,7 +295,7 @@ class CheckoutDataBuilder implements BuilderInterface
}
$formFields['lineItems'][] = [
'itemId' => 'shippingCost',
'id' => 'shippingCost',
'amountExcludingTax' => $formattedPriceExcludingTax,
'taxAmount' => $formattedTaxAmount,
'description' => $order->getShippingDescription(),
......
......@@ -67,9 +67,10 @@ class CheckoutResponseValidator extends AbstractValidator
$payment->setAdditionalInformation('3dActive', false);
$isValid = true;
$errorMessages = [];
$resultCode = $response['resultCode'];
// validate result
if (!empty($resultCode)) {
if (!empty($response['resultCode'])) {
$resultCode = $response['resultCode'];
$payment->setAdditionalInformation('resultCode', $resultCode);
if (!empty($response['action'])) {
......@@ -149,59 +150,8 @@ class CheckoutResponseValidator extends AbstractValidator
case "ChallengeShopper":
case "PresentToShopper":
case 'Pending':
// nothing extra
break;
case "RedirectShopper":
// todo check if needed
if (
isset($response['redirect']['data']['PaReq']) &&
isset($response['redirect']['data']['MD'])
) {
$paReq = null;
$md = null;
$payment->setAdditionalInformation('3dActive', true);
if (!empty($response['redirect']['data']['PaReq'])) {
$paReq = $response['redirect']['data']['PaReq'];
}
if (!empty($response['redirect']['data']['MD'])) {
$md = $response['redirect']['data']['MD'];
}
if ($paReq && $md && $redirectUrl && $paymentData && $redirectMethod) {
$payment->setAdditionalInformation('redirectUrl', $redirectUrl);
$payment->setAdditionalInformation('redirectMethod', $redirectMethod);
$payment->setAdditionalInformation('paRequest', $paReq);
$payment->setAdditionalInformation('md', $md);
$payment->setAdditionalInformation('paymentData', $paymentData);
} else {
$isValid = false;
$errorMsg = __('3D secure is not valid.');
$this->adyenLogger->error($errorMsg);
$errorMessages[] = $errorMsg;
}
// otherwise it is an alternative payment method which only requires the
// redirect url to be present
} else {
// Flag to show we are in the checkoutAPM flow
$payment->setAdditionalInformation('checkoutAPM', true);
if (!empty($response['details'])) {
$payment->setAdditionalInformation('details', $response['details']);
}
if ($redirectUrl && $paymentData && $redirectMethod) {
$payment->setAdditionalInformation('redirectUrl', $redirectUrl);
$payment->setAdditionalInformation('redirectMethod', $redirectMethod);
$payment->setAdditionalInformation('paymentData', $paymentData);
} else {
$isValid = false;
$errorMsg = __('Payment method is not valid.');
$this->adyenLogger->error($errorMsg);
$errorMessages[] = $errorMsg;
}
}
// nothing extra
break;
case "Refused":
$errorMsg = __('The payment is REFUSED.');
......
......@@ -116,51 +116,24 @@ class Requests extends AbstractHelper
// In case of virtual product and guest checkout there is a workaround to get the guest's email address
if (!empty($additionalData['guestEmail'])) {
if ($this->adyenHelper->isPaymentMethodOpenInvoiceMethod($paymentMethod) &&
!$this->adyenHelper->isPaymentMethodAfterpayTouchMethod($paymentMethod)
) {
$request['paymentMethod']['personalDetails']['shopperEmail'] = $additionalData['guestEmail'];
} else {
$request['shopperEmail'] = $additionalData['guestEmail'];
}
$request['shopperEmail'] = $additionalData['guestEmail'];
}
if (!empty($billingAddress)) {
// Openinvoice (klarna and afterpay BUT not afterpay touch) methods requires different request format
if ($this->adyenHelper->isPaymentMethodOpenInvoiceMethod($paymentMethod) &&
!$this->adyenHelper->isPaymentMethodAfterpayTouchMethod($paymentMethod)
) {
if ($customerEmail = $billingAddress->getEmail()) {
$request['paymentMethod']['personalDetails']['shopperEmail'] = $customerEmail;
}
if ($customerTelephone = trim($billingAddress->getTelephone())) {
$request['paymentMethod']['personalDetails']['telephoneNumber'] = $customerTelephone;
}
if ($firstName = $billingAddress->getFirstname()) {
$request['paymentMethod']['personalDetails']['firstName'] = $firstName;
}
if ($lastName = $billingAddress->getLastname()) {
$request['paymentMethod']['personalDetails']['lastName'] = $lastName;
}
} else {
if ($customerEmail = $billingAddress->getEmail()) {
$request['shopperEmail'] = $customerEmail;
}
if ($customerEmail = $billingAddress->getEmail()) {
$request['shopperEmail'] = $customerEmail;
}
if ($customerTelephone = trim($billingAddress->getTelephone())) {
$request['telephoneNumber'] = $customerTelephone;
}
if ($customerTelephone = trim($billingAddress->getTelephone())) {
$request['telephoneNumber'] = $customerTelephone;
}
if ($firstName = $billingAddress->getFirstname()) {
$request['shopperName']['firstName'] = $firstName;
}
if ($firstName = $billingAddress->getFirstname()) {
$request['shopperName']['firstName'] = $firstName;
}
if ($lastName = $billingAddress->getLastname()) {
$request['shopperName']['lastName'] = $lastName;
}
if ($lastName = $billingAddress->getLastname()) {
$request['shopperName']['lastName'] = $lastName;
}
if ($countryId = $billingAddress->getCountryId()) {
......@@ -325,38 +298,6 @@ class Requests extends AbstractHelper
return $request;
}
/**
* @param array $request
* @param $additionalData
* @param $storeId
* @return array
*/
public function buildThreeDS2Data($additionalData, $storeId, $request = [])
{
if ($this->adyenHelper->isCreditCardThreeDS2Enabled($storeId)) {
$request['additionalData']['allow3DS2'] = true;
$request['origin'] = $this->adyenHelper->getOrigin($storeId);
$request['channel'] = 'web';
$request['browserInfo']['screenWidth'] = $additionalData[AdyenCcDataAssignObserver::SCREEN_WIDTH];
$request['browserInfo']['screenHeight'] = $additionalData[AdyenCcDataAssignObserver::SCREEN_HEIGHT];
$request['browserInfo']['colorDepth'] = $additionalData[AdyenCcDataAssignObserver::SCREEN_COLOR_DEPTH];
$request['browserInfo']['timeZoneOffset'] = $additionalData[AdyenCcDataAssignObserver::TIMEZONE_OFFSET];
$request['browserInfo']['language'] = $additionalData[AdyenCcDataAssignObserver::LANGUAGE];
if ($javaEnabled = $additionalData[AdyenCcDataAssignObserver::JAVA_ENABLED]) {
$request['browserInfo']['javaEnabled'] = $javaEnabled;
} else {
$request['browserInfo']['javaEnabled'] = false;
}
} else {
$request['additionalData']['allow3DS2'] = false;
$request['origin'] = $this->adyenHelper->getOrigin($storeId);
$request['channel'] = 'web';
}
return $request;
}
/**
* @param array $request
* @return array
......
......@@ -73,6 +73,9 @@ class AdyenGenericConfigProvider implements ConfigProviderInterface
$config['payment']['adyen']['checkoutEnvironment'] = $this->adyenHelper->getCheckoutEnvironment(
$this->storeManager->getStore()->getId()
);
$config['payment']['adyen']['locale'] = $this->adyenHelper->getStoreLocale(
$this->storeManager->getStore()->getId()
);
return $config;
}
......
......@@ -15,7 +15,7 @@
*
* Adyen Payment module (https://www.adyen.com/)
*
* Copyright (c) 2015 Adyen BV (https://www.adyen.com/)
* Copyright (c) 2020 Adyen BV (https://www.adyen.com/)
* See LICENSE.txt for license details.
*
* Author: Adyen <magento@adyen.com>
......@@ -26,77 +26,92 @@ namespace Adyen\Payment\Observer;
use Magento\Framework\Event\Observer;
use Magento\Payment\Observer\AbstractDataAssignObserver;
use Magento\Quote\Api\Data\PaymentInterface;
use \Adyen\Service\Validator\CheckoutStateDataValidator;
use \Adyen\Service\Validator\DataArrayValidator;
class AdyenCcDataAssignObserver extends AbstractDataAssignObserver
{
const CC_TYPE = 'cc_type';
const NUMBER_OF_INSTALLMENTS = 'number_of_installments';
const STORE_CC = 'store_cc';
const ENCRYPTED_CREDIT_CARD_NUMBER = 'number';
const ENCRYPTED_SECURITY_CODE = 'cvc';
const ENCRYPTED_EXPIRY_MONTH = 'expiryMonth';
const ENCRYPTED_EXPIRY_YEAR = 'expiryYear';
const HOLDER_NAME = 'holderName';
const VARIANT = 'variant';
const JAVA_ENABLED = 'java_enabled';
const SCREEN_COLOR_DEPTH = 'screen_color_depth';
const SCREEN_WIDTH = 'screen_width';
const SCREEN_HEIGHT = 'screen_height';
const TIMEZONE_OFFSET = 'timezone_offset';
const LANGUAGE = 'language';
const GUEST_EMAIL = 'guestEmail';
const COMBO_CARD_TYPE = 'combo_card_type';
const STATE_DATA = 'stateData';
/**
* Approved root level keys from additional data array
*
* @var array
*/
protected $additionalInformationList = [
self::CC_TYPE,
self::NUMBER_OF_INSTALLMENTS,
self::STORE_CC,
self::ENCRYPTED_CREDIT_CARD_NUMBER,
self::ENCRYPTED_SECURITY_CODE,
self::ENCRYPTED_EXPIRY_MONTH,
self::ENCRYPTED_EXPIRY_YEAR,
self::HOLDER_NAME,
self::VARIANT,
self::JAVA_ENABLED,
self::SCREEN_COLOR_DEPTH,
self::SCREEN_WIDTH,
self::SCREEN_HEIGHT,
self::TIMEZONE_OFFSET,
self::LANGUAGE,
private static $approvedAdditionalDataKeys = [
self::STATE_DATA,
self::GUEST_EMAIL,
self::COMBO_CARD_TYPE
self::COMBO_CARD_TYPE,
self::NUMBER_OF_INSTALLMENTS
];
/**
* @var CheckoutStateDataValidator
*/
protected $checkoutStateDataValidator;
/**
* AdyenCcDataAssignObserver constructor.
*
* @param CheckoutStateDataValidator $checkoutStateDataValidator
*/
public function __construct(
CheckoutStateDataValidator $checkoutStateDataValidator
) {
$this->checkoutStateDataValidator = $checkoutStateDataValidator;
}
/**
* @param Observer $observer
* @return void
*/
public function execute(Observer $observer)
{
// Get request fields
$data = $this->readDataArgument($observer);
// Get additional data array
$additionalData = $data->getData(PaymentInterface::KEY_ADDITIONAL_DATA);
if (!is_array($additionalData)) {
return;
}
$paymentInfo = $this->readPaymentModelArgument($observer);
// Get a validated additional data array
$additionalData = DataArrayValidator::getArrayOnlyWithApprovedKeys(
$additionalData,
self::$approvedAdditionalDataKeys
);
// set ccType
if (!empty($additionalData['cc_type'])) {
$paymentInfo->setCcType($additionalData['cc_type']);
// json decode state data
$stateData = [];
if (!empty($additionalData[self::STATE_DATA])) {
$stateData = json_decode($additionalData[self::STATE_DATA], true);
}
// Get validated state data array
if (!empty($stateData)) {
$stateData = $this->checkoutStateDataValidator->getValidatedAdditionalData(
$stateData
);
}
// Replace state data with the decoded and validated state data
$additionalData[self::STATE_DATA] = $stateData;
// Set additional data in the payment
$paymentInfo = $this->readPaymentModelArgument($observer);
foreach ($additionalData as $key => $data) {
$paymentInfo->setAdditionalInformation($key, $data);
}
foreach ($this->additionalInformationList as $additionalInformationKey) {
if (isset($additionalData[$additionalInformationKey])) {
$paymentInfo->setAdditionalInformation(
$additionalInformationKey,
$additionalData[$additionalInformationKey]
);
}
// set ccType
if (!empty($additionalData[self::CC_TYPE])) {
$paymentInfo->setCcType($additionalData[self::CC_TYPE]);
}
}
}
......@@ -15,7 +15,7 @@
*
* Adyen Payment module (https://www.adyen.com/)
*
* Copyright (c) 2015 Adyen BV (https://www.adyen.com/)
* Copyright (c) 2020 Adyen BV (https://www.adyen.com/)
* See LICENSE.txt for license details.
*
* Author: Adyen <magento@adyen.com>
......@@ -23,6 +23,8 @@
namespace Adyen\Payment\Observer;
use Adyen\Service\Validator\CheckoutStateDataValidator;
use Adyen\Service\Validator\DataArrayValidator;
use Magento\Framework\Event\Observer;
use Magento\Payment\Observer\AbstractDataAssignObserver;
use Magento\Quote\Api\Data\PaymentInterface;
......@@ -30,62 +32,84 @@ use Magento\Quote\Api\Data\PaymentInterface;
class AdyenHppDataAssignObserver extends AbstractDataAssignObserver
{
const BRAND_CODE = 'brand_code';
const ISSUER_ID = 'issuer_id';
const GENDER = 'gender';
const DOB = 'dob';
const TELEPHONE = 'telephone';
const DF_VALUE = 'df_value';
const SSN = 'ssn';
const OWNER_NAME = 'ownerName';
const BANK_ACCOUNT_OWNER_NAME = 'bankAccountOwnerName';
const IBAN_NUMBER = 'ibanNumber';
const BANK_ACCOUNT_NUMBER = 'bankAccountNumber';
const BANK_LOCATIONID = 'bankLocationId';
const GUEST_EMAIL = 'guestEmail';
const STATE_DATA = 'stateData';
/**
* Approved root level keys from additional data array
*
* @var array
*/
protected $additionalInformationList = [
private static $approvedAdditionalDataKeys = [
self::BRAND_CODE,
self::ISSUER_ID,
self::GENDER,
self::DOB,
self::TELEPHONE,
self::DF_VALUE,
self::SSN,
self::OWNER_NAME,
self::BANK_ACCOUNT_OWNER_NAME,
self::IBAN_NUMBER,
self::BANK_ACCOUNT_NUMBER,
self::BANK_LOCATIONID
self::GUEST_EMAIL,
self::STATE_DATA
];
/**
* @var CheckoutStateDataValidator
*/
protected $checkoutStateDataValidator;
/**
* AdyenHppDataAssignObserver constructor.
*
* @param CheckoutStateDataValidator $checkoutStateDataValidator
*/
public function __construct(
CheckoutStateDataValidator $checkoutStateDataValidator
) {
$this->checkoutStateDataValidator = $checkoutStateDataValidator;
}
/**
* @param Observer $observer
* @return void
*/
public function execute(Observer $observer)
{
// Get request fields
$data = $this->readDataArgument($observer);
// Get additional data array
$additionalData = $data->getData(PaymentInterface::KEY_ADDITIONAL_DATA);
if (!is_array($additionalData)) {
return;
}
$paymentInfo = $this->readPaymentModelArgument($observer);
// Get a validated additional data array
$additionalData = DataArrayValidator::getArrayOnlyWithApprovedKeys(
$additionalData,
self::$approvedAdditionalDataKeys
);
if (isset($additionalData[self::BRAND_CODE])) {
$paymentInfo->setCcType($additionalData[self::BRAND_CODE]);
// json decode state data
$stateData = [];
if (!empty($additionalData[self::STATE_DATA])) {
$stateData = json_decode($additionalData[self::STATE_DATA], true);
}
// Get validated state data array
if (!empty($stateData)) {
$stateData = $this->checkoutStateDataValidator->getValidatedAdditionalData(
$stateData
);
}
// Replace state data with the decoded and validated state data
$additionalData[self::STATE_DATA] = $stateData;
// Set additional data in the payment
$paymentInfo = $this->readPaymentModelArgument($observer);
foreach ($additionalData as $key => $data) {
$paymentInfo->setAdditionalInformation($key, $data);
}
foreach ($this->additionalInformationList as $additionalInformationKey) {
if (isset($additionalData[$additionalInformationKey])) {
$paymentInfo->setAdditionalInformation(
$additionalInformationKey,
$additionalData[$additionalInformationKey]
);
}
// set ccType
if (!empty($additionalData[self::BRAND_CODE])) {
$paymentInfo->setCcType($additionalData[self::BRAND_CODE]);
}
}
}
......@@ -14,7 +14,7 @@
}
],
"require": {
"adyen/php-api-library": "^6.3",
"adyen/php-api-library": "^8",
"magento/framework": ">=101.0.8 <102 || >=102.0.1",
"magento/module-vault": "101.*",
"magento/module-paypal": ">=100.2.6",
......
......@@ -557,9 +557,8 @@
<item name="payment" xsi:type="string">Adyen\Payment\Gateway\Request\PaymentDataBuilder</item>
<item name="browserinfo" xsi:type="string">Adyen\Payment\Gateway\Request\BrowserInfoDataBuilder</item>
<item name="recurring" xsi:type="string">Adyen\Payment\Gateway\Request\RecurringDataBuilder</item>
<item name="transaction" xsi:type="string">Adyen\Payment\Gateway\Request\CcAuthorizationDataBuilder</item>
<item name="transaction" xsi:type="string">Adyen\Payment\Gateway\Request\CheckoutDataBuilder</item>
<item name="vault" xsi:type="string">Adyen\Payment\Gateway\Request\VaultDataBuilder</item>
<item name="threeds2" xsi:type="string">Adyen\Payment\Gateway\Request\ThreeDS2DataBuilder</item>
</argument>
</arguments>
</virtualType>
......@@ -606,7 +605,7 @@
<item name="browserinfo" xsi:type="string">Adyen\Payment\Gateway\Request\BrowserInfoDataBuilder</item>
<item name="recurring" xsi:type="string">Adyen\Payment\Gateway\Request\RecurringDataBuilder</item>
<item name="oneclick" xsi:type="string">Adyen\Payment\Gateway\Request\OneclickAuthorizationDataBuilder</item>
<item name="threeds2" xsi:type="string">Adyen\Payment\Gateway\Request\ThreeDS2DataBuilder</item>
<item name="transaction" xsi:type="string">Adyen\Payment\Gateway\Request\CheckoutDataBuilder</item>
<item name="redirect" xsi:type="string">Adyen\Payment\Gateway\Request\RedirectDataBuilder</item>
</argument>
</arguments>
......
This diff is collapsed.
This diff is collapsed.
<?php
/**
* ######
* ######
......@@ -15,59 +14,29 @@
*
* Adyen Payment module (https://www.adyen.com/)
*
* Copyright (c) 2019 Adyen BV (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\Gateway\Request;
use Magento\Payment\Gateway\Request\BuilderInterface;
class ThreeDS2DataBuilder implements BuilderInterface
{
/**
* @var \Magento\Framework\App\State
*/
private $appState;
/**
* @var \Adyen\Payment\Helper\Requests
*/
private $adyenRequestsHelper;
/**
* ThreeDS2DataBuilder constructor.
*
* @param \Magento\Framework\Model\Context $context
* @param \Adyen\Payment\Helper\Requests $adyenRequestsHelper
*/
public function __construct(
\Magento\Framework\Model\Context $context,
\Adyen\Payment\Helper\Requests $adyenRequestsHelper
) {
$this->appState = $context->getAppState();
$this->adyenRequestsHelper = $adyenRequestsHelper;
define(
[
],
function () {
'use strict';
return {
getOriginKey: function () {
return window.checkoutConfig.payment.adyen.originKey;
},
showLogo: function () {
return window.checkoutConfig.payment.adyen.showLogo;
},
getLocale: function () {
return window.checkoutConfig.payment.adyen.locale;
},
getCheckoutEnvironment: function () {
return window.checkoutConfig.payment.adyen.checkoutEnvironment;
},
};
}
/**
* @param array $buildSubject
* @return array
* @throws \Magento\Framework\Exception\LocalizedException
*/
public function build(array $buildSubject)
{
/** @var \Magento\Payment\Gateway\Data\PaymentDataObject $paymentDataObject */
$paymentDataObject = \Magento\Payment\Gateway\Helper\SubjectReader::readPayment($buildSubject);
$payment = $paymentDataObject->getPayment();
$order = $paymentDataObject->getOrder();
$additionalInformation = $payment->getAdditionalInformation();
$request['body'] = $this->adyenRequestsHelper->buildThreeDS2Data(
$additionalInformation,
$order->getStoreId(),
[]
);
return $request;
}
}
);
/**
* Copyright © 2015 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
define(
[
'ko'
],
function (ko) {
'use strict';
return ko.observableArray([]);
}
);
......@@ -6,74 +6,86 @@ define(
[
'underscore',
'Magento_Checkout/js/model/quote',
'Adyen_Payment/js/model/adyen-method-list',
'Magento_Customer/js/model/customer',
'Magento_Checkout/js/model/url-builder',
'mage/storage'
'mage/storage',
'Adyen_Payment/js/bundle',
'ko'
],
function (_, quote, methodList, customer, urlBuilder, storage) {
function(
_,
quote,
customer,
urlBuilder,
storage,
adyenComponent,
ko
) {
'use strict';
return {
paymentMethods: ko.observable({}),
/**
* Populate the list of payment methods
* @param {Array} methods
*/
setPaymentMethods: function (methods) {
methodList(methods);
},
/**
* Get the list of available payment methods.
* @returns {Array}
*/
getAvailablePaymentMethods: function () {
return methodList();
},
/**
* Retrieve the list of available payment methods from the server
* Retrieve the list of available payment methods from Adyen
*/
retrieveAvailablePaymentMethods: function (callback) {
var self = this;
retrievePaymentMethods: function() {
// url for guest users
var serviceUrl = urlBuilder.createUrl(
'/guest-carts/:cartId/retrieve-adyen-payment-methods', {
cartId: quote.getQuoteId(),
});
// retrieve payment methods
var serviceUrl,
payload;
// url for logged in users
if (customer.isLoggedIn()) {
serviceUrl = urlBuilder.createUrl('/carts/mine/retrieve-adyen-payment-methods', {});
} else {
serviceUrl = urlBuilder.createUrl('/guest-carts/:cartId/retrieve-adyen-payment-methods', {
cartId: quote.getQuoteId()
});
serviceUrl = urlBuilder.createUrl(
'/carts/mine/retrieve-adyen-payment-methods', {});
}
payload = {
// Construct payload for the retrieve payment methods request
var payload = {
cartId: quote.getQuoteId(),
shippingAddress: quote.shippingAddress()
shippingAddress: quote.shippingAddress(),
};
storage.post(
return storage.post(
serviceUrl,
JSON.stringify(payload)
).done(
function (response) {
self.setPaymentMethods(response);
if (callback) {
callback();
}
}
).fail(
function () {
self.setPaymentMethods([]);
}
)
JSON.stringify(payload),
);
},
getOrderPaymentStatus: function (orderId) {
var serviceUrl = urlBuilder.createUrl('/adyen/orders/:orderId/payment-status', {
orderId: orderId
});
getPaymentMethods: function() {
return this.paymentMethods;
},
setPaymentMethods: function(paymentMethods) {
this.paymentMethods(paymentMethods);
},
getOrderPaymentStatus: function(orderId) {
var serviceUrl = urlBuilder.createUrl(
'/adyen/orders/:orderId/payment-status', {
orderId: orderId,
});
return storage.get(serviceUrl);
},
/**
* The results that the components returns in the onComplete callback needs to be sent to the
* backend to the /adyen/paymentDetails endpoint and based on the response render a new
* component or place the order (validateThreeDS2OrPlaceOrder)
* @param response
*/
paymentDetails: function(data) {
var payload = {
'payload': JSON.stringify(data),
};
var serviceUrl = urlBuilder.createUrl('/adyen/paymentDetails',
{});
return storage.post(
serviceUrl,
JSON.stringify(payload),
true,
);
}
};
}
},
);
/**
* Copyright © 2015 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
define(
[
'Magento_Checkout/js/model/url-builder',
'mage/storage'
],
function (urlBuilder, storage) {
'use strict';
return {
/**
* The results that the components returns in the onComplete callback needs to be sent to the
* backend to the /adyen/paymentDetails endpoint and based on the response render a new
* component or place the order (validateThreeDS2OrPlaceOrder)
* @param response
*/
process: function (data) {
var payload = {
"payload": JSON.stringify(data)
};
var serviceUrl = urlBuilder.createUrl('/adyen/paymentDetails', {});
return storage.post(
serviceUrl,
JSON.stringify(payload),
true
);
}
};
}
);
!function(e,n){"object"===typeof exports&&"object"===typeof module?module.exports=n():"function"===typeof define&&define.amd?define([],n):"object"===typeof exports?exports.ThreedDS2Utils=n():e.ThreedDS2Utils=n()}(this,function(){return function(e){var n={};function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}return t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:r})},t.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,n){if(1&n&&(e=t(e)),8&n)return e;if(4&n&&"object"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(t.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var o in e)t.d(r,o,function(n){return e[n]}.bind(null,o));return r},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},t.p="",t(t.s=0)}([function(e,n,t){"use strict";t.r(n);var r={container:void 0},o={"01":["250px","400px"],"02":["390px","400px"],"03":["500px","600px"],"04":["600px","400px"],"05":["100%","100%"]};function a(e){return o.hasOwnProperty(e)?e:"01"}var i={createIframe:function(e,n){var t=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"0",o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"0",a=arguments.length>4?arguments[4]:void 0;if(!n||0===n.length)throw new Error("Name parameter missing for iframe");e instanceof HTMLElement?r.container=e:r.container=document.body;var i=document.createElement("iframe");i.classList.add(n+"Class"),i.width=t,i.height=o,i.name=n,i.setAttribute("frameborder","0"),i.setAttribute("border","0");var d=document.createTextNode("<p>Your browser does not support iframes.</p>");return i.appendChild(d),r.container.appendChild(i),function(e,n){e.attachEvent?e.attachEvent("onload",function(){n&&"function"===typeof n&&n(e.contentWindow)}):e.onload=function(){n&&"function"===typeof n&&n(e.contentWindow)}}(i,a),i},createForm:function(e,n,t,r,o){if(!e||!n||!t||!r||!o)throw new Error("Not all required parameters provided for form creation");if(0===e.length||0===n.length||0===t.length||0===r.length||0===o.length)throw new Error("Not all required parameters have suitable values");var a=document.createElement("form");a.style.display="none",a.name=e,a.action=n,a.method="POST",a.target=t;var i=document.createElement("input");return i.name=r,i.value=o,a.appendChild(i),a},getBrowserInfo:function(){var e=window&&window.screen?window.screen.width:"",n=window&&window.screen?window.screen.height:"",t=window&&window.screen?window.screen.colorDepth:"",r=window&&window.navigator?window.navigator.userAgent:"",o=!(!window||!window.navigator)&&navigator.javaEnabled(),a="";return window&&window.navigator&&(a=window.navigator.language?window.navigator.language:window.navigator.browserLanguage),{screenWidth:e,screenHeight:n,colorDepth:t,userAgent:r,timeZoneOffset:(new Date).getTimezoneOffset(),language:a,javaEnabled:o}},base64Url:{encode:function(e){var n=window.btoa(e).split("=")[0];return n=(n=n.replace("/+/g","-")).replace("///g","_")},decode:function(e){var n=e;switch((n=(n=n.replace("/-/g","+")).replace("/_/g","/")).length%4){case 0:break;case 2:n+="==";break;case 3:n+="=";break;default:window.console&&window.console.log&&window.console.log("### base64url::decodeBase64URL:: Illegal base64url string!")}try{return window.atob(n)}catch(e){throw new Error(e)}}},config:{challengeWindowSizes:o,validateChallengeWindowSize:a,getChallengeWindowSize:function(e){return o[a(e)]},THREEDS_METHOD_TIMEOUT:1e4,CHALLENGE_TIMEOUT:6e5}};n.default=i}]).default});
\ No newline at end of file
......@@ -24,11 +24,19 @@
define(
[
'uiComponent',
'Magento_Checkout/js/model/payment/renderer-list'
'Magento_Checkout/js/model/payment/renderer-list',
'Adyen_Payment/js/model/adyen-payment-service',
'Adyen_Payment/js/model/adyen-configuration',
'Magento_Checkout/js/model/quote',
'Magento_Checkout/js/model/full-screen-loader',
],
function (
Component,
rendererList
rendererList,
adyenPaymentService,
adyenConfiguration,
quote,
fullScreenLoader
) {
'use strict';
rendererList.push(
......@@ -66,6 +74,25 @@ define(
initialize: function () {
this._super();
var shippingAddressCountry = "";
quote.shippingAddress.subscribe(function(address) {
// In case the country hasn't changed don't retrieve new payment methods
if (shippingAddressCountry === quote.shippingAddress().countryId) {
return;
}
shippingAddressCountry = quote.shippingAddress().countryId;
fullScreenLoader.startLoader();
// Retrieve adyen payment methods
adyenPaymentService.retrievePaymentMethods().done(function(paymentMethods) {
paymentMethods = JSON.parse(paymentMethods);
adyenPaymentService.setPaymentMethods(paymentMethods);
fullScreenLoader.stopLoader();
}).fail(function() {
})
});
if (this.isGooglePayEnabled()) {
var googlepayscript = document.createElement('script');
googlepayscript.src = "https://pay.google.com/gp/p/js/pay.js";
......
......@@ -33,13 +33,11 @@ define(
'uiLayout',
'Magento_Ui/js/model/messages',
'mage/url',
'Adyen_Payment/js/threeds2-js-utils',
'Magento_Checkout/js/model/full-screen-loader',
'Magento_Paypal/js/action/set-payment-method',
'Magento_Checkout/js/model/url-builder',
'mage/storage',
'Magento_Checkout/js/action/place-order',
'Adyen_Payment/js/model/payment-details',
'Magento_Checkout/js/model/error-processor',
'Adyen_Payment/js/model/adyen-payment-service',
'Adyen_Payment/js/bundle',
......@@ -58,13 +56,11 @@ function(
layout,
Messages,
url,
threeDS2Utils,
fullScreenLoader,
setPaymentMethodAction,
urlBuilder,
storage,
placeOrderAction,
paymentDetails,
errorProcessor,
adyenPaymentService,
AdyenComponent
......@@ -347,7 +343,7 @@ function(
onComplete: function(result) {
var request = result.data;
request.orderId = orderId;
paymentDetails.process(request).
adyenPaymentService.paymentDetails(request).
done(function(responseJSON) {
self.validateThreeDS2OrPlaceOrder(responseJSON,
orderId);
......@@ -386,7 +382,7 @@ function(
fullScreenLoader.startLoader();
var request = result.data;
request.orderId = orderId;
paymentDetails.process(request).
adyenPaymentService.paymentDetails(request).
done(function(responseJSON) {
self.validateThreeDS2OrPlaceOrder(responseJSON,
orderId);
......@@ -413,7 +409,8 @@ function(
*/
getData: function() {
var self = this;
var browserInfo = threeDS2Utils.getBrowserInfo();
// todo use state.data
var browserInfo = [];
return {
'method': self.method,
......
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