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 @@ ...@@ -23,6 +23,7 @@
namespace Adyen\Payment\Gateway\Request; namespace Adyen\Payment\Gateway\Request;
use Adyen\Payment\Observer\AdyenCcDataAssignObserver;
use Magento\Payment\Gateway\Request\BuilderInterface; use Magento\Payment\Gateway\Request\BuilderInterface;
use Adyen\Payment\Observer\AdyenHppDataAssignObserver; use Adyen\Payment\Observer\AdyenHppDataAssignObserver;
...@@ -77,22 +78,13 @@ class CheckoutDataBuilder implements BuilderInterface ...@@ -77,22 +78,13 @@ class CheckoutDataBuilder implements BuilderInterface
$payment = $paymentDataObject->getPayment(); $payment = $paymentDataObject->getPayment();
$order = $payment->getOrder(); $order = $payment->getOrder();
$storeId = $order->getStoreId(); $storeId = $order->getStoreId();
$requestBody = [];
// Initialize the request body with the validated state data
$requestBody = $payment->getAdditionalInformation(AdyenCcDataAssignObserver::STATE_DATA);
// do not send email // do not send email
$order->setCanSendNewEmailFlag(false); $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( $requestBody['returnUrl'] = $this->storeManager->getStore()->getBaseUrl(
\Magento\Framework\UrlInterface::URL_TYPE_LINK \Magento\Framework\UrlInterface::URL_TYPE_LINK
) . 'adyen/process/result'; ) . 'adyen/process/result';
...@@ -110,46 +102,6 @@ class CheckoutDataBuilder implements BuilderInterface ...@@ -110,46 +102,6 @@ class CheckoutDataBuilder implements BuilderInterface
$requestBody['bankAccount']['ownerName'] = $payment->getAdditionalInformation("bankAccountOwnerName"); $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( if ($this->adyenHelper->isPaymentMethodOpenInvoiceMethod(
$payment->getAdditionalInformation(AdyenHppDataAssignObserver::BRAND_CODE) $payment->getAdditionalInformation(AdyenHppDataAssignObserver::BRAND_CODE)
) || $this->adyenHelper->isPaymentMethodAfterpayTouchMethod( ) || $this->adyenHelper->isPaymentMethodAfterpayTouchMethod(
...@@ -213,12 +165,55 @@ class CheckoutDataBuilder implements BuilderInterface ...@@ -213,12 +165,55 @@ class CheckoutDataBuilder implements BuilderInterface
$order->setCanSendNewEmailFlag(true); $order->setCanSendNewEmailFlag(true);
} }
// 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; $requestBody['paymentMethod'] = $requestBodyPaymentMethod;
}
$request['body'] = $requestBody; $request['body'] = $requestBody;
return $request; 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 * @param \Magento\Sales\Model\Order $order
* *
...@@ -251,7 +246,6 @@ class CheckoutDataBuilder implements BuilderInterface ...@@ -251,7 +246,6 @@ class CheckoutDataBuilder implements BuilderInterface
$formFields['lineItems'][] = [ $formFields['lineItems'][] = [
'id' => $item->getId(), 'id' => $item->getId(),
'itemId' => $item->getId(),
'amountExcludingTax' => $formattedPriceExcludingTax, 'amountExcludingTax' => $formattedPriceExcludingTax,
'taxAmount' => $formattedTaxAmount, 'taxAmount' => $formattedTaxAmount,
'description' => $item->getName(), 'description' => $item->getName(),
...@@ -301,7 +295,7 @@ class CheckoutDataBuilder implements BuilderInterface ...@@ -301,7 +295,7 @@ class CheckoutDataBuilder implements BuilderInterface
} }
$formFields['lineItems'][] = [ $formFields['lineItems'][] = [
'itemId' => 'shippingCost', 'id' => 'shippingCost',
'amountExcludingTax' => $formattedPriceExcludingTax, 'amountExcludingTax' => $formattedPriceExcludingTax,
'taxAmount' => $formattedTaxAmount, 'taxAmount' => $formattedTaxAmount,
'description' => $order->getShippingDescription(), 'description' => $order->getShippingDescription(),
......
...@@ -67,9 +67,10 @@ class CheckoutResponseValidator extends AbstractValidator ...@@ -67,9 +67,10 @@ class CheckoutResponseValidator extends AbstractValidator
$payment->setAdditionalInformation('3dActive', false); $payment->setAdditionalInformation('3dActive', false);
$isValid = true; $isValid = true;
$errorMessages = []; $errorMessages = [];
$resultCode = $response['resultCode'];
// validate result // validate result
if (!empty($resultCode)) { if (!empty($response['resultCode'])) {
$resultCode = $response['resultCode'];
$payment->setAdditionalInformation('resultCode', $resultCode); $payment->setAdditionalInformation('resultCode', $resultCode);
if (!empty($response['action'])) { if (!empty($response['action'])) {
...@@ -149,59 +150,8 @@ class CheckoutResponseValidator extends AbstractValidator ...@@ -149,59 +150,8 @@ class CheckoutResponseValidator extends AbstractValidator
case "ChallengeShopper": case "ChallengeShopper":
case "PresentToShopper": case "PresentToShopper":
case 'Pending': case 'Pending':
// nothing extra
break;
case "RedirectShopper": case "RedirectShopper":
// todo check if needed // nothing extra
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;
}
}
break; break;
case "Refused": case "Refused":
$errorMsg = __('The payment is REFUSED.'); $errorMsg = __('The payment is REFUSED.');
......
...@@ -116,36 +116,10 @@ class Requests extends AbstractHelper ...@@ -116,36 +116,10 @@ class Requests extends AbstractHelper
// In case of virtual product and guest checkout there is a workaround to get the guest's email address // In case of virtual product and guest checkout there is a workaround to get the guest's email address
if (!empty($additionalData['guestEmail'])) { 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)) { 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()) { if ($customerEmail = $billingAddress->getEmail()) {
$request['shopperEmail'] = $customerEmail; $request['shopperEmail'] = $customerEmail;
} }
...@@ -161,7 +135,6 @@ class Requests extends AbstractHelper ...@@ -161,7 +135,6 @@ class Requests extends AbstractHelper
if ($lastName = $billingAddress->getLastname()) { if ($lastName = $billingAddress->getLastname()) {
$request['shopperName']['lastName'] = $lastName; $request['shopperName']['lastName'] = $lastName;
} }
}
if ($countryId = $billingAddress->getCountryId()) { if ($countryId = $billingAddress->getCountryId()) {
$request['countryCode'] = $countryId; $request['countryCode'] = $countryId;
...@@ -325,38 +298,6 @@ class Requests extends AbstractHelper ...@@ -325,38 +298,6 @@ class Requests extends AbstractHelper
return $request; 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 * @param array $request
* @return array * @return array
......
...@@ -73,6 +73,9 @@ class AdyenGenericConfigProvider implements ConfigProviderInterface ...@@ -73,6 +73,9 @@ class AdyenGenericConfigProvider implements ConfigProviderInterface
$config['payment']['adyen']['checkoutEnvironment'] = $this->adyenHelper->getCheckoutEnvironment( $config['payment']['adyen']['checkoutEnvironment'] = $this->adyenHelper->getCheckoutEnvironment(
$this->storeManager->getStore()->getId() $this->storeManager->getStore()->getId()
); );
$config['payment']['adyen']['locale'] = $this->adyenHelper->getStoreLocale(
$this->storeManager->getStore()->getId()
);
return $config; return $config;
} }
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
* *
* Adyen Payment module (https://www.adyen.com/) * 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. * See LICENSE.txt for license details.
* *
* Author: Adyen <magento@adyen.com> * Author: Adyen <magento@adyen.com>
...@@ -26,77 +26,92 @@ namespace Adyen\Payment\Observer; ...@@ -26,77 +26,92 @@ namespace Adyen\Payment\Observer;
use Magento\Framework\Event\Observer; use Magento\Framework\Event\Observer;
use Magento\Payment\Observer\AbstractDataAssignObserver; use Magento\Payment\Observer\AbstractDataAssignObserver;
use Magento\Quote\Api\Data\PaymentInterface; use Magento\Quote\Api\Data\PaymentInterface;
use \Adyen\Service\Validator\CheckoutStateDataValidator;
use \Adyen\Service\Validator\DataArrayValidator;
class AdyenCcDataAssignObserver extends AbstractDataAssignObserver class AdyenCcDataAssignObserver extends AbstractDataAssignObserver
{ {
const CC_TYPE = 'cc_type'; const CC_TYPE = 'cc_type';
const NUMBER_OF_INSTALLMENTS = 'number_of_installments'; const NUMBER_OF_INSTALLMENTS = 'number_of_installments';
const STORE_CC = 'store_cc'; 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 GUEST_EMAIL = 'guestEmail';
const COMBO_CARD_TYPE = 'combo_card_type'; const COMBO_CARD_TYPE = 'combo_card_type';
const STATE_DATA = 'stateData';
/** /**
* Approved root level keys from additional data array
*
* @var array * @var array
*/ */
protected $additionalInformationList = [ private static $approvedAdditionalDataKeys = [
self::CC_TYPE, self::STATE_DATA,
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,
self::GUEST_EMAIL, 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 * @param Observer $observer
* @return void * @return void
*/ */
public function execute(Observer $observer) public function execute(Observer $observer)
{ {
// Get request fields
$data = $this->readDataArgument($observer); $data = $this->readDataArgument($observer);
// Get additional data array
$additionalData = $data->getData(PaymentInterface::KEY_ADDITIONAL_DATA); $additionalData = $data->getData(PaymentInterface::KEY_ADDITIONAL_DATA);
if (!is_array($additionalData)) { if (!is_array($additionalData)) {
return; return;
} }
$paymentInfo = $this->readPaymentModelArgument($observer); // Get a validated additional data array
$additionalData = DataArrayValidator::getArrayOnlyWithApprovedKeys(
$additionalData,
self::$approvedAdditionalDataKeys
);
// set ccType // json decode state data
if (!empty($additionalData['cc_type'])) { $stateData = [];
$paymentInfo->setCcType($additionalData['cc_type']); if (!empty($additionalData[self::STATE_DATA])) {
$stateData = json_decode($additionalData[self::STATE_DATA], true);
} }
foreach ($this->additionalInformationList as $additionalInformationKey) { // Get validated state data array
if (isset($additionalData[$additionalInformationKey])) { if (!empty($stateData)) {
$paymentInfo->setAdditionalInformation( $stateData = $this->checkoutStateDataValidator->getValidatedAdditionalData(
$additionalInformationKey, $stateData
$additionalData[$additionalInformationKey]
); );
} }
// 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);
}
// set ccType
if (!empty($additionalData[self::CC_TYPE])) {
$paymentInfo->setCcType($additionalData[self::CC_TYPE]);
} }
} }
} }
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
* *
* Adyen Payment module (https://www.adyen.com/) * 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. * See LICENSE.txt for license details.
* *
* Author: Adyen <magento@adyen.com> * Author: Adyen <magento@adyen.com>
...@@ -23,6 +23,8 @@ ...@@ -23,6 +23,8 @@
namespace Adyen\Payment\Observer; namespace Adyen\Payment\Observer;
use Adyen\Service\Validator\CheckoutStateDataValidator;
use Adyen\Service\Validator\DataArrayValidator;
use Magento\Framework\Event\Observer; use Magento\Framework\Event\Observer;
use Magento\Payment\Observer\AbstractDataAssignObserver; use Magento\Payment\Observer\AbstractDataAssignObserver;
use Magento\Quote\Api\Data\PaymentInterface; use Magento\Quote\Api\Data\PaymentInterface;
...@@ -30,62 +32,84 @@ use Magento\Quote\Api\Data\PaymentInterface; ...@@ -30,62 +32,84 @@ use Magento\Quote\Api\Data\PaymentInterface;
class AdyenHppDataAssignObserver extends AbstractDataAssignObserver class AdyenHppDataAssignObserver extends AbstractDataAssignObserver
{ {
const BRAND_CODE = 'brand_code'; 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 DF_VALUE = 'df_value';
const SSN = 'ssn'; const GUEST_EMAIL = 'guestEmail';
const OWNER_NAME = 'ownerName'; const STATE_DATA = 'stateData';
const BANK_ACCOUNT_OWNER_NAME = 'bankAccountOwnerName';
const IBAN_NUMBER = 'ibanNumber';
const BANK_ACCOUNT_NUMBER = 'bankAccountNumber';
const BANK_LOCATIONID = 'bankLocationId';
/** /**
* Approved root level keys from additional data array
*
* @var array * @var array
*/ */
protected $additionalInformationList = [ private static $approvedAdditionalDataKeys = [
self::BRAND_CODE, self::BRAND_CODE,
self::ISSUER_ID,
self::GENDER,
self::DOB,
self::TELEPHONE,
self::DF_VALUE, self::DF_VALUE,
self::SSN, self::GUEST_EMAIL,
self::OWNER_NAME, self::STATE_DATA
self::BANK_ACCOUNT_OWNER_NAME,
self::IBAN_NUMBER,
self::BANK_ACCOUNT_NUMBER,
self::BANK_LOCATIONID
]; ];
/**
* @var CheckoutStateDataValidator
*/
protected $checkoutStateDataValidator;
/**
* AdyenHppDataAssignObserver constructor.
*
* @param CheckoutStateDataValidator $checkoutStateDataValidator
*/
public function __construct(
CheckoutStateDataValidator $checkoutStateDataValidator
) {
$this->checkoutStateDataValidator = $checkoutStateDataValidator;
}
/** /**
* @param Observer $observer * @param Observer $observer
* @return void * @return void
*/ */
public function execute(Observer $observer) public function execute(Observer $observer)
{ {
// Get request fields
$data = $this->readDataArgument($observer); $data = $this->readDataArgument($observer);
// Get additional data array
$additionalData = $data->getData(PaymentInterface::KEY_ADDITIONAL_DATA); $additionalData = $data->getData(PaymentInterface::KEY_ADDITIONAL_DATA);
if (!is_array($additionalData)) { if (!is_array($additionalData)) {
return; return;
} }
$paymentInfo = $this->readPaymentModelArgument($observer); // Get a validated additional data array
$additionalData = DataArrayValidator::getArrayOnlyWithApprovedKeys(
$additionalData,
self::$approvedAdditionalDataKeys
);
if (isset($additionalData[self::BRAND_CODE])) { // json decode state data
$paymentInfo->setCcType($additionalData[self::BRAND_CODE]); $stateData = [];
if (!empty($additionalData[self::STATE_DATA])) {
$stateData = json_decode($additionalData[self::STATE_DATA], true);
} }
foreach ($this->additionalInformationList as $additionalInformationKey) { // Get validated state data array
if (isset($additionalData[$additionalInformationKey])) { if (!empty($stateData)) {
$paymentInfo->setAdditionalInformation( $stateData = $this->checkoutStateDataValidator->getValidatedAdditionalData(
$additionalInformationKey, $stateData
$additionalData[$additionalInformationKey]
); );
} }
// 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);
}
// set ccType
if (!empty($additionalData[self::BRAND_CODE])) {
$paymentInfo->setCcType($additionalData[self::BRAND_CODE]);
} }
} }
} }
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
} }
], ],
"require": { "require": {
"adyen/php-api-library": "^6.3", "adyen/php-api-library": "^8",
"magento/framework": ">=101.0.8 <102 || >=102.0.1", "magento/framework": ">=101.0.8 <102 || >=102.0.1",
"magento/module-vault": "101.*", "magento/module-vault": "101.*",
"magento/module-paypal": ">=100.2.6", "magento/module-paypal": ">=100.2.6",
......
...@@ -557,9 +557,8 @@ ...@@ -557,9 +557,8 @@
<item name="payment" xsi:type="string">Adyen\Payment\Gateway\Request\PaymentDataBuilder</item> <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="browserinfo" xsi:type="string">Adyen\Payment\Gateway\Request\BrowserInfoDataBuilder</item>
<item name="recurring" xsi:type="string">Adyen\Payment\Gateway\Request\RecurringDataBuilder</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="vault" xsi:type="string">Adyen\Payment\Gateway\Request\VaultDataBuilder</item>
<item name="threeds2" xsi:type="string">Adyen\Payment\Gateway\Request\ThreeDS2DataBuilder</item>
</argument> </argument>
</arguments> </arguments>
</virtualType> </virtualType>
...@@ -606,7 +605,7 @@ ...@@ -606,7 +605,7 @@
<item name="browserinfo" xsi:type="string">Adyen\Payment\Gateway\Request\BrowserInfoDataBuilder</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="recurring" xsi:type="string">Adyen\Payment\Gateway\Request\RecurringDataBuilder</item>
<item name="oneclick" xsi:type="string">Adyen\Payment\Gateway\Request\OneclickAuthorizationDataBuilder</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> <item name="redirect" xsi:type="string">Adyen\Payment\Gateway\Request\RedirectDataBuilder</item>
</argument> </argument>
</arguments> </arguments>
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
<?php
/** /**
* ###### * ######
* ###### * ######
...@@ -15,59 +14,29 @@ ...@@ -15,59 +14,29 @@
* *
* Adyen Payment module (https://www.adyen.com/) * 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. * See LICENSE.txt for license details.
* *
* Author: Adyen <magento@adyen.com> * Author: Adyen <magento@adyen.com>
*/ */
define(
namespace Adyen\Payment\Gateway\Request; [
],
use Magento\Payment\Gateway\Request\BuilderInterface; function () {
'use strict';
class ThreeDS2DataBuilder implements BuilderInterface return {
{ getOriginKey: function () {
/** return window.checkoutConfig.payment.adyen.originKey;
* @var \Magento\Framework\App\State },
*/ showLogo: function () {
private $appState; return window.checkoutConfig.payment.adyen.showLogo;
},
/** getLocale: function () {
* @var \Adyen\Payment\Helper\Requests return window.checkoutConfig.payment.adyen.locale;
*/ },
private $adyenRequestsHelper; getCheckoutEnvironment: function () {
return window.checkoutConfig.payment.adyen.checkoutEnvironment;
/** },
* 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;
}
/**
* @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( ...@@ -6,74 +6,86 @@ define(
[ [
'underscore', 'underscore',
'Magento_Checkout/js/model/quote', 'Magento_Checkout/js/model/quote',
'Adyen_Payment/js/model/adyen-method-list',
'Magento_Customer/js/model/customer', 'Magento_Customer/js/model/customer',
'Magento_Checkout/js/model/url-builder', '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'; 'use strict';
return { return {
paymentMethods: ko.observable({}),
/** /**
* Populate the list of payment methods * Retrieve the list of available payment methods from Adyen
* @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
*/ */
retrieveAvailablePaymentMethods: function (callback) { retrievePaymentMethods: function() {
var self = this; // url for guest users
var serviceUrl = urlBuilder.createUrl(
'/guest-carts/:cartId/retrieve-adyen-payment-methods', {
cartId: quote.getQuoteId(),
});
// retrieve payment methods // url for logged in users
var serviceUrl,
payload;
if (customer.isLoggedIn()) { if (customer.isLoggedIn()) {
serviceUrl = urlBuilder.createUrl('/carts/mine/retrieve-adyen-payment-methods', {}); serviceUrl = urlBuilder.createUrl(
} else { '/carts/mine/retrieve-adyen-payment-methods', {});
serviceUrl = urlBuilder.createUrl('/guest-carts/:cartId/retrieve-adyen-payment-methods', {
cartId: quote.getQuoteId()
});
} }
payload = { // Construct payload for the retrieve payment methods request
var payload = {
cartId: quote.getQuoteId(), cartId: quote.getQuoteId(),
shippingAddress: quote.shippingAddress() shippingAddress: quote.shippingAddress(),
}; };
storage.post( return storage.post(
serviceUrl, serviceUrl,
JSON.stringify(payload) JSON.stringify(payload),
).done( );
function (response) { },
self.setPaymentMethods(response); getPaymentMethods: function() {
if (callback) { return this.paymentMethods;
callback(); },
} setPaymentMethods: function(paymentMethods) {
} this.paymentMethods(paymentMethods);
).fail(
function () {
self.setPaymentMethods([]);
}
)
}, },
getOrderPaymentStatus: function (orderId) { getOrderPaymentStatus: function(orderId) {
var serviceUrl = urlBuilder.createUrl('/adyen/orders/:orderId/payment-status', { var serviceUrl = urlBuilder.createUrl(
orderId: orderId '/adyen/orders/:orderId/payment-status', {
orderId: orderId,
}); });
return storage.get(serviceUrl); 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 @@ ...@@ -24,11 +24,19 @@
define( define(
[ [
'uiComponent', '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 ( function (
Component, Component,
rendererList rendererList,
adyenPaymentService,
adyenConfiguration,
quote,
fullScreenLoader
) { ) {
'use strict'; 'use strict';
rendererList.push( rendererList.push(
...@@ -66,6 +74,25 @@ define( ...@@ -66,6 +74,25 @@ define(
initialize: function () { initialize: function () {
this._super(); 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()) { if (this.isGooglePayEnabled()) {
var googlepayscript = document.createElement('script'); var googlepayscript = document.createElement('script');
googlepayscript.src = "https://pay.google.com/gp/p/js/pay.js"; googlepayscript.src = "https://pay.google.com/gp/p/js/pay.js";
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
* *
* Adyen Payment module (https://www.adyen.com/) * 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. * See LICENSE.txt for license details.
* *
* Author: Adyen <magento@adyen.com> * Author: Adyen <magento@adyen.com>
...@@ -25,94 +25,74 @@ define( ...@@ -25,94 +25,74 @@ define(
'ko', 'ko',
'Magento_Payment/js/view/payment/cc-form', 'Magento_Payment/js/view/payment/cc-form',
'Magento_Customer/js/model/customer', 'Magento_Customer/js/model/customer',
'Magento_Payment/js/model/credit-card-validation/credit-card-data',
'Magento_Checkout/js/model/payment/additional-validators', 'Magento_Checkout/js/model/payment/additional-validators',
'Magento_Checkout/js/model/quote', 'Magento_Checkout/js/model/quote',
'Adyen_Payment/js/model/installments', 'Adyen_Payment/js/model/installments',
'mage/url', 'mage/url',
'Magento_Vault/js/view/payment/vault-enabler', 'Magento_Vault/js/view/payment/vault-enabler',
'Magento_Checkout/js/model/url-builder', 'Magento_Checkout/js/model/url-builder',
'mage/storage',
'Magento_Checkout/js/model/full-screen-loader', 'Magento_Checkout/js/model/full-screen-loader',
'Magento_Paypal/js/action/set-payment-method',
'Magento_Checkout/js/action/select-payment-method',
'Adyen_Payment/js/threeds2-js-utils',
'Adyen_Payment/js/model/payment-details',
'Magento_Checkout/js/model/error-processor', 'Magento_Checkout/js/model/error-processor',
'Adyen_Payment/js/model/adyen-payment-service', 'Adyen_Payment/js/model/adyen-payment-service',
'Adyen_Payment/js/model/adyen-configuration',
'Adyen_Payment/js/bundle' 'Adyen_Payment/js/bundle'
], ],
function ( function(
$, $,
ko, ko,
Component, Component,
customer, customer,
creditCardData,
additionalValidators, additionalValidators,
quote, quote,
installmentsHelper, installmentsHelper,
url, url,
VaultEnabler, VaultEnabler,
urlBuilder, urlBuilder,
storage,
fullScreenLoader, fullScreenLoader,
setPaymentMethodAction,
selectPaymentMethodAction,
threeDS2Utils,
paymentDetails,
errorProcessor, errorProcessor,
adyenPaymentService, adyenPaymentService,
AdyenComponent adyenConfiguration,
adyenComponent
) { ) {
'use strict'; 'use strict';
return Component.extend({ return Component.extend({
// need to duplicate as without the button will never activate on first time page view // need to duplicate as without the button will never activate on first time page view
isPlaceOrderActionAllowed: ko.observable(quote.billingAddress() != null), isPlaceOrderActionAllowed: ko.observable(
quote.billingAddress() != null),
comboCardOption: ko.observable('credit'), comboCardOption: ko.observable('credit'),
defaults: { defaults: {
template: 'Adyen_Payment/payment/cc-form', template: 'Adyen_Payment/payment/cc-form',
creditCardOwner: '', installment: '', // keep it until the component implements installments
storeCc: false, orderId: 0, // TODO is this the best place to store it?
installment: '',
creditCardDetailsValid: false,
orderId: 0
}, },
/** /**
* @returns {exports.initialize} * @returns {exports.initialize}
*/ */
initialize: function () { initialize: function() {
this._super(); this._super();
this.vaultEnabler = new VaultEnabler(); this.vaultEnabler = new VaultEnabler();
this.vaultEnabler.setPaymentCode(this.getVaultCode()); this.vaultEnabler.setPaymentCode(this.getVaultCode());
this.vaultEnabler.isActivePaymentTokenEnabler(false); this.vaultEnabler.isActivePaymentTokenEnabler(false);
// initialize adyen component for general use this.checkoutComponent = new AdyenCheckout({
this.checkout = new AdyenCheckout({
hasHolderName: true, hasHolderName: true,
locale: this.getLocale(), locale: adyenConfiguration.getLocale(),
originKey: this.getOriginKey(), originKey: adyenConfiguration.getOriginKey(),
environment: this.getCheckoutEnvironment(), environment: adyenConfiguration.getCheckoutEnvironment(),
paymentMethodsResponse: adyenPaymentService.getPaymentMethods().paymentMethodsResponse,
onAdditionalDetails: this.handleOnAdditionalDetails.bind(this) onAdditionalDetails: this.handleOnAdditionalDetails.bind(this)
}); },
);
return this; return this;
}, },
initObservable: function () { initObservable: function() {
this._super() this._super().observe([
.observe([ 'creditCardType', // TODO check if still needed
'creditCardType',
'creditCardOwner',
'creditCardNumber',
'securityCode',
'expiryMonth',
'expiryYear',
'installment', 'installment',
'installments', 'installments',
'creditCardDetailsValid', 'placeOrderAllowed',
'placeOrderAllowed'
]); ]);
return this; return this;
...@@ -121,8 +101,10 @@ define( ...@@ -121,8 +101,10 @@ define(
* Returns true if card details can be stored * Returns true if card details can be stored
* @returns {*|boolean} * @returns {*|boolean}
*/ */
getEnableStoreDetails: function () { getEnableStoreDetails: function() {
return this.canCreateBillingAgreement() && !this.isVaultEnabled(); // TODO refactor the configuration for this
return this.canCreateBillingAgreement() &&
!this.isVaultEnabled();
}, },
/** /**
* Renders the secure fields, * Renders the secure fields,
...@@ -130,7 +112,7 @@ define( ...@@ -130,7 +112,7 @@ define(
* sets up the callbacks for card components and * sets up the callbacks for card components and
* set up the installments * set up the installments
*/ */
renderSecureFields: function () { renderSecureFields: function() {
var self = this; var self = this;
if (!self.getOriginKey()) { if (!self.getOriginKey()) {
...@@ -141,36 +123,24 @@ define( ...@@ -141,36 +123,24 @@ define(
// installments // installments
var allInstallments = self.getAllInstallments(); var allInstallments = self.getAllInstallments();
var cardNode = document.getElementById('cardContainer');
self.cardComponent = self.checkoutComponent.create('card', {
self.cardComponent = self.checkout.create('card', {
type: 'card',
enableStoreDetails: self.getEnableStoreDetails(), enableStoreDetails: self.getEnableStoreDetails(),
groupTypes: self.getAvailableCardTypeAltCodes(), groupTypes: self.getAvailableCardTypeAltCodes(),
onChange: function(state, component) {
onChange: function (state, component) { self.placeOrderAllowed(!!state.isValid);
if (!!state.isValid && !component.state.errors.encryptedSecurityCode) {
self.storeCc = !!state.data.storePaymentMethod;
self.creditCardNumber(state.data.paymentMethod.encryptedCardNumber);
self.expiryMonth(state.data.paymentMethod.encryptedExpiryMonth);
self.expiryYear(state.data.paymentMethod.encryptedExpiryYear);
self.securityCode(state.data.paymentMethod.encryptedSecurityCode);
self.creditCardOwner(state.data.paymentMethod.holderName);
self.creditCardDetailsValid(true);
self.placeOrderAllowed(true);
} else {
self.creditCardDetailsValid(false);
self.placeOrderAllowed(false);
}
}, },
onBrand: function (state) { // Keep onBrand as is until checkout component supports installments
onBrand: function(state) {
// Define the card type // Define the card type
// translate adyen card type to magento card type // translate adyen card type to magento card type
var creditCardType = self.getCcCodeByAltCode(state.brand); var creditCardType = self.getCcCodeByAltCode(
state.brand);
if (creditCardType) { if (creditCardType) {
// If the credit card type is already set, check if it changed or not // If the credit card type is already set, check if it changed or not
if (!self.creditCardType() || self.creditCardType() && self.creditCardType() != creditCardType) { if (!self.creditCardType() ||
self.creditCardType() &&
self.creditCardType() != creditCardType) {
var numberOfInstallments = []; var numberOfInstallments = [];
if (creditCardType in allInstallments) { if (creditCardType in allInstallments) {
...@@ -180,7 +150,9 @@ define( ...@@ -180,7 +150,9 @@ define(
var precision = quote.getPriceFormat().precision; var precision = quote.getPriceFormat().precision;
var currencyCode = quote.totals().quote_currency_code; var currencyCode = quote.totals().quote_currency_code;
numberOfInstallments = installmentsHelper.getInstallmentsWithPrices(installmentCreditcard, grandTotal, precision, currencyCode); numberOfInstallments = installmentsHelper.getInstallmentsWithPrices(
installmentCreditcard, grandTotal,
precision, currencyCode);
} }
if (numberOfInstallments) { if (numberOfInstallments) {
...@@ -191,24 +163,25 @@ define( ...@@ -191,24 +163,25 @@ define(
} }
// for BCMC as this is not a core payment method inside magento use maestro as brand detection // for BCMC as this is not a core payment method inside magento use maestro as brand detection
if (creditCardType == "BCMC") { if (creditCardType == 'BCMC') {
self.creditCardType("MI"); self.creditCardType('MI');
} else { } else {
self.creditCardType(creditCardType); self.creditCardType(creditCardType);
} }
} else { } else {
self.creditCardType("") self.creditCardType('');
self.installments(0); self.installments(0);
} }
} }
}).mount(cardNode); }).mount('#cardContainer');
}, },
handleAction: function (action, orderId) { handleAction: function(action, orderId) {
var self = this; var self = this;
try { try {
var component = self.checkout.createFromAction(action).mount('#cc_actionContainer'); self.checkoutComponent.createFromAction(
action).mount('#cc_actionContainer');
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} }
...@@ -217,44 +190,31 @@ define( ...@@ -217,44 +190,31 @@ define(
* This method is a workaround to close the modal in the right way and reconstruct the threeDS2Modal. * This method is a workaround to close the modal in the right way and reconstruct the threeDS2Modal.
* This will solve issues when you cancel the 3DS2 challenge and retry the payment * This will solve issues when you cancel the 3DS2 challenge and retry the payment
*/ */
closeModal: function (popupModal) { closeModal: function(popupModal) {
popupModal.modal("closeModal"); popupModal.modal('closeModal');
$('.cc_actionModal').remove(); $('.cc_actionModal').remove();
$('.modals-overlay').remove(); $('.modals-overlay').remove();
$('body').removeClass('_has-modal'); $('body').removeClass('_has-modal');
// reconstruct the threeDS2Modal container again otherwise component can not find the threeDS2Modal // reconstruct the threeDS2Modal container again otherwise component can not find the threeDS2Modal
$('#cc_actionModalWrapper').append("<div id=\"cc_actionModal\">" + $('#cc_actionModalWrapper').
"<div id=\"cc_actionContainer\"></div>" + append('<div id="cc_actionModal">' +
"</div>"); '<div id="cc_actionContainer"></div>' +
'</div>');
}, },
/** /**
* Get data for place order * Get data for place order
* @returns {{method: *}} * @returns {{method: *}}
*/ */
getData: function () { getData: function() {
var browserInfo = threeDS2Utils.getBrowserInfo();
var data = { var data = {
'method': this.item.method, 'method': this.item.method,
additional_data: { additional_data: {
'stateData': JSON.stringify(this.cardComponent.data),
'guestEmail': quote.guestEmail, 'guestEmail': quote.guestEmail,
'cc_type': this.creditCardType(), 'cc_type': this.creditCardType(),
'number': this.creditCardNumber(), 'combo_card_type': this.comboCardOption(),
'cvc': this.securityCode(), },
'expiryMonth': this.expiryMonth(),
'expiryYear': this.expiryYear(),
'holderName': this.creditCardOwner(),
'store_cc': this.storeCc,
'number_of_installments': this.installment(),
'java_enabled': browserInfo.javaEnabled,
'screen_color_depth': browserInfo.colorDepth,
'screen_width': browserInfo.screenWidth,
'screen_height': browserInfo.screenHeight,
'timezone_offset': browserInfo.timeZoneOffset,
'language': browserInfo.language,
'combo_card_type': this.comboCardOption()
}
}; };
this.vaultEnabler.visitAdditionalData(data); this.vaultEnabler.visitAdditionalData(data);
return data; return data;
...@@ -263,8 +223,11 @@ define( ...@@ -263,8 +223,11 @@ define(
* Returns state of place order button * Returns state of place order button
* @returns {boolean} * @returns {boolean}
*/ */
isButtonActive: function () { isButtonActive: function() {
return this.isActive() && this.getCode() == this.isChecked() && this.isPlaceOrderActionAllowed() && this.placeOrderAllowed(); // TODO check if isPlaceOrderActionAllowed and placeOrderAllowed are both needed
return this.isActive() && this.getCode() == this.isChecked() &&
this.isPlaceOrderActionAllowed() &&
this.placeOrderAllowed();
}, },
/** /**
* Custom place order function * Custom place order function
...@@ -275,7 +238,7 @@ define( ...@@ -275,7 +238,7 @@ define(
* @param event * @param event
* @returns {boolean} * @returns {boolean}
*/ */
placeOrder: function (data, event) { placeOrder: function(data, event) {
var self = this; var self = this;
if (event) { if (event) {
...@@ -286,21 +249,21 @@ define( ...@@ -286,21 +249,21 @@ define(
fullScreenLoader.startLoader(); fullScreenLoader.startLoader();
self.isPlaceOrderActionAllowed(false); self.isPlaceOrderActionAllowed(false);
self.getPlaceOrderDeferredObject() self.getPlaceOrderDeferredObject().fail(
.fail( function() {
function () {
fullScreenLoader.stopLoader(); fullScreenLoader.stopLoader();
self.isPlaceOrderActionAllowed(true); self.isPlaceOrderActionAllowed(true);
} },
).done( ).done(
function (orderId) { function(orderId) {
self.afterPlaceOrder(); self.afterPlaceOrder();
self.orderId = orderId; self.orderId = orderId;
adyenPaymentService.getOrderPaymentStatus(orderId) adyenPaymentService.getOrderPaymentStatus(orderId).
.done(function (responseJSON) { done(function(responseJSON) {
self.handleAdyenResult(responseJSON, orderId) self.handleAdyenResult(responseJSON,
orderId);
}); });
} },
); );
} }
return false; return false;
...@@ -309,21 +272,22 @@ define( ...@@ -309,21 +272,22 @@ define(
* Based on the response we can start a 3DS2 validation or place the order * Based on the response we can start a 3DS2 validation or place the order
* @param responseJSON * @param responseJSON
*/ */
handleAdyenResult: function (responseJSON, orderId) { handleAdyenResult: function(responseJSON, orderId) {
var self = this; var self = this;
var response = JSON.parse(responseJSON); var response = JSON.parse(responseJSON);
if (!!response.isFinal) { if (!!response.isFinal) {
// Status is final redirect to the redirectUrl // Status is final redirect to the redirectUrl
window.location.replace(url.build( window.location.replace(url.build(
window.checkoutConfig.payment[quote.paymentMethod().method].redirectUrl window.checkoutConfig.payment[quote.paymentMethod().method].redirectUrl,
)); ));
} else { } else {
// Handle action // Handle action
self.handleAction(response.action, orderId); self.handleAction(response.action, orderId);
} }
}, },
handleOnAdditionalDetails: function (result) { handleOnAdditionalDetails: function(result) {
var self = this; var self = this;
var request = result.data; var request = result.data;
...@@ -331,6 +295,7 @@ define( ...@@ -331,6 +295,7 @@ define(
fullScreenLoader.stopLoader(); fullScreenLoader.stopLoader();
// TODO outsource creating the modal
var popupModal = $('#cc_actionModal').modal({ var popupModal = $('#cc_actionModal').modal({
// disable user to hide popup // disable user to hide popup
clickableOverlay: false, clickableOverlay: false,
...@@ -338,15 +303,17 @@ define( ...@@ -338,15 +303,17 @@ define(
innerScroll: false, innerScroll: false,
// empty buttons, we don't need that // empty buttons, we don't need that
buttons: [], buttons: [],
modalClass: 'cc_actionModal' modalClass: 'cc_actionModal',
}); });
popupModal.modal("openModal"); popupModal.modal('openModal');
paymentDetails.process(request).done(function (responseJSON) { adyenPaymentService.paymentDetails(request).
self.handleAdyenResult(responseJSON, self.orderId) done(function(responseJSON) {
}).fail(function (result) { self.handleAdyenResult(responseJSON, self.orderId);
errorProcessor.process(result, self.messageContainer); }).
fail(function(response) {
errorProcessor.process(response, self.messageContainer);
self.isPlaceOrderActionAllowed(true); self.isPlaceOrderActionAllowed(true);
fullScreenLoader.stopLoader(); fullScreenLoader.stopLoader();
}); });
...@@ -356,54 +323,34 @@ define( ...@@ -356,54 +323,34 @@ define(
* *
* @returns {boolean} * @returns {boolean}
*/ */
validate: function () { validate: function() {
var self = this;
var form = 'form[data-role=adyen-cc-form]'; var form = 'form[data-role=adyen-cc-form]';
var validate = $(form).validation() && $(form).validation('isValid'); var validate = $(form).validation() &&
$(form).validation('isValid') &&
this.cardComponent.isValid;
if (!validate) { if (!validate) {
this.cardComponent.showValidation();
return false; return false;
} }
return true; return true;
}, },
/**
* Validates if the typed in card holder is valid
* - length validation, can not be empty
*
* @returns {boolean}
*/
isCardOwnerValid: function () {
if (this.creditCardOwner().length == 0) {
return false;
}
return true;
},
/**
* The card component send the card details validity in a callback which is saved in the
* creditCardDetailsValid observable
*
* @returns {*}
*/
isCreditCardDetailsValid: function () {
return this.creditCardDetailsValid();
},
/** /**
* Translates the card type alt code (used in Adyen) to card type code (used in Magento) if it's available * Translates the card type alt code (used in Adyen) to card type code (used in Magento) if it's available
* *
* @param altCode * @param altCode
* @returns {*} * @returns {*}
*/ */
getCcCodeByAltCode: function (altCode) { getCcCodeByAltCode: function(altCode) {
var ccTypes = window.checkoutConfig.payment.ccform.availableTypesByAlt[this.getCode()]; var ccTypes = window.checkoutConfig.payment.ccform.availableTypesByAlt[this.getCode()];
if (ccTypes.hasOwnProperty(altCode)) { if (ccTypes.hasOwnProperty(altCode)) {
return ccTypes[altCode]; return ccTypes[altCode];
} }
return ""; return '';
}, },
/** /**
* Get available card types translated to the Adyen card type codes * Get available card types translated to the Adyen card type codes
...@@ -411,7 +358,8 @@ define( ...@@ -411,7 +358,8 @@ define(
* *
* @returns {string[]} * @returns {string[]}
*/ */
getAvailableCardTypeAltCodes: function () { // TODO check if we still need to rely on this or we can rely on the paymentMethods response types
getAvailableCardTypeAltCodes: function() {
var ccTypes = window.checkoutConfig.payment.ccform.availableTypesByAlt[this.getCode()]; var ccTypes = window.checkoutConfig.payment.ccform.availableTypesByAlt[this.getCode()];
return Object.keys(ccTypes); return Object.keys(ccTypes);
}, },
...@@ -420,52 +368,30 @@ define( ...@@ -420,52 +368,30 @@ define(
* *
* @returns {*} * @returns {*}
*/ */
getCode: function () { getCode: function() {
return window.checkoutConfig.payment.adyenCc.methodCode; return window.checkoutConfig.payment.adyenCc.methodCode;
}, },
getOriginKey: function () { canCreateBillingAgreement: function() {
return window.checkoutConfig.payment.adyen.originKey;
},
getCheckoutEnvironment: function () {
return window.checkoutConfig.payment.adyen.checkoutEnvironment;
},
getLocale: function () {
return window.checkoutConfig.payment.adyenCc.locale;
},
isActive: function () {
return true;
},
getControllerName: function () {
return window.checkoutConfig.payment.iframe.controllerName[this.getCode()];
},
getPlaceOrderUrl: function () {
return window.checkoutConfig.payment.iframe.placeOrderUrl[this.getCode()];
},
canCreateBillingAgreement: function () {
if (customer.isLoggedIn()) { if (customer.isLoggedIn()) {
return window.checkoutConfig.payment.adyenCc.canCreateBillingAgreement; return window.checkoutConfig.payment.adyenCc.canCreateBillingAgreement;
} }
return false; return false;
}, },
isShowLegend: function () { getIcons: function(type) {
return true; return window.checkoutConfig.payment.adyenCc.icons.hasOwnProperty(
}, type)
showLogo: function () {
return window.checkoutConfig.payment.adyen.showLogo;
},
getIcons: function (type) {
return window.checkoutConfig.payment.adyenCc.icons.hasOwnProperty(type)
? window.checkoutConfig.payment.adyenCc.icons[type] ? window.checkoutConfig.payment.adyenCc.icons[type]
: false : false;
}, },
hasInstallments: function () { hasInstallments: function() {
return this.comboCardOption() === 'credit' && window.checkoutConfig.payment.adyenCc.hasInstallments; return this.comboCardOption() === 'credit' &&
window.checkoutConfig.payment.adyenCc.hasInstallments;
}, },
getAllInstallments: function () { getAllInstallments: function() {
return window.checkoutConfig.payment.adyenCc.installments; return window.checkoutConfig.payment.adyenCc.installments;
}, },
areComboCardsEnabled: function () { areComboCardsEnabled: function() {
if (quote.billingAddress() === null) { if (quote.billingAddress() === null) {
return false; return false;
} }
...@@ -473,32 +399,52 @@ define( ...@@ -473,32 +399,52 @@ define(
var currencyCode = quote.totals().quote_currency_code; var currencyCode = quote.totals().quote_currency_code;
var allowedCurrenciesByCountry = { var allowedCurrenciesByCountry = {
'BR': 'BRL', 'BR': 'BRL',
'MX': 'MXN' 'MX': 'MXN',
}; };
return allowedCurrenciesByCountry[countryId] && return allowedCurrenciesByCountry[countryId] &&
currencyCode === allowedCurrenciesByCountry[countryId]; currencyCode === allowedCurrenciesByCountry[countryId];
}, },
setPlaceOrderHandler: function (handler) { getOriginKey: function() {
this.placeOrderHandler = handler; return adyenConfiguration.getOriginKey();
},
setValidateHandler: function (handler) {
this.validateHandler = handler;
},
context: function () {
return this;
}, },
/** /**
* @returns {Bool} * @returns {Bool}
*/ */
isVaultEnabled: function () { isVaultEnabled: function() {
return this.vaultEnabler.isVaultEnabled(); return this.vaultEnabler.isVaultEnabled();
}, },
/** /**
* @returns {String} * @returns {String}
*/ */
getVaultCode: function () { getVaultCode: function() {
return window.checkoutConfig.payment[this.getCode()].vaultCode; return window.checkoutConfig.payment[this.getCode()].vaultCode;
} },
// Default payment functions
setPlaceOrderHandler: function(handler) {
this.placeOrderHandler = handler;
},
setValidateHandler: function(handler) {
this.validateHandler = handler;
},
context: function() {
return this;
},
isShowLegend: function() {
return true;
},
showLogo: function() {
return adyenConfiguration.showLogo();
},
isActive: function() {
return true;
},
getControllerName: function() {
return window.checkoutConfig.payment.iframe.controllerName[this.getCode()];
},
getPlaceOrderUrl: function() {
return window.checkoutConfig.payment.iframe.placeOrderUrl[this.getCode()];
},
}); });
} },
); );
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
* *
* Adyen Payment module (https://www.adyen.com/) * 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. * See LICENSE.txt for license details.
* *
* Author: Adyen <magento@adyen.com> * Author: Adyen <magento@adyen.com>
...@@ -28,17 +28,14 @@ define( ...@@ -28,17 +28,14 @@ define(
'Magento_Checkout/js/model/quote', 'Magento_Checkout/js/model/quote',
'Magento_Checkout/js/checkout-data', 'Magento_Checkout/js/checkout-data',
'Magento_Checkout/js/model/payment/additional-validators', 'Magento_Checkout/js/model/payment/additional-validators',
'mage/storage',
'Magento_Checkout/js/model/url-builder',
'Adyen_Payment/js/model/adyen-payment-service', 'Adyen_Payment/js/model/adyen-payment-service',
'Magento_Customer/js/model/customer',
'Magento_Checkout/js/model/full-screen-loader', 'Magento_Checkout/js/model/full-screen-loader',
'Magento_Checkout/js/action/place-order', 'Magento_Checkout/js/action/place-order',
'uiLayout', 'uiLayout',
'Magento_Ui/js/model/messages', 'Magento_Ui/js/model/messages',
'Adyen_Payment/js/model/payment-details',
'Magento_Checkout/js/model/error-processor', 'Magento_Checkout/js/model/error-processor',
'Adyen_Payment/js/bundle' 'Adyen_Payment/js/bundle',
'Adyen_Payment/js/model/adyen-configuration',
], ],
function( function(
ko, ko,
...@@ -48,22 +45,17 @@ define( ...@@ -48,22 +45,17 @@ define(
quote, quote,
checkoutData, checkoutData,
additionalValidators, additionalValidators,
storage,
urlBuilder,
adyenPaymentService, adyenPaymentService,
customer,
fullScreenLoader, fullScreenLoader,
placeOrderAction, placeOrderAction,
layout, layout,
Messages, Messages,
paymentDetails,
errorProcessor, errorProcessor,
AdyenComponent AdyenComponent,
adyenConfiguration,
) { ) {
'use strict'; 'use strict';
var brandCode = ko.observable(null);
var paymentMethod = ko.observable(null);
var shippingAddressCountryCode = quote.shippingAddress().countryId;
var unsupportedPaymentMethods = [ var unsupportedPaymentMethods = [
'scheme', 'scheme',
'boleto', 'boleto',
...@@ -73,60 +65,62 @@ define( ...@@ -73,60 +65,62 @@ define(
'applepay', 'applepay',
'paywithgoogle']; 'paywithgoogle'];
var popupModal; var popupModal;
/** var brandCode = ko.observable(null);
* Shareble adyen checkout component var paymentMethod = ko.observable(null);
* @type {AdyenCheckout}
*/
var checkoutComponent;
var orderId;
return Component.extend({ return Component.extend({
self: this, self: this,
defaults: { defaults: {
template: 'Adyen_Payment/payment/hpp-form', template: 'Adyen_Payment/payment/hpp-form',
brandCode: '', orderId: 0,
paymentMethods: {}
}, },
initObservable: function() { initObservable: function() {
this._super().observe([ this._super().observe([
'brandCode', 'brandCode',
'issuer', 'paymentMethod',
'gender', 'adyenPaymentMethods'
'dob',
'telephone',
'ownerName',
'ibanNumber',
'ssn',
'bankAccountNumber',
'bankLocationId',
]); ]);
return this; return this;
}, initialize: function() { },
initialize: function() {
var self = this; var self = this;
this._super(); this._super();
fullScreenLoader.startLoader(); fullScreenLoader.startLoader();
/** var paymentMethodsObserver = adyenPaymentService.getPaymentMethods();
* Create sherable checkout component
* @type {AdyenCheckout} paymentMethodsObserver.subscribe(function(paymentMethodsResponse) {
*/ self.loadAdyenPaymentMethods(paymentMethodsResponse);
self.checkoutComponent = new AdyenCheckout({
locale: self.getLocale(),
onAdditionalDetails: self.handleOnAdditionalDetails.bind(self),
originKey: self.getOriginKey(),
environment: self.getCheckoutEnvironment(),
}); });
// reset variable: self.loadAdyenPaymentMethods(paymentMethodsObserver());
adyenPaymentService.setPaymentMethods(); },
loadAdyenPaymentMethods: function(paymentMethodsResponse) {
var self = this;
if (!!paymentMethodsResponse) {
var paymentMethods = paymentMethodsResponse.paymentMethodsResponse.paymentMethods;
this.checkoutComponent = new AdyenCheckout({
hasHolderName: true,
locale: adyenConfiguration.getLocale(),
originKey: adyenConfiguration.getOriginKey(),
environment: adyenConfiguration.getCheckoutEnvironment(),
paymentMethodsResponse: paymentMethodsResponse.paymentMethodsResponse,
onAdditionalDetails: this.handleOnAdditionalDetails.bind(
this),
},
);
adyenPaymentService.retrieveAvailablePaymentMethods(function() { // Needed until the new ratepay component is released
var paymentMethods = adyenPaymentService.getAvailablePaymentMethods(); if (JSON.stringify(paymentMethods).indexOf('ratepay') >
if (JSON.stringify(paymentMethods).indexOf('ratepay') > -1) { -1) {
var ratePayId = window.checkoutConfig.payment.adyenHpp.ratePayId; var ratePayId = window.checkoutConfig.payment.adyenHpp.ratePayId;
var dfValueRatePay = self.getRatePayDeviceIdentToken(); var dfValueRatePay = self.getRatePayDeviceIdentToken();
// TODO check if still needed with checkout component
window.di = { window.di = {
t: dfValueRatePay.replace(':', ''), t: dfValueRatePay.replace(':', ''),
v: ratePayId, v: ratePayId,
...@@ -135,38 +129,32 @@ define( ...@@ -135,38 +129,32 @@ define(
// Load Ratepay script // Load Ratepay script
var ratepayScriptTag = document.createElement('script'); var ratepayScriptTag = document.createElement('script');
ratepayScriptTag.src = '//d.ratepay.com/' + ratePayId + '/di.js'; ratepayScriptTag.src = '//d.ratepay.com/' + ratePayId +
'/di.js';
ratepayScriptTag.type = 'text/javascript'; ratepayScriptTag.type = 'text/javascript';
document.body.appendChild(ratepayScriptTag); document.body.appendChild(ratepayScriptTag);
} }
self.adyenPaymentMethods(self.getAdyenHppPaymentMethods(paymentMethodsResponse));
fullScreenLoader.stopLoader(); fullScreenLoader.stopLoader();
}); }
}, },
getAdyenHppPaymentMethods: function() { getAdyenHppPaymentMethods: function(paymentMethodsResponse) {
var self = this; var self = this;
var currentShippingAddressCountryCode = quote.shippingAddress().countryId;
// retrieve new payment methods if country code changed
if (shippingAddressCountryCode != currentShippingAddressCountryCode) {
fullScreenLoader.startLoader();
adyenPaymentService.retrieveAvailablePaymentMethods();
shippingAddressCountryCode = currentShippingAddressCountryCode;
fullScreenLoader.stopLoader();
}
var paymentMethods = adyenPaymentService.getAvailablePaymentMethods(); var paymentMethods = paymentMethodsResponse.paymentMethodsResponse.paymentMethods;
var paymentList = _.reduce(paymentMethods, var paymentList = _.reduce(paymentMethods,
function(accumulator, value) { function(accumulator, paymentMethod) {
if (!self.isPaymentMethodSupported(value.type)) { if (!self.isPaymentMethodSupported(
paymentMethod.type)) {
return accumulator; return accumulator;
} }
var messageContainer = new Messages(); var messageContainer = new Messages();
var name = 'messages-' + var name = 'messages-' +
self.getBrandCodeFromPaymentMethod(value); self.getBrandCodeFromPaymentMethod(paymentMethod);
var messagesComponent = { var messagesComponent = {
parent: self.name, parent: self.name,
name: name, name: name,
...@@ -185,11 +173,14 @@ define( ...@@ -185,11 +173,14 @@ define(
* @returns {*} * @returns {*}
*/ */
result.getBrandCode = function() { result.getBrandCode = function() {
return self.getBrandCodeFromPaymentMethod(value); return self.getBrandCodeFromPaymentMethod(
paymentMethod);
}; };
result.value = result.getBrandCode(); result.brandCode = result.getBrandCode();
result.name = value; result.name = paymentMethod.name;
result.icon = {}; // TODO get icon details
result.method = self.item.method; result.method = self.item.method;
/** /**
* Observable to enable and disable place order buttons for payment methods * Observable to enable and disable place order buttons for payment methods
...@@ -202,7 +193,8 @@ define( ...@@ -202,7 +193,8 @@ define(
}; };
result.getMessageName = function() { result.getMessageName = function() {
return 'messages-' + return 'messages-' +
self.getBrandCodeFromPaymentMethod(value); self.getBrandCodeFromPaymentMethod(
paymentMethod);
}; };
result.getMessageContainer = function() { result.getMessageContainer = function() {
return messageContainer; return messageContainer;
...@@ -218,7 +210,8 @@ define( ...@@ -218,7 +210,8 @@ define(
self.isPlaceOrderActionAllowed(false); self.isPlaceOrderActionAllowed(false);
$.when( $.when(
placeOrderAction(data, self.currentMessageContainer), placeOrderAction(data,
self.currentMessageContainer),
).fail( ).fail(
function(response) { function(response) {
self.isPlaceOrderActionAllowed(true); self.isPlaceOrderActionAllowed(true);
...@@ -228,9 +221,11 @@ define( ...@@ -228,9 +221,11 @@ define(
).done( ).done(
function(orderId) { function(orderId) {
self.afterPlaceOrder(); self.afterPlaceOrder();
adyenPaymentService.getOrderPaymentStatus(orderId). adyenPaymentService.getOrderPaymentStatus(
orderId).
done(function(responseJSON) { done(function(responseJSON) {
self.validateActionOrPlaceOrder(responseJSON, self.validateActionOrPlaceOrder(
responseJSON,
orderId); orderId);
}); });
}, },
...@@ -250,314 +245,145 @@ define( ...@@ -250,314 +245,145 @@ define(
result.afterPlaceOrder = function() { result.afterPlaceOrder = function() {
return self.afterPlaceOrder(); return self.afterPlaceOrder();
}; };
/**
* Checks if payment method is open invoice
* @returns {*|isPaymentMethodOpenInvoiceMethod}
*/
result.isPaymentMethodOpenInvoiceMethod = function() {
return value.isPaymentMethodOpenInvoiceMethod;
};
/**
* Checks if payment method is open invoice but not in the list below
* [klarna, afterpay]
* @returns {boolean}
*/
result.isPaymentMethodOtherOpenInvoiceMethod = function() {
if (
!result.isPaymentMethodAfterPay() &&
!result.isPaymentMethodKlarna() &&
!result.isPaymentMethodAfterPayTouch() &&
value.isPaymentMethodOpenInvoiceMethod
) {
return true;
}
return false;
};
/**
* Checks if payment method is klarna
* @returns {boolean}
*/
result.isPaymentMethodKlarna = function() {
if (result.getBrandCode() === 'klarna') {
return true;
}
return false;
};
/**
* Checks if payment method is after pay
* @returns {boolean}
*/
result.isPaymentMethodAfterPay = function() {
if (result.getBrandCode() === 'afterpay_default') {
return true;
}
return false;
};
/**
* Checks if payment method is after pay touch
* @returns {boolean}
*/
result.isPaymentMethodAfterPayTouch = function() {
if (result.getBrandCode() === 'afterpaytouch') {
return true;
}
return false;
};
/**
* Get personal number (SSN) length based on the buyer's country
* @returns {number}
*/
result.getSsnLength = function() {
if (quote.billingAddress().countryId == 'NO') {
//14 digits for Norway ÅÅÅÅMMDD-XXXXX
return 14;
} else {
//13 digits for other Nordic countries ÅÅÅÅMMDD-XXXX
return 13;
}
};
/**
* Get max length for the Bank account number
*/
result.getBankAccountNumberMaxLength = function() {
return 17;
};
/**
* Finds the issuer property in the payment method's response and if available returns it's index
* @returns
*/
result.findIssuersProperty = function() {
var issuerKey = false;
if (typeof value.details !== 'undefined') {
$.each(value.details, function(key, detail) {
if (typeof detail.items !== 'undefined' && detail.key ==
'issuer') {
issuerKey = key;
}
});
}
return issuerKey;
};
/**
* Checks if the payment method has issuers property available
* @returns {boolean}
*/
result.hasIssuersProperty = function() {
if (result.findIssuersProperty() !== false) {
return true;
}
return false;
};
/**
* Checks if the payment method has issuer(s) available
* @returns {boolean}
*/
result.hasIssuersAvailable = function() {
if (result.hasIssuersProperty() &&
value.details[result.findIssuersProperty()].items.length >
0) {
return true;
}
return false;
};
/**
* Returns the issuers for a payment method
* @returns {*}
*/
result.getIssuers = function() {
if (result.hasIssuersAvailable()) {
return value.details[result.findIssuersProperty()].items;
}
return [];
};
/**
* Checks if payment method is iDeal
* @returns {boolean}
*/
result.isIdeal = function() {
if (result.getBrandCode().indexOf('ideal') >= 0) {
return true;
}
return false;
};
/**
* Checks if payment method is ACH
* @returns {boolean}
*/
result.isAch = function() {
if (result.getBrandCode().indexOf('ach') == 0) {
return true;
}
return false;
};
/**
* Checks if payment method is sepa direct debit
*/
result.isSepaDirectDebit = function() {
if (result.getBrandCode().indexOf('sepadirectdebit') >= 0) {
return true;
}
return false;
};
/** /**
* Renders the secure fields, * Renders the secure fields,
* creates the ideal component, * creates the ideal component,
* sets up the callbacks for ideal components and * sets up the callbacks for ideal components and
*/ */
result.renderIdealComponent = function() { result.renderCheckoutComponent = function() {
result.isPlaceOrderAllowed(false); result.isPlaceOrderAllowed(false);
var idealNode = document.getElementById('iDealContainer'); var showPayButton = false;
const showPayButtonPaymentMethods = [
'paypal',
'applePay',
'googlePay'
];
var ideal = self.checkoutComponent.create('ideal', { if (showPayButtonPaymentMethods.includes(
items: result.getIssuers(), paymentMethod.type)) {
onChange: function(state) { showPayButton = true;
if (!!state.isValid) {
result.issuer(state.data.paymentMethod.issuer);
result.isPlaceOrderAllowed(true);
} else {
result.isPlaceOrderAllowed(false);
} }
},
});
ideal.mount(idealNode);
};
/**
* Creates the sepa direct debit component,
* sets up the callbacks for sepa components
*/
result.renderSepaDirectDebitComponent = function() {
result.isPlaceOrderAllowed(false);
var sepaDirectDebitNode = document.getElementById(
'sepaDirectDebitContainer');
var sepaDirectDebit = self.checkoutComponent.create( // TODO take the terms and confitions magento checkbox into account as well
'sepadirectdebit', { // If the details are empty and the pay button does not needs to be rendered by the component
countryCode: self.getLocale(), // simply skip rendering the adyen checkout component
onChange: function(state) { if (!paymentMethod.details && !showPayButton) {
if (!!state.isValid) {
result.ownerName(
state.data.paymentMethod['sepa.ownerName']);
result.ibanNumber(
state.data.paymentMethod['sepa.ibanNumber']);
result.isPlaceOrderAllowed(true); result.isPlaceOrderAllowed(true);
} else {
result.isPlaceOrderAllowed(false);
}
},
});
sepaDirectDebit.mount(sepaDirectDebitNode);
};
/**
* Creates the klarna component,
* sets up the callbacks for klarna components
*/
result.renderKlarnaComponent = function() {
/* The new Klarna integration doesn't return details and the component does not handle it */
if (!value.details) {
return; return;
} }
var klarnaNode = document.getElementById('klarnaContainer'); var city = '';
var country = '';
var klarna = self.checkoutComponent.create('klarna', { var postalCode = '';
countryCode: self.getLocale(), var street = '';
details: self.filterOutOpenInvoiceComponentDetails( var firstName = '';
value.details), var lastName = '';
visibility: { var telephone = '';
personalDetails: 'editable', var email = '';
var shopperGender = '';
var shopperDateOfBirth = '';
if (!!quote && !!quote.shippingAddress()) {
city = quote.shippingAddress().city;
country = quote.shippingAddress().countryId;
postalCode = quote.shippingAddress().postcode;
street = quote.shippingAddress().
street.
join(' ');
firstName = quote.shippingAddress().firstname;
lastName = quote.shippingAddress().lastname;
telephone = quote.shippingAddress().telephone;
if (!!customerData.email) {
email = customerData.email;
} else if (!!quote.guestEmail) {
email = quote.guestEmail;
}
shopperGender = customerData.gender;
shopperDateOfBirth = customerData.dob;
}
function getAdyenGender(gender) {
if (gender == 1) {
return 'MALE';
} else if (gender == 2) {
return 'FEMALE';
}
return 'UNKNOWN';
}
/*Use the storedPaymentMethod object and the custom onChange function as the configuration object together*/
var configuration = Object.assign(paymentMethod, {
showPayButton: showPayButton,
countryCode: country,
currencyCode: quote.totals().quote_currency_code,
amount: quote.totals().grand_total, //TODO minor units and PW-2029 adjustment
data: {
personalDetails: {
firstName: firstName,
lastName: lastName,
telephoneNumber: telephone,
shopperEmail: email,
gender: getAdyenGender(shopperGender),
dateOfBirth: shopperDateOfBirth,
}, },
onChange: function(state) { billingAddress: {
if (!!state.isValid) { city: city,
result.dob( country: country,
state.data.paymentMethod.personalDetails.dateOfBirth); houseNumberOrName: '',
result.telephone( postalCode: postalCode,
state.data.paymentMethod.personalDetails.telephoneNumber); street: street,
result.gender(
state.data.paymentMethod.personalDetails.gender);
result.isPlaceOrderAllowed(true);
} else {
result.isPlaceOrderAllowed(false);
}
}, },
}).mount(klarnaNode);
};
/**
* Creates the afterpay component,
* sets up the callbacks for klarna components
*/
result.renderAfterPayComponent = function() {
var afterPay = self.checkoutComponent.create('afterpay', {
countryCode: self.getLocale(),
details: self.filterOutOpenInvoiceComponentDetails(
value.details),
visibility: {
personalDetails: 'editable',
}, },
onChange: function(state) { onChange: function(state) {
if (!!state.isValid) { result.isPlaceOrderAllowed(state.isValid);
result.dob(
state.data.paymentMethod.personalDetails.dateOfBirth);
result.telephone(
state.data.paymentMethod.personalDetails.telephoneNumber);
result.gender(
state.data.paymentMethod.personalDetails.gender);
result.isPlaceOrderAllowed(true);
} else {
result.isPlaceOrderAllowed(false);
} }
}, });
}).mount(document.getElementById('afterPayContainer'));
};
try {
result.component = self.checkoutComponent.create(
result.getBrandCode(), configuration).
mount(
'#adyen-alternative-payment-container-' +
result.getBrandCode());
} catch (err) {
console.log(err);
// The component does not exist yet
}
};
// TODO do the same way as the card payments
result.continueToAdyenBrandCode = function() { result.continueToAdyenBrandCode = function() {
// set payment method to adyen_hpp // set payment method to adyen_hpp
var self = this; var self = this;
if (this.validate() && additionalValidators.validate()) { if (this.validate() &&
additionalValidators.validate()) {
var data = {}; var data = {};
data.method = self.method; data.method = self.method;
var additionalData = {}; var additionalData = {};
additionalData.brand_code = self.value; additionalData.brand_code = self.brandCode;
if (self.hasIssuersAvailable()) { let stateData;
additionalData.issuer_id = this.issuer(); if ('component' in self) {
} else if (self.isPaymentMethodOpenInvoiceMethod()) { stateData = self.component.data;
additionalData.gender = this.gender(); } else {
additionalData.dob = this.dob(); stateData = {
additionalData.telephone = this.telephone(); paymentMethod: {
additionalData.ssn = this.ssn(); type: self.brandCode
}
};
}
additionalData.stateData = JSON.stringify(stateData);
if (brandCode() == 'ratepay') { if (brandCode() == 'ratepay') {
additionalData.df_value = this.getRatePayDeviceIdentToken(); additionalData.df_value = this.getRatePayDeviceIdentToken();
} }
} else if (self.isSepaDirectDebit()) {
additionalData.ownerName = this.ownerName();
additionalData.ibanNumber = this.ibanNumber();
} else if (self.isAch()) {
additionalData.bankAccountOwnerName = this.ownerName();
additionalData.bankAccountNumber = this.bankAccountNumber();
additionalData.bankLocationId = this.bankLocationId();
}
data.additional_data = additionalData; data.additional_data = additionalData;
this.placeRedirectOrder(data); this.placeRedirectOrder(data);
...@@ -566,46 +392,9 @@ define( ...@@ -566,46 +392,9 @@ define(
return false; return false;
}; };
if (result.hasIssuersProperty()) {
if (!result.hasIssuersAvailable()) {
return false;
}
result.issuerIds = result.getIssuers();
result.issuer = ko.observable(null);
} else if (value.isPaymentMethodOpenInvoiceMethod) {
result.telephone = ko.observable(
quote.shippingAddress().telephone);
result.gender = ko.observable(
window.checkoutConfig.payment.adyenHpp.gender);
result.dob = ko.observable(
window.checkoutConfig.payment.adyenHpp.dob);
result.datepickerValue = ko.observable(); // needed ??
result.ssn = ko.observable();
result.getRatePayDeviceIdentToken = function() { result.getRatePayDeviceIdentToken = function() {
return window.checkoutConfig.payment.adyenHpp.deviceIdentToken; return window.checkoutConfig.payment.adyenHpp.deviceIdentToken;
}; };
result.showSsn = function() {
if (result.getBrandCode().indexOf('klarna') >= 0) {
var ba = quote.billingAddress();
if (ba != null) {
var nordicCountriesList = window.checkoutConfig.payment.adyenHpp.nordicCountries;
if (nordicCountriesList.indexOf(ba.countryId) >= 0) {
return true;
}
}
}
return false;
};
} else if (result.isSepaDirectDebit()) {
result.ownerName = ko.observable(null);
result.ibanNumber = ko.observable(null);
} else if (result.isAch()) {
result.ownerName = ko.observable(null);
result.bankAccountNumber = ko.observable(null);
result.bankLocationId = ko.observable(null);
}
accumulator.push(result); accumulator.push(result);
return accumulator; return accumulator;
...@@ -625,22 +414,14 @@ define( ...@@ -625,22 +414,14 @@ define(
return true; return true;
} }
for (var i = 0; i < unsupportedPaymentMethods.length; i++) { for (var i = 0; i < unsupportedPaymentMethods.length; i++) {
var match = paymentMethod.match(unsupportedPaymentMethods[i]); var match = paymentMethod.match(
unsupportedPaymentMethods[i]);
if (match) { if (match) {
return false; return false;
} }
} }
return true; return true;
}, },
getGenderTypes: function() {
return _.map(window.checkoutConfig.payment.adyenHpp.genderTypes,
function(value, key) {
return {
'key': key,
'value': value,
};
});
},
selectPaymentMethodBrandCode: function() { selectPaymentMethodBrandCode: function() {
var self = this; var self = this;
...@@ -649,12 +430,12 @@ define( ...@@ -649,12 +430,12 @@ define(
'method': self.method, 'method': self.method,
'po_number': null, 'po_number': null,
'additional_data': { 'additional_data': {
brand_code: self.value, brand_code: self.brandCode,
}, },
}; };
// set the brandCode // set the brandCode
brandCode(self.value); brandCode(self.brandCode);
// set payment method // set payment method
paymentMethod(self.method); paymentMethod(self.method);
...@@ -690,9 +471,6 @@ define( ...@@ -690,9 +471,6 @@ define(
} }
return null; return null;
}), }),
isIconEnabled: function() {
return window.checkoutConfig.payment.adyen.showLogo;
},
/** /**
* Based on the response we can start a action component or redirect * Based on the response we can start a action component or redirect
* @param responseJSON * @param responseJSON
...@@ -701,27 +479,18 @@ define( ...@@ -701,27 +479,18 @@ define(
var self = this; var self = this;
var response = JSON.parse(responseJSON); var response = JSON.parse(responseJSON);
if (!!response.action) { if (!!response.isFinal) {
// render component // Status is final redirect to the redirectUrl
self.orderId = orderId;
self.renderActionComponent(response.action);
} else {
$.mage.redirect( $.mage.redirect(
window.checkoutConfig.payment[quote.paymentMethod().method].redirectUrl, window.checkoutConfig.payment[quote.paymentMethod().method].redirectUrl,
); );
} else {
// render component
self.orderId = orderId;
self.renderActionComponent(response.action);
} }
}, },
/**
* Rendering the 3DS2.0 components
* To do the device fingerprint at the response of IdentifyShopper render the threeDS2DeviceFingerprint
* component
* To render the challenge for the customer at the response of ChallengeShopper render the
* threeDS2Challenge component
* Both of them is going to be rendered in a Magento dialog popup
*
* @param type
* @param token
*/
renderActionComponent: function(action) { renderActionComponent: function(action) {
var self = this; var self = this;
var actionNode = document.getElementById('ActionContainer'); var actionNode = document.getElementById('ActionContainer');
...@@ -750,14 +519,15 @@ define( ...@@ -750,14 +519,15 @@ define(
request.orderId = self.orderId; request.orderId = self.orderId;
// Using the same processor as 3DS2, refactor to generic name in a upcomming release will be breaking change for merchants. // Using the same processor as 3DS2, refactor to generic name in a upcomming release will be breaking change for merchants.
paymentDetails.process(request).done(function() { adyenPaymentService.paymentDetails(request).done(function() {
$.mage.redirect( $.mage.redirect(
window.checkoutConfig.payment[quote.paymentMethod().method].redirectUrl, window.checkoutConfig.payment[quote.paymentMethod().method].redirectUrl,
); );
}).fail(function(response) { }).fail(function(response) {
fullScreenLoader.stopLoader(); fullScreenLoader.stopLoader();
self.closeModal(self.popupModal); self.closeModal(self.popupModal);
errorProcessor.process(response, self.currentMessageContainer); errorProcessor.process(response,
self.currentMessageContainer);
self.isPlaceOrderActionAllowed(true); self.isPlaceOrderActionAllowed(true);
self.showErrorMessage(response); self.showErrorMessage(response);
}); });
...@@ -784,7 +554,8 @@ define( ...@@ -784,7 +554,8 @@ define(
}, },
validate: function(brandCode) { validate: function(brandCode) {
var form = '#payment_form_' + this.getCode() + '_' + brandCode; var form = '#payment_form_' + this.getCode() + '_' + brandCode;
var validate = $(form).validation() && $(form).validation('isValid'); var validate = $(form).validation() &&
$(form).validation('isValid');
if (!validate) { if (!validate) {
return false; return false;
...@@ -804,58 +575,10 @@ define( ...@@ -804,58 +575,10 @@ define(
return ''; return '';
}, },
getRatePayDeviceIdentToken: function() { getRatePayDeviceIdentToken: function() {
return window.checkoutConfig.payment.adyenHpp.deviceIdentToken; return window.checkoutConfig.payment.adyenHpp.deviceIdentToken;
}, },
getLocale: function() {
return window.checkoutConfig.payment.adyenHpp.locale;
},
/**
* In the open invoice components we need to validate only the personal details and only the
* dateOfBirth, telephoneNumber and gender if it's set in the admin
* @param details
* @returns {Array}
*/
filterOutOpenInvoiceComponentDetails: function(details) {
var self = this;
var filteredDetails = _.map(details, function(parentDetail) {
if (parentDetail.key == 'personalDetails') {
var detailObject = _.map(parentDetail.details, function(detail) {
if (detail.key == 'dateOfBirth' ||
detail.key == 'telephoneNumber' ||
detail.key == 'gender') {
return detail;
}
});
if (!!detailObject) {
return {
'key': parentDetail.key,
'type': parentDetail.type,
'details': self.filterUndefinedItemsInArray(detailObject),
};
}
}
});
return self.filterUndefinedItemsInArray(filteredDetails);
},
/**
* Helper function to filter out the undefined items from an array
* @param arr
* @returns {*}
*/
filterUndefinedItemsInArray: function(arr) {
return arr.filter(function(item) {
return typeof item !== 'undefined';
});
},
getOriginKey: function() {
return window.checkoutConfig.payment.adyen.originKey;
},
getCheckoutEnvironment: function() {
return window.checkoutConfig.payment.adyen.checkoutEnvironment;
},
}); });
}, },
); );
...@@ -33,13 +33,11 @@ define( ...@@ -33,13 +33,11 @@ define(
'uiLayout', 'uiLayout',
'Magento_Ui/js/model/messages', 'Magento_Ui/js/model/messages',
'mage/url', 'mage/url',
'Adyen_Payment/js/threeds2-js-utils',
'Magento_Checkout/js/model/full-screen-loader', 'Magento_Checkout/js/model/full-screen-loader',
'Magento_Paypal/js/action/set-payment-method', 'Magento_Paypal/js/action/set-payment-method',
'Magento_Checkout/js/model/url-builder', 'Magento_Checkout/js/model/url-builder',
'mage/storage', 'mage/storage',
'Magento_Checkout/js/action/place-order', 'Magento_Checkout/js/action/place-order',
'Adyen_Payment/js/model/payment-details',
'Magento_Checkout/js/model/error-processor', 'Magento_Checkout/js/model/error-processor',
'Adyen_Payment/js/model/adyen-payment-service', 'Adyen_Payment/js/model/adyen-payment-service',
'Adyen_Payment/js/bundle', 'Adyen_Payment/js/bundle',
...@@ -58,13 +56,11 @@ function( ...@@ -58,13 +56,11 @@ function(
layout, layout,
Messages, Messages,
url, url,
threeDS2Utils,
fullScreenLoader, fullScreenLoader,
setPaymentMethodAction, setPaymentMethodAction,
urlBuilder, urlBuilder,
storage, storage,
placeOrderAction, placeOrderAction,
paymentDetails,
errorProcessor, errorProcessor,
adyenPaymentService, adyenPaymentService,
AdyenComponent AdyenComponent
...@@ -347,7 +343,7 @@ function( ...@@ -347,7 +343,7 @@ function(
onComplete: function(result) { onComplete: function(result) {
var request = result.data; var request = result.data;
request.orderId = orderId; request.orderId = orderId;
paymentDetails.process(request). adyenPaymentService.paymentDetails(request).
done(function(responseJSON) { done(function(responseJSON) {
self.validateThreeDS2OrPlaceOrder(responseJSON, self.validateThreeDS2OrPlaceOrder(responseJSON,
orderId); orderId);
...@@ -386,7 +382,7 @@ function( ...@@ -386,7 +382,7 @@ function(
fullScreenLoader.startLoader(); fullScreenLoader.startLoader();
var request = result.data; var request = result.data;
request.orderId = orderId; request.orderId = orderId;
paymentDetails.process(request). adyenPaymentService.paymentDetails(request).
done(function(responseJSON) { done(function(responseJSON) {
self.validateThreeDS2OrPlaceOrder(responseJSON, self.validateThreeDS2OrPlaceOrder(responseJSON,
orderId); orderId);
...@@ -413,7 +409,8 @@ function( ...@@ -413,7 +409,8 @@ function(
*/ */
getData: function() { getData: function() {
var self = this; var self = this;
var browserInfo = threeDS2Utils.getBrowserInfo(); // todo use state.data
var browserInfo = [];
return { return {
'method': self.method, 'method': self.method,
......
...@@ -15,38 +15,37 @@ ...@@ -15,38 +15,37 @@
* *
* Adyen Payment module (https://www.adyen.com/) * 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. * See LICENSE.txt for license details.
* *
* Author: Adyen <magento@adyen.com> * Author: Adyen <magento@adyen.com>
*/ */
--> -->
<!-- TODO check if it can be outsources to an adyen-methods template to be used by all the methods-->
<div id="ActionWrapper"> <div id="ActionWrapper">
<div id="ActionModal"> <div id="ActionModal">
<div id="ActionContainer"></div> <div id="ActionContainer"></div>
</div> </div>
</div> </div>
<!-- ko foreach: getAdyenHppPaymentMethods() -->
<div class="payment-method" data-bind="css: {'_active': (value == $parent.isBrandCodeChecked())}"> <!-- ko foreach: adyenPaymentMethods -->
<div class="payment-method" data-bind="css: {'_active': (brandCode == $parent.isBrandCodeChecked())}">
<div class="payment-method-title field choice"> <div class="payment-method-title field choice">
<input type="radio" <input type="radio"
name="payment[method]" name="payment[method]"
class="radio" class="radio"
data-bind="attr: {'id': value}, value: value, checked: $parent.isBrandCodeChecked, click: $parent.selectPaymentMethodBrandCode"/> data-bind="attr: {'id': 'adyen_' + brandCode}, value: 'adyen_' + brandCode, checked: brandCode == $parent.isBrandCodeChecked, click: $parent.selectPaymentMethodBrandCode"/>
<label data-bind="attr: {'for': value}" class="label"> <label data-bind="attr: {'for': 'adyen_' + brandCode}" class="label">
<!-- ko if: name.icon --> <!-- ko if: icon -->
<img data-bind="attr: { <img data-bind="attr: {
'src': name.icon.url, 'src': icon.url
'width': name.icon.url.width,
'height': name.icon.url.height
}"> }">
<!--/ko--> <!--/ko-->
<span data-bind="text: name.title"></span> <span data-bind="text: name"></span>
</label> </label>
</div> </div>
<div class="payment-method-content"> <div class="payment-method-content">
...@@ -56,7 +55,7 @@ ...@@ -56,7 +55,7 @@
<!-- ko template: getTemplate() --><!-- /ko --> <!-- ko template: getTemplate() --><!-- /ko -->
<!--/ko--> <!--/ko-->
<div> <div>
<span class="message message-error error hpp-message" data-bind="attr: {id: 'messages-' + value}"></span> <span class="message message-error error hpp-message" data-bind="attr: {id: 'messages-' + brandCode}"></span>
</div> </div>
<div class="payment-method-billing-address"> <div class="payment-method-billing-address">
...@@ -65,242 +64,10 @@ ...@@ -65,242 +64,10 @@
<!--/ko--> <!--/ko-->
</div> </div>
<form class="form" data-role="adyen-hpp-form" action="#" method="post" data-bind="mageInit: { 'validation':[]}, attr: {id: 'payment_form_' + $parent.getCode() + '_' + value}"> <form class="form" data-role="adyen-hpp-form" action="#" method="post" data-bind="mageInit: { 'validation':[]}, attr: {id: 'payment_form_' + $parent.getCode() + '_' + brandCode}">
<fieldset class="fieldset" data-bind='attr: {id: "payment_fieldset_" + $parent.getCode() + "_" + value}'> <fieldset class="fieldset" data-bind='attr: {id: "payment_fieldset_" + $parent.getCode() + "_" + brandCode}'>
<!-- ko if: hasIssuersAvailable() --> <div data-bind='attr: {id: "adyen-alternative-payment-container-" + brandCode}'
<!-- ko if: isIdeal() --> afterRender="renderCheckoutComponent()"></div>
<div class="checkout-component-dock" afterRender="renderIdealComponent()" data-bind="attr: { id: 'iDealContainer'}"></div>
<!--/ko-->
<!-- ko ifnot: isIdeal() -->
<label data-bind="attr: {'for': 'issuerId'}" class="label">
<span><!-- ko text: $t('Select Your Bank') --><!-- /ko --></span>
</label>
<div class="field">
<select name="payment[issuer_id]" data-bind="
options: getIssuers(),
optionsText: 'name',
optionsValue: 'id',
value: issuer,
optionsCaption: $t('Choose Your Bank')">
</select>
</div>
<!--/ko-->
<!--/ko-->
<!-- ko if: isPaymentMethodKlarna() -->
<!-- ko if: showSsn() -->
<div class="field ssn type required">
<label data-bind="attr: {for: getCode() + '_ssn_' + value}" class="adyen-checkout__label__text">
<span><!-- ko text: $t('Personal number')--><!-- /ko --></span>
</label>
<div class="control">
<input type="text" class="input-text"
name="payment[ssn]"
data-bind="
attr: {
id: getCode() + '_ssn_' + value,
title: $t('Social Security Number'),
'data-container': getCode() + '-ssn',
maxlength : getSsnLength()
},
value: ssn"
data-validate="{required:true}"
/>
</div>
</div>
<!--/ko-->
<div class="checkout-component-dock" afterRender="renderKlarnaComponent()" data-bind="attr: { id: 'klarnaContainer'}"></div>
<!--/ko-->
<!-- ko if: isPaymentMethodAfterPay() -->
<!-- ko if: showSsn() -->
<div class="field ssn type required">
<label data-bind="attr: {for: getCode() + '_ssn_' + value}" class="adyen-checkout__label__text">
<span><!-- ko text: $t('Personal number')--><!-- /ko --></span>
</label>
<div class="control">
<input type="text" class="input-text"
name="payment[ssn]"
data-bind="
attr: {
id: getCode() + '_ssn_' + value,
title: $t('Social Security Number'),
'data-container': getCode() + '-ssn',
maxlength : getSsnLength()
},
value: ssn"
data-validate="{required:true}"
/>
</div>
</div>
<!--/ko-->
<div class="checkout-component-dock" afterRender="renderAfterPayComponent()" data-bind="attr: { id: 'afterPayContainer'}"></div>
<!--/ko-->
<!-- ko if: isPaymentMethodOtherOpenInvoiceMethod() -->
<div class="field gender required">
<label data-bind="attr: {for: getCode() + '_gender_type_' + value}" class="label">
<span><!-- ko text: $t('Gender')--><!-- /ko --></span>
</label>
<div class="control">
<select class="select select-gender-type"
name="payment[gender]"
data-bind="attr: {id: getCode() + '_gender_type_' + value, 'data-container': getCode() + '-gender-type'},
options: $parent.getGenderTypes(),
optionsValue: 'key',
optionsText: 'value',
optionsCaption: $t('-Please select-'),
value: gender"
data-validate="{required:true}">
</select>
</div>
</div>
<div class="field dob type required">
<label data-bind="attr: {for: getCode() + '_dob_' + value}" class="label">
<span><!-- ko text: $t('Date of Birth')--><!-- /ko --></span>
</label>
<div class="control">
<input type="text" class="input-text"
name="payment[dob]"
data-bind="
attr: {
id: getCode() + '_dob_' + value,
title: $t('Date of Birth'),
'data-container': getCode() + '-dob_' + value,
},
datepicker: {
storage: datepickerValue,
options: { showOn: 'both', changeYear: true, yearRange: '-99:-1', defaultDate: '-20y' }
},
value: dob"
data-validate="{required:true}"
/>
</div>
</div>
<div class="field telephone type required">
<label data-bind="attr: {for: getCode() + '_telephone_' + value}" class="label">
<span><!-- ko text: $t('Telephone')--><!-- /ko --></span>
</label>
<div class="control">
<input type="number" class="input-text"
name="payment[telephone]"
data-bind="
attr: {
id: getCode() + '_telephone_' + value,
title: $t('Telephone'),
'data-container': getCode() + '-telephone_' + value,
'data-validate': JSON.stringify({'required-number':true})
},
value: telephone"
data-validate="{required:true}"
/>
</div>
</div>
<!-- ko if: showSsn() -->
<div class="field ssn type required">
<label data-bind="attr: {for: getCode() + '_ssn_' + value}" class="adyen-checkout__label__text">
<span><!-- ko text: $t('Personal number')--><!-- /ko --></span>
</label>
<div class="control">
<input type="text" class="input-text"
name="payment[ssn]"
data-bind="
attr: {
id: getCode() + '_ssn_' + value,
title: $t('Social Security Number'),
'data-container': getCode() + '-ssn',
maxlength : getSsnLength()
},
value: ssn"
data-validate="{required:true}"
/>
</div>
</div>
<!--/ko-->
<!--/ko-->
<!-- ko if: isSepaDirectDebit() -->
<div class="checkout-component-dock" afterRender="renderSepaDirectDebitComponent()" data-bind="attr: { id: 'sepaDirectDebitContainer'}"></div>
<!--/ko-->
<!-- ko if: isAch() -->
<div class="field ownerName type required">
<label data-bind="attr: {for: getCode() + '_ownerName_' + value}" class="label">
<span><!-- ko text: $t('Owner name')--><!-- /ko --></span>
</label>
<div class="control">
<input type="text" class="input-text"
name="payment[ownerName]"
data-bind="
attr: {
id: getCode() + '_ownerName_' + value,
title: $t('Owner name'),
'data-container': getCode() + '-ownerName_' + value,
'data-validate': JSON.stringify({'required':true})
},
value: ownerName"
data-validate="{required:true}"
/>
</div>
</div>
<div class="field bankAccountNumber type required">
<label data-bind="attr: {for: getCode() + '_bankAccountNumber_' + value}" class="label">
<span><!-- ko text: $t('Bank account number')--><!-- /ko --></span>
</label>
<div class="control">
<input type="number" class="input-text"
name="payment[bankAccountNumber]"
data-bind="
attr: {
id: getCode() + '_bankAccountNumber_' + value,
title: $t('Bank account number'),
'data-container': getCode() + '-bankAccountNumber_' + value,
'data-validate': JSON.stringify({'required':true}),
minlength: 3,
maxlength : getBankAccountNumberMaxLength()
},
value: bankAccountNumber"
data-validate="{required:true}"
/>
</div>
</div>
<div class="field bankLocationId type required">
<label data-bind="attr: {for: getCode() + '_bankLocationId_' + value}" class="label">
<span><!-- ko text: $t('Bank location ID')--><!-- /ko --></span>
</label>
<div class="control">
<input type="number" class="input-text"
name="payment[bankLocationId]"
data-bind="
attr: {
id: getCode() + '_bankLocationId_' + value,
title: $t('Bank location ID'),
'data-container': getCode() + '-bankLocationId_' + value,
'data-validate': JSON.stringify({'required-number':true}),
minlength: 9,
maxlength: 9
},
value: bankLocationId"
data-validate="{required:true}"
/>
</div>
</div>
<!--/ko-->
</fieldset> </fieldset>
<div class="checkout-agreements-block"> <div class="checkout-agreements-block">
...@@ -309,15 +76,13 @@ ...@@ -309,15 +76,13 @@
<!--/ko--> <!--/ko-->
</div> </div>
<div class="actions-toolbar"> <div class="actions-toolbar">
<div class="primary"> <div class="primary">
<button class="action primary checkout" <button class="action primary checkout"
type="submit" type="submit"
data-bind=" data-bind="
click: continueToAdyenBrandCode, click: continueToAdyenBrandCode,
enable: placeOrderAllowed() && (value == $parent.isBrandCodeChecked()), enable: placeOrderAllowed() && (brandCode == $parent.isBrandCodeChecked()),
css: {disabled: !$parent.isPlaceOrderActionAllowed()}" css: {disabled: !$parent.isPlaceOrderActionAllowed()}"
disabled> disabled>
<span data-bind="text: $t('Place Order')"></span> <span data-bind="text: $t('Place Order')"></span>
......
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