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

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

* Remove methodlist

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

* Rename retrieveAvailablePaymentMethods to getPaymentMethods

and return a promise instead of setting the methods when done

* Remove threeds2-js-utils.js and usage

* Add adyen-configuration.js

* Initialize checkout component and store it

* Use the new paymentMethods response from the component

* Move logic from CcAuthorizationDataBuilder to CheckoutDataBuilder

Remove CcAuthorizationDataBuilder.php

* require php api library version 8

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

* Move logic from ThreeDS2DataBuilder to CheckoutDataBuilder

Remove ThreeDS2DataBuilder.php

* Simplify adyen-cc-method.js

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

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

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

* Adjust the AdyenCcDataAssignObserver to handle the full state data

* Update generic component to the latest bundle

* Fix 3DS1 response in CheckoutResponseValidator.php

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

* Standardise cc and alternative payment methods observers

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

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

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

* Standardise alternative payment methods frontend

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

* Remove line item itemId from checkout requests

Version 64 does not support it anymore

* Adjust personal details request structure to checkout version 64

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

* Make paymentMethods array observable

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

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

* Fix code smells

* Fix removed dependency
Co-authored-by: default avatarÁngel Campos <angel.campos@adyen.com>
parent 8cf53d82
<?php
/**
* ######
* ######
* ############ ####( ###### #####. ###### ############ ############
* ############# #####( ###### #####. ###### ############# #############
* ###### #####( ###### #####. ###### ##### ###### ##### ######
* ###### ###### #####( ###### #####. ###### ##### ##### ##### ######
* ###### ###### #####( ###### #####. ###### ##### ##### ######
* ############# ############# ############# ############# ##### ######
* ############ ############ ############# ############ ##### ######
* ######
* #############
* ############
*
* Adyen Payment module (https://www.adyen.com/)
*
* Copyright (c) 2019 Adyen BV (https://www.adyen.com/)
* See LICENSE.txt for license details.
*
* Author: Adyen <magento@adyen.com>
*/
namespace Adyen\Payment\Gateway\Request;
use Magento\Payment\Gateway\Request\BuilderInterface;
use Adyen\Payment\Observer\AdyenCcDataAssignObserver;
class CcAuthorizationDataBuilder implements BuilderInterface
{
/**
* @param array $buildSubject
* @return mixed
*/
public function build(array $buildSubject)
{
/** @var \Magento\Payment\Gateway\Data\PaymentDataObject $paymentDataObject */
$paymentDataObject = \Magento\Payment\Gateway\Helper\SubjectReader::readPayment($buildSubject);
$payment = $paymentDataObject->getPayment();
$requestBody = [];
// If ccType is set use this. For bcmc you need bcmc otherwise it will fail
$requestBody['paymentMethod']['type'] = "scheme";
if ($cardNumber = $payment->getAdditionalInformation(AdyenCcDataAssignObserver::ENCRYPTED_CREDIT_CARD_NUMBER)) {
$requestBody['paymentMethod']['encryptedCardNumber'] = $cardNumber;
}
if ($expiryMonth = $payment->getAdditionalInformation(AdyenCcDataAssignObserver::ENCRYPTED_EXPIRY_MONTH)) {
$requestBody['paymentMethod']['encryptedExpiryMonth'] = $expiryMonth;
}
if ($expiryYear = $payment->getAdditionalInformation(AdyenCcDataAssignObserver::ENCRYPTED_EXPIRY_YEAR)) {
$requestBody['paymentMethod']['encryptedExpiryYear'] = $expiryYear;
}
if ($holderName = $payment->getAdditionalInformation(AdyenCcDataAssignObserver::HOLDER_NAME)) {
$requestBody['paymentMethod']['holderName'] = $holderName;
}
if ($securityCode = $payment->getAdditionalInformation(AdyenCcDataAssignObserver::ENCRYPTED_SECURITY_CODE)) {
$requestBody['paymentMethod']['encryptedSecurityCode'] = $securityCode;
}
// Remove from additional data
$payment->unsAdditionalInformation(AdyenCcDataAssignObserver::ENCRYPTED_CREDIT_CARD_NUMBER);
$payment->unsAdditionalInformation(AdyenCcDataAssignObserver::ENCRYPTED_EXPIRY_MONTH);
$payment->unsAdditionalInformation(AdyenCcDataAssignObserver::ENCRYPTED_EXPIRY_YEAR);
$payment->unsAdditionalInformation(AdyenCcDataAssignObserver::ENCRYPTED_SECURITY_CODE);
$payment->unsAdditionalInformation(AdyenCcDataAssignObserver::HOLDER_NAME);
// if installments is set and card type is credit card add it into the request
$numberOfInstallments = $payment->getAdditionalInformation(
AdyenCcDataAssignObserver::NUMBER_OF_INSTALLMENTS
) ?: 0;
$comboCardType = $payment->getAdditionalInformation(AdyenCcDataAssignObserver::COMBO_CARD_TYPE) ?: 'credit';
if ($numberOfInstallments > 0) {
$requestBody['installments']['value'] = $numberOfInstallments;
}
// if card type is debit then change the issuer type and unset the installments field
if ($comboCardType == 'debit') {
if ($selectedDebitBrand = $this->getSelectedDebitBrand($payment->getAdditionalInformation('cc_type'))) {
$requestBody['additionalData']['overwriteBrand'] = true;
$requestBody['selectedBrand'] = $selectedDebitBrand;
$requestBody['paymentMethod']['type'] = $selectedDebitBrand;
}
unset($requestBody['installments']);
}
$request['body'] = $requestBody;
return $request;
}
/**
* @param string $brand
* @return string
*/
private function getSelectedDebitBrand($brand)
{
if ($brand == 'VI') {
return 'electron';
}
if ($brand == 'MC') {
return 'maestro';
}
return null;
}
}
......@@ -23,6 +23,7 @@
namespace Adyen\Payment\Gateway\Request;
use Adyen\Payment\Observer\AdyenCcDataAssignObserver;
use Magento\Payment\Gateway\Request\BuilderInterface;
use Adyen\Payment\Observer\AdyenHppDataAssignObserver;
......@@ -77,22 +78,13 @@ class CheckoutDataBuilder implements BuilderInterface
$payment = $paymentDataObject->getPayment();
$order = $payment->getOrder();
$storeId = $order->getStoreId();
$requestBody = [];
// Initialize the request body with the validated state data
$requestBody = $payment->getAdditionalInformation(AdyenCcDataAssignObserver::STATE_DATA);
// do not send email
$order->setCanSendNewEmailFlag(false);
$requestBodyPaymentMethod['type'] = $payment->getAdditionalInformation(
AdyenHppDataAssignObserver::BRAND_CODE
);
// Additional data for payment methods with issuer list
if ($payment->getAdditionalInformation(AdyenHppDataAssignObserver::ISSUER_ID)) {
$requestBodyPaymentMethod['issuer'] = $payment->getAdditionalInformation(
AdyenHppDataAssignObserver::ISSUER_ID
);
}
$requestBody['returnUrl'] = $this->storeManager->getStore()->getBaseUrl(
\Magento\Framework\UrlInterface::URL_TYPE_LINK
) . 'adyen/process/result';
......@@ -110,46 +102,6 @@ class CheckoutDataBuilder implements BuilderInterface
$requestBody['bankAccount']['ownerName'] = $payment->getAdditionalInformation("bankAccountOwnerName");
}
// Additional data for open invoice payment
if ($payment->getAdditionalInformation("gender")) {
$order->setCustomerGender(
$this->gender->getMagentoGenderFromAdyenGender(
$payment->getAdditionalInformation("gender")
)
);
$requestBodyPaymentMethod['personalDetails']['gender'] = $payment->getAdditionalInformation("gender");
}
if ($payment->getAdditionalInformation("dob")) {
$order->setCustomerDob($payment->getAdditionalInformation("dob"));
$requestBodyPaymentMethod['personalDetails']['dateOfBirth'] = $this->adyenHelper->formatDate(
$payment->getAdditionalInformation("dob"),
'Y-m-d'
);
}
if ($payment->getAdditionalInformation("telephone")) {
$order->getBillingAddress()->setTelephone($payment->getAdditionalInformation("telephone"));
$requestBodyPaymentMethod['personalDetails']['telephoneNumber'] = $payment->getAdditionalInformation(
"telephone"
);
}
if ($payment->getAdditionalInformation("ssn")) {
$requestBodyPaymentMethod['personalDetails']['socialSecurityNumber'] =
$payment->getAdditionalInformation("ssn");
}
// Additional data for sepa direct debit
if ($payment->getAdditionalInformation("ownerName")) {
$requestBodyPaymentMethod['sepa.ownerName'] = $payment->getAdditionalInformation("ownerName");
}
if ($payment->getAdditionalInformation("ibanNumber")) {
$requestBodyPaymentMethod['sepa.ibanNumber'] = $payment->getAdditionalInformation("ibanNumber");
}
if ($this->adyenHelper->isPaymentMethodOpenInvoiceMethod(
$payment->getAdditionalInformation(AdyenHppDataAssignObserver::BRAND_CODE)
) || $this->adyenHelper->isPaymentMethodAfterpayTouchMethod(
......@@ -213,12 +165,55 @@ class CheckoutDataBuilder implements BuilderInterface
$order->setCanSendNewEmailFlag(true);
}
$requestBody['paymentMethod'] = $requestBodyPaymentMethod;
// if installments is set and card type is credit card add it into the request
$numberOfInstallments = $payment->getAdditionalInformation(
AdyenCcDataAssignObserver::NUMBER_OF_INSTALLMENTS
) ?: 0;
$comboCardType = $payment->getAdditionalInformation(AdyenCcDataAssignObserver::COMBO_CARD_TYPE) ?: 'credit';
if ($numberOfInstallments > 0) {
$requestBody['installments']['value'] = $numberOfInstallments;
}
// if card type is debit then change the issuer type and unset the installments field
if ($comboCardType == 'debit') {
if ($selectedDebitBrand = $this->getSelectedDebitBrand($payment->getAdditionalInformation('cc_type'))) {
$requestBody['additionalData']['overwriteBrand'] = true;
$requestBody['selectedBrand'] = $selectedDebitBrand;
$requestBody['paymentMethod']['type'] = $selectedDebitBrand;
}
unset($requestBody['installments']);
}
if ($this->adyenHelper->isCreditCardThreeDS2Enabled($storeId)) {
$requestBody['additionalData']['allow3DS2'] = true;
}
$requestBody['origin'] = $this->adyenHelper->getOrigin($storeId);
$requestBody['channel'] = 'web';
if (isset($requestBodyPaymentMethod)) {
$requestBody['paymentMethod'] = $requestBodyPaymentMethod;
}
$request['body'] = $requestBody;
return $request;
}
/**
* @param string $brand
* @return string
*/
private function getSelectedDebitBrand($brand)
{
if ($brand == 'VI') {
return 'electron';
}
if ($brand == 'MC') {
return 'maestro';
}
return null;
}
/**
* @param \Magento\Sales\Model\Order $order
*
......@@ -251,7 +246,6 @@ class CheckoutDataBuilder implements BuilderInterface
$formFields['lineItems'][] = [
'id' => $item->getId(),
'itemId' => $item->getId(),
'amountExcludingTax' => $formattedPriceExcludingTax,
'taxAmount' => $formattedTaxAmount,
'description' => $item->getName(),
......@@ -301,7 +295,7 @@ class CheckoutDataBuilder implements BuilderInterface
}
$formFields['lineItems'][] = [
'itemId' => 'shippingCost',
'id' => 'shippingCost',
'amountExcludingTax' => $formattedPriceExcludingTax,
'taxAmount' => $formattedTaxAmount,
'description' => $order->getShippingDescription(),
......
......@@ -67,9 +67,10 @@ class CheckoutResponseValidator extends AbstractValidator
$payment->setAdditionalInformation('3dActive', false);
$isValid = true;
$errorMessages = [];
$resultCode = $response['resultCode'];
// validate result
if (!empty($resultCode)) {
if (!empty($response['resultCode'])) {
$resultCode = $response['resultCode'];
$payment->setAdditionalInformation('resultCode', $resultCode);
if (!empty($response['action'])) {
......@@ -149,59 +150,8 @@ class CheckoutResponseValidator extends AbstractValidator
case "ChallengeShopper":
case "PresentToShopper":
case 'Pending':
// nothing extra
break;
case "RedirectShopper":
// todo check if needed
if (
isset($response['redirect']['data']['PaReq']) &&
isset($response['redirect']['data']['MD'])
) {
$paReq = null;
$md = null;
$payment->setAdditionalInformation('3dActive', true);
if (!empty($response['redirect']['data']['PaReq'])) {
$paReq = $response['redirect']['data']['PaReq'];
}
if (!empty($response['redirect']['data']['MD'])) {
$md = $response['redirect']['data']['MD'];
}
if ($paReq && $md && $redirectUrl && $paymentData && $redirectMethod) {
$payment->setAdditionalInformation('redirectUrl', $redirectUrl);
$payment->setAdditionalInformation('redirectMethod', $redirectMethod);
$payment->setAdditionalInformation('paRequest', $paReq);
$payment->setAdditionalInformation('md', $md);
$payment->setAdditionalInformation('paymentData', $paymentData);
} else {
$isValid = false;
$errorMsg = __('3D secure is not valid.');
$this->adyenLogger->error($errorMsg);
$errorMessages[] = $errorMsg;
}
// otherwise it is an alternative payment method which only requires the
// redirect url to be present
} else {
// Flag to show we are in the checkoutAPM flow
$payment->setAdditionalInformation('checkoutAPM', true);
if (!empty($response['details'])) {
$payment->setAdditionalInformation('details', $response['details']);
}
if ($redirectUrl && $paymentData && $redirectMethod) {
$payment->setAdditionalInformation('redirectUrl', $redirectUrl);
$payment->setAdditionalInformation('redirectMethod', $redirectMethod);
$payment->setAdditionalInformation('paymentData', $paymentData);
} else {
$isValid = false;
$errorMsg = __('Payment method is not valid.');
$this->adyenLogger->error($errorMsg);
$errorMessages[] = $errorMsg;
}
}
// nothing extra
break;
case "Refused":
$errorMsg = __('The payment is REFUSED.');
......
......@@ -116,51 +116,24 @@ class Requests extends AbstractHelper
// In case of virtual product and guest checkout there is a workaround to get the guest's email address
if (!empty($additionalData['guestEmail'])) {
if ($this->adyenHelper->isPaymentMethodOpenInvoiceMethod($paymentMethod) &&
!$this->adyenHelper->isPaymentMethodAfterpayTouchMethod($paymentMethod)
) {
$request['paymentMethod']['personalDetails']['shopperEmail'] = $additionalData['guestEmail'];
} else {
$request['shopperEmail'] = $additionalData['guestEmail'];
}
$request['shopperEmail'] = $additionalData['guestEmail'];
}
if (!empty($billingAddress)) {
// Openinvoice (klarna and afterpay BUT not afterpay touch) methods requires different request format
if ($this->adyenHelper->isPaymentMethodOpenInvoiceMethod($paymentMethod) &&
!$this->adyenHelper->isPaymentMethodAfterpayTouchMethod($paymentMethod)
) {
if ($customerEmail = $billingAddress->getEmail()) {
$request['paymentMethod']['personalDetails']['shopperEmail'] = $customerEmail;
}
if ($customerTelephone = trim($billingAddress->getTelephone())) {
$request['paymentMethod']['personalDetails']['telephoneNumber'] = $customerTelephone;
}
if ($firstName = $billingAddress->getFirstname()) {
$request['paymentMethod']['personalDetails']['firstName'] = $firstName;
}
if ($lastName = $billingAddress->getLastname()) {
$request['paymentMethod']['personalDetails']['lastName'] = $lastName;
}
} else {
if ($customerEmail = $billingAddress->getEmail()) {
$request['shopperEmail'] = $customerEmail;
}
if ($customerEmail = $billingAddress->getEmail()) {
$request['shopperEmail'] = $customerEmail;
}
if ($customerTelephone = trim($billingAddress->getTelephone())) {
$request['telephoneNumber'] = $customerTelephone;
}
if ($customerTelephone = trim($billingAddress->getTelephone())) {
$request['telephoneNumber'] = $customerTelephone;
}
if ($firstName = $billingAddress->getFirstname()) {
$request['shopperName']['firstName'] = $firstName;
}
if ($firstName = $billingAddress->getFirstname()) {
$request['shopperName']['firstName'] = $firstName;
}
if ($lastName = $billingAddress->getLastname()) {
$request['shopperName']['lastName'] = $lastName;
}
if ($lastName = $billingAddress->getLastname()) {
$request['shopperName']['lastName'] = $lastName;
}
if ($countryId = $billingAddress->getCountryId()) {
......@@ -325,38 +298,6 @@ class Requests extends AbstractHelper
return $request;
}
/**
* @param array $request
* @param $additionalData
* @param $storeId
* @return array
*/
public function buildThreeDS2Data($additionalData, $storeId, $request = [])
{
if ($this->adyenHelper->isCreditCardThreeDS2Enabled($storeId)) {
$request['additionalData']['allow3DS2'] = true;
$request['origin'] = $this->adyenHelper->getOrigin($storeId);
$request['channel'] = 'web';
$request['browserInfo']['screenWidth'] = $additionalData[AdyenCcDataAssignObserver::SCREEN_WIDTH];
$request['browserInfo']['screenHeight'] = $additionalData[AdyenCcDataAssignObserver::SCREEN_HEIGHT];
$request['browserInfo']['colorDepth'] = $additionalData[AdyenCcDataAssignObserver::SCREEN_COLOR_DEPTH];
$request['browserInfo']['timeZoneOffset'] = $additionalData[AdyenCcDataAssignObserver::TIMEZONE_OFFSET];
$request['browserInfo']['language'] = $additionalData[AdyenCcDataAssignObserver::LANGUAGE];
if ($javaEnabled = $additionalData[AdyenCcDataAssignObserver::JAVA_ENABLED]) {
$request['browserInfo']['javaEnabled'] = $javaEnabled;
} else {
$request['browserInfo']['javaEnabled'] = false;
}
} else {
$request['additionalData']['allow3DS2'] = false;
$request['origin'] = $this->adyenHelper->getOrigin($storeId);
$request['channel'] = 'web';
}
return $request;
}
/**
* @param array $request
* @return array
......
......@@ -73,6 +73,9 @@ class AdyenGenericConfigProvider implements ConfigProviderInterface
$config['payment']['adyen']['checkoutEnvironment'] = $this->adyenHelper->getCheckoutEnvironment(
$this->storeManager->getStore()->getId()
);
$config['payment']['adyen']['locale'] = $this->adyenHelper->getStoreLocale(
$this->storeManager->getStore()->getId()
);
return $config;
}
......
......@@ -15,7 +15,7 @@
*
* Adyen Payment module (https://www.adyen.com/)
*
* Copyright (c) 2015 Adyen BV (https://www.adyen.com/)
* Copyright (c) 2020 Adyen BV (https://www.adyen.com/)
* See LICENSE.txt for license details.
*
* Author: Adyen <magento@adyen.com>
......@@ -26,77 +26,92 @@ namespace Adyen\Payment\Observer;
use Magento\Framework\Event\Observer;
use Magento\Payment\Observer\AbstractDataAssignObserver;
use Magento\Quote\Api\Data\PaymentInterface;
use \Adyen\Service\Validator\CheckoutStateDataValidator;
use \Adyen\Service\Validator\DataArrayValidator;
class AdyenCcDataAssignObserver extends AbstractDataAssignObserver
{
const CC_TYPE = 'cc_type';
const NUMBER_OF_INSTALLMENTS = 'number_of_installments';
const STORE_CC = 'store_cc';
const ENCRYPTED_CREDIT_CARD_NUMBER = 'number';
const ENCRYPTED_SECURITY_CODE = 'cvc';
const ENCRYPTED_EXPIRY_MONTH = 'expiryMonth';
const ENCRYPTED_EXPIRY_YEAR = 'expiryYear';
const HOLDER_NAME = 'holderName';
const VARIANT = 'variant';
const JAVA_ENABLED = 'java_enabled';
const SCREEN_COLOR_DEPTH = 'screen_color_depth';
const SCREEN_WIDTH = 'screen_width';
const SCREEN_HEIGHT = 'screen_height';
const TIMEZONE_OFFSET = 'timezone_offset';
const LANGUAGE = 'language';
const GUEST_EMAIL = 'guestEmail';
const COMBO_CARD_TYPE = 'combo_card_type';
const STATE_DATA = 'stateData';
/**
* Approved root level keys from additional data array
*
* @var array
*/
protected $additionalInformationList = [
self::CC_TYPE,
self::NUMBER_OF_INSTALLMENTS,
self::STORE_CC,
self::ENCRYPTED_CREDIT_CARD_NUMBER,
self::ENCRYPTED_SECURITY_CODE,
self::ENCRYPTED_EXPIRY_MONTH,
self::ENCRYPTED_EXPIRY_YEAR,
self::HOLDER_NAME,
self::VARIANT,
self::JAVA_ENABLED,
self::SCREEN_COLOR_DEPTH,
self::SCREEN_WIDTH,
self::SCREEN_HEIGHT,
self::TIMEZONE_OFFSET,
self::LANGUAGE,
private static $approvedAdditionalDataKeys = [
self::STATE_DATA,
self::GUEST_EMAIL,
self::COMBO_CARD_TYPE
self::COMBO_CARD_TYPE,
self::NUMBER_OF_INSTALLMENTS
];
/**
* @var CheckoutStateDataValidator
*/
protected $checkoutStateDataValidator;
/**
* AdyenCcDataAssignObserver constructor.
*
* @param CheckoutStateDataValidator $checkoutStateDataValidator
*/
public function __construct(
CheckoutStateDataValidator $checkoutStateDataValidator
) {
$this->checkoutStateDataValidator = $checkoutStateDataValidator;
}
/**
* @param Observer $observer
* @return void
*/
public function execute(Observer $observer)
{
// Get request fields
$data = $this->readDataArgument($observer);
// Get additional data array
$additionalData = $data->getData(PaymentInterface::KEY_ADDITIONAL_DATA);
if (!is_array($additionalData)) {
return;
}
$paymentInfo = $this->readPaymentModelArgument($observer);
// Get a validated additional data array
$additionalData = DataArrayValidator::getArrayOnlyWithApprovedKeys(
$additionalData,
self::$approvedAdditionalDataKeys
);
// set ccType
if (!empty($additionalData['cc_type'])) {
$paymentInfo->setCcType($additionalData['cc_type']);
// json decode state data
$stateData = [];
if (!empty($additionalData[self::STATE_DATA])) {
$stateData = json_decode($additionalData[self::STATE_DATA], true);
}
// Get validated state data array
if (!empty($stateData)) {
$stateData = $this->checkoutStateDataValidator->getValidatedAdditionalData(
$stateData
);
}
// Replace state data with the decoded and validated state data
$additionalData[self::STATE_DATA] = $stateData;
// Set additional data in the payment
$paymentInfo = $this->readPaymentModelArgument($observer);
foreach ($additionalData as $key => $data) {
$paymentInfo->setAdditionalInformation($key, $data);
}
foreach ($this->additionalInformationList as $additionalInformationKey) {
if (isset($additionalData[$additionalInformationKey])) {
$paymentInfo->setAdditionalInformation(
$additionalInformationKey,
$additionalData[$additionalInformationKey]
);
}
// set ccType
if (!empty($additionalData[self::CC_TYPE])) {
$paymentInfo->setCcType($additionalData[self::CC_TYPE]);
}
}
}
......@@ -15,7 +15,7 @@
*
* Adyen Payment module (https://www.adyen.com/)
*
* Copyright (c) 2015 Adyen BV (https://www.adyen.com/)
* Copyright (c) 2020 Adyen BV (https://www.adyen.com/)
* See LICENSE.txt for license details.
*
* Author: Adyen <magento@adyen.com>
......@@ -23,6 +23,8 @@
namespace Adyen\Payment\Observer;
use Adyen\Service\Validator\CheckoutStateDataValidator;
use Adyen\Service\Validator\DataArrayValidator;
use Magento\Framework\Event\Observer;
use Magento\Payment\Observer\AbstractDataAssignObserver;
use Magento\Quote\Api\Data\PaymentInterface;
......@@ -30,62 +32,84 @@ use Magento\Quote\Api\Data\PaymentInterface;
class AdyenHppDataAssignObserver extends AbstractDataAssignObserver
{
const BRAND_CODE = 'brand_code';
const ISSUER_ID = 'issuer_id';
const GENDER = 'gender';
const DOB = 'dob';
const TELEPHONE = 'telephone';
const DF_VALUE = 'df_value';
const SSN = 'ssn';
const OWNER_NAME = 'ownerName';
const BANK_ACCOUNT_OWNER_NAME = 'bankAccountOwnerName';
const IBAN_NUMBER = 'ibanNumber';
const BANK_ACCOUNT_NUMBER = 'bankAccountNumber';
const BANK_LOCATIONID = 'bankLocationId';
const GUEST_EMAIL = 'guestEmail';
const STATE_DATA = 'stateData';
/**
* Approved root level keys from additional data array
*
* @var array
*/
protected $additionalInformationList = [
private static $approvedAdditionalDataKeys = [
self::BRAND_CODE,
self::ISSUER_ID,
self::GENDER,
self::DOB,
self::TELEPHONE,
self::DF_VALUE,
self::SSN,
self::OWNER_NAME,
self::BANK_ACCOUNT_OWNER_NAME,
self::IBAN_NUMBER,
self::BANK_ACCOUNT_NUMBER,
self::BANK_LOCATIONID
self::GUEST_EMAIL,
self::STATE_DATA
];
/**
* @var CheckoutStateDataValidator
*/
protected $checkoutStateDataValidator;
/**
* AdyenHppDataAssignObserver constructor.
*
* @param CheckoutStateDataValidator $checkoutStateDataValidator
*/
public function __construct(
CheckoutStateDataValidator $checkoutStateDataValidator
) {
$this->checkoutStateDataValidator = $checkoutStateDataValidator;
}
/**
* @param Observer $observer
* @return void
*/
public function execute(Observer $observer)
{
// Get request fields
$data = $this->readDataArgument($observer);
// Get additional data array
$additionalData = $data->getData(PaymentInterface::KEY_ADDITIONAL_DATA);
if (!is_array($additionalData)) {
return;
}
$paymentInfo = $this->readPaymentModelArgument($observer);
// Get a validated additional data array
$additionalData = DataArrayValidator::getArrayOnlyWithApprovedKeys(
$additionalData,
self::$approvedAdditionalDataKeys
);
if (isset($additionalData[self::BRAND_CODE])) {
$paymentInfo->setCcType($additionalData[self::BRAND_CODE]);
// json decode state data
$stateData = [];
if (!empty($additionalData[self::STATE_DATA])) {
$stateData = json_decode($additionalData[self::STATE_DATA], true);
}
// Get validated state data array
if (!empty($stateData)) {
$stateData = $this->checkoutStateDataValidator->getValidatedAdditionalData(
$stateData
);
}
// Replace state data with the decoded and validated state data
$additionalData[self::STATE_DATA] = $stateData;
// Set additional data in the payment
$paymentInfo = $this->readPaymentModelArgument($observer);
foreach ($additionalData as $key => $data) {
$paymentInfo->setAdditionalInformation($key, $data);
}
foreach ($this->additionalInformationList as $additionalInformationKey) {
if (isset($additionalData[$additionalInformationKey])) {
$paymentInfo->setAdditionalInformation(
$additionalInformationKey,
$additionalData[$additionalInformationKey]
);
}
// set ccType
if (!empty($additionalData[self::BRAND_CODE])) {
$paymentInfo->setCcType($additionalData[self::BRAND_CODE]);
}
}
}
......@@ -14,7 +14,7 @@
}
],
"require": {
"adyen/php-api-library": "^6.3",
"adyen/php-api-library": "^8",
"magento/framework": ">=101.0.8 <102 || >=102.0.1",
"magento/module-vault": "101.*",
"magento/module-paypal": ">=100.2.6",
......
......@@ -557,9 +557,8 @@
<item name="payment" xsi:type="string">Adyen\Payment\Gateway\Request\PaymentDataBuilder</item>
<item name="browserinfo" xsi:type="string">Adyen\Payment\Gateway\Request\BrowserInfoDataBuilder</item>
<item name="recurring" xsi:type="string">Adyen\Payment\Gateway\Request\RecurringDataBuilder</item>
<item name="transaction" xsi:type="string">Adyen\Payment\Gateway\Request\CcAuthorizationDataBuilder</item>
<item name="transaction" xsi:type="string">Adyen\Payment\Gateway\Request\CheckoutDataBuilder</item>
<item name="vault" xsi:type="string">Adyen\Payment\Gateway\Request\VaultDataBuilder</item>
<item name="threeds2" xsi:type="string">Adyen\Payment\Gateway\Request\ThreeDS2DataBuilder</item>
</argument>
</arguments>
</virtualType>
......@@ -606,7 +605,7 @@
<item name="browserinfo" xsi:type="string">Adyen\Payment\Gateway\Request\BrowserInfoDataBuilder</item>
<item name="recurring" xsi:type="string">Adyen\Payment\Gateway\Request\RecurringDataBuilder</item>
<item name="oneclick" xsi:type="string">Adyen\Payment\Gateway\Request\OneclickAuthorizationDataBuilder</item>
<item name="threeds2" xsi:type="string">Adyen\Payment\Gateway\Request\ThreeDS2DataBuilder</item>
<item name="transaction" xsi:type="string">Adyen\Payment\Gateway\Request\CheckoutDataBuilder</item>
<item name="redirect" xsi:type="string">Adyen\Payment\Gateway\Request\RedirectDataBuilder</item>
</argument>
</arguments>
......
This 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 @@
*
* Adyen Payment module (https://www.adyen.com/)
*
* Copyright (c) 2019 Adyen BV (https://www.adyen.com/)
* Copyright (c) 2020 Adyen BV (https://www.adyen.com/)
* See LICENSE.txt for license details.
*
* Author: Adyen <magento@adyen.com>
*/
namespace Adyen\Payment\Gateway\Request;
use Magento\Payment\Gateway\Request\BuilderInterface;
class ThreeDS2DataBuilder implements BuilderInterface
{
/**
* @var \Magento\Framework\App\State
*/
private $appState;
/**
* @var \Adyen\Payment\Helper\Requests
*/
private $adyenRequestsHelper;
/**
* ThreeDS2DataBuilder constructor.
*
* @param \Magento\Framework\Model\Context $context
* @param \Adyen\Payment\Helper\Requests $adyenRequestsHelper
*/
public function __construct(
\Magento\Framework\Model\Context $context,
\Adyen\Payment\Helper\Requests $adyenRequestsHelper
) {
$this->appState = $context->getAppState();
$this->adyenRequestsHelper = $adyenRequestsHelper;
define(
[
],
function () {
'use strict';
return {
getOriginKey: function () {
return window.checkoutConfig.payment.adyen.originKey;
},
showLogo: function () {
return window.checkoutConfig.payment.adyen.showLogo;
},
getLocale: function () {
return window.checkoutConfig.payment.adyen.locale;
},
getCheckoutEnvironment: function () {
return window.checkoutConfig.payment.adyen.checkoutEnvironment;
},
};
}
/**
* @param array $buildSubject
* @return array
* @throws \Magento\Framework\Exception\LocalizedException
*/
public function build(array $buildSubject)
{
/** @var \Magento\Payment\Gateway\Data\PaymentDataObject $paymentDataObject */
$paymentDataObject = \Magento\Payment\Gateway\Helper\SubjectReader::readPayment($buildSubject);
$payment = $paymentDataObject->getPayment();
$order = $paymentDataObject->getOrder();
$additionalInformation = $payment->getAdditionalInformation();
$request['body'] = $this->adyenRequestsHelper->buildThreeDS2Data(
$additionalInformation,
$order->getStoreId(),
[]
);
return $request;
}
}
);
/**
* Copyright © 2015 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
define(
[
'ko'
],
function (ko) {
'use strict';
return ko.observableArray([]);
}
);
......@@ -6,74 +6,86 @@ define(
[
'underscore',
'Magento_Checkout/js/model/quote',
'Adyen_Payment/js/model/adyen-method-list',
'Magento_Customer/js/model/customer',
'Magento_Checkout/js/model/url-builder',
'mage/storage'
'mage/storage',
'Adyen_Payment/js/bundle',
'ko'
],
function (_, quote, methodList, customer, urlBuilder, storage) {
function(
_,
quote,
customer,
urlBuilder,
storage,
adyenComponent,
ko
) {
'use strict';
return {
paymentMethods: ko.observable({}),
/**
* Populate the list of payment methods
* @param {Array} methods
*/
setPaymentMethods: function (methods) {
methodList(methods);
},
/**
* Get the list of available payment methods.
* @returns {Array}
*/
getAvailablePaymentMethods: function () {
return methodList();
},
/**
* Retrieve the list of available payment methods from the server
* Retrieve the list of available payment methods from Adyen
*/
retrieveAvailablePaymentMethods: function (callback) {
var self = this;
retrievePaymentMethods: function() {
// url for guest users
var serviceUrl = urlBuilder.createUrl(
'/guest-carts/:cartId/retrieve-adyen-payment-methods', {
cartId: quote.getQuoteId(),
});
// retrieve payment methods
var serviceUrl,
payload;
// url for logged in users
if (customer.isLoggedIn()) {
serviceUrl = urlBuilder.createUrl('/carts/mine/retrieve-adyen-payment-methods', {});
} else {
serviceUrl = urlBuilder.createUrl('/guest-carts/:cartId/retrieve-adyen-payment-methods', {
cartId: quote.getQuoteId()
});
serviceUrl = urlBuilder.createUrl(
'/carts/mine/retrieve-adyen-payment-methods', {});
}
payload = {
// Construct payload for the retrieve payment methods request
var payload = {
cartId: quote.getQuoteId(),
shippingAddress: quote.shippingAddress()
shippingAddress: quote.shippingAddress(),
};
storage.post(
return storage.post(
serviceUrl,
JSON.stringify(payload)
).done(
function (response) {
self.setPaymentMethods(response);
if (callback) {
callback();
}
}
).fail(
function () {
self.setPaymentMethods([]);
}
)
JSON.stringify(payload),
);
},
getOrderPaymentStatus: function (orderId) {
var serviceUrl = urlBuilder.createUrl('/adyen/orders/:orderId/payment-status', {
orderId: orderId
});
getPaymentMethods: function() {
return this.paymentMethods;
},
setPaymentMethods: function(paymentMethods) {
this.paymentMethods(paymentMethods);
},
getOrderPaymentStatus: function(orderId) {
var serviceUrl = urlBuilder.createUrl(
'/adyen/orders/:orderId/payment-status', {
orderId: orderId,
});
return storage.get(serviceUrl);
},
/**
* The results that the components returns in the onComplete callback needs to be sent to the
* backend to the /adyen/paymentDetails endpoint and based on the response render a new
* component or place the order (validateThreeDS2OrPlaceOrder)
* @param response
*/
paymentDetails: function(data) {
var payload = {
'payload': JSON.stringify(data),
};
var serviceUrl = urlBuilder.createUrl('/adyen/paymentDetails',
{});
return storage.post(
serviceUrl,
JSON.stringify(payload),
true,
);
}
};
}
},
);
/**
* Copyright © 2015 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
define(
[
'Magento_Checkout/js/model/url-builder',
'mage/storage'
],
function (urlBuilder, storage) {
'use strict';
return {
/**
* The results that the components returns in the onComplete callback needs to be sent to the
* backend to the /adyen/paymentDetails endpoint and based on the response render a new
* component or place the order (validateThreeDS2OrPlaceOrder)
* @param response
*/
process: function (data) {
var payload = {
"payload": JSON.stringify(data)
};
var serviceUrl = urlBuilder.createUrl('/adyen/paymentDetails', {});
return storage.post(
serviceUrl,
JSON.stringify(payload),
true
);
}
};
}
);
!function(e,n){"object"===typeof exports&&"object"===typeof module?module.exports=n():"function"===typeof define&&define.amd?define([],n):"object"===typeof exports?exports.ThreedDS2Utils=n():e.ThreedDS2Utils=n()}(this,function(){return function(e){var n={};function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}return t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:r})},t.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,n){if(1&n&&(e=t(e)),8&n)return e;if(4&n&&"object"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(t.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var o in e)t.d(r,o,function(n){return e[n]}.bind(null,o));return r},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},t.p="",t(t.s=0)}([function(e,n,t){"use strict";t.r(n);var r={container:void 0},o={"01":["250px","400px"],"02":["390px","400px"],"03":["500px","600px"],"04":["600px","400px"],"05":["100%","100%"]};function a(e){return o.hasOwnProperty(e)?e:"01"}var i={createIframe:function(e,n){var t=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"0",o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"0",a=arguments.length>4?arguments[4]:void 0;if(!n||0===n.length)throw new Error("Name parameter missing for iframe");e instanceof HTMLElement?r.container=e:r.container=document.body;var i=document.createElement("iframe");i.classList.add(n+"Class"),i.width=t,i.height=o,i.name=n,i.setAttribute("frameborder","0"),i.setAttribute("border","0");var d=document.createTextNode("<p>Your browser does not support iframes.</p>");return i.appendChild(d),r.container.appendChild(i),function(e,n){e.attachEvent?e.attachEvent("onload",function(){n&&"function"===typeof n&&n(e.contentWindow)}):e.onload=function(){n&&"function"===typeof n&&n(e.contentWindow)}}(i,a),i},createForm:function(e,n,t,r,o){if(!e||!n||!t||!r||!o)throw new Error("Not all required parameters provided for form creation");if(0===e.length||0===n.length||0===t.length||0===r.length||0===o.length)throw new Error("Not all required parameters have suitable values");var a=document.createElement("form");a.style.display="none",a.name=e,a.action=n,a.method="POST",a.target=t;var i=document.createElement("input");return i.name=r,i.value=o,a.appendChild(i),a},getBrowserInfo:function(){var e=window&&window.screen?window.screen.width:"",n=window&&window.screen?window.screen.height:"",t=window&&window.screen?window.screen.colorDepth:"",r=window&&window.navigator?window.navigator.userAgent:"",o=!(!window||!window.navigator)&&navigator.javaEnabled(),a="";return window&&window.navigator&&(a=window.navigator.language?window.navigator.language:window.navigator.browserLanguage),{screenWidth:e,screenHeight:n,colorDepth:t,userAgent:r,timeZoneOffset:(new Date).getTimezoneOffset(),language:a,javaEnabled:o}},base64Url:{encode:function(e){var n=window.btoa(e).split("=")[0];return n=(n=n.replace("/+/g","-")).replace("///g","_")},decode:function(e){var n=e;switch((n=(n=n.replace("/-/g","+")).replace("/_/g","/")).length%4){case 0:break;case 2:n+="==";break;case 3:n+="=";break;default:window.console&&window.console.log&&window.console.log("### base64url::decodeBase64URL:: Illegal base64url string!")}try{return window.atob(n)}catch(e){throw new Error(e)}}},config:{challengeWindowSizes:o,validateChallengeWindowSize:a,getChallengeWindowSize:function(e){return o[a(e)]},THREEDS_METHOD_TIMEOUT:1e4,CHALLENGE_TIMEOUT:6e5}};n.default=i}]).default});
\ No newline at end of file
......@@ -24,11 +24,19 @@
define(
[
'uiComponent',
'Magento_Checkout/js/model/payment/renderer-list'
'Magento_Checkout/js/model/payment/renderer-list',
'Adyen_Payment/js/model/adyen-payment-service',
'Adyen_Payment/js/model/adyen-configuration',
'Magento_Checkout/js/model/quote',
'Magento_Checkout/js/model/full-screen-loader',
],
function (
Component,
rendererList
rendererList,
adyenPaymentService,
adyenConfiguration,
quote,
fullScreenLoader
) {
'use strict';
rendererList.push(
......@@ -66,6 +74,25 @@ define(
initialize: function () {
this._super();
var shippingAddressCountry = "";
quote.shippingAddress.subscribe(function(address) {
// In case the country hasn't changed don't retrieve new payment methods
if (shippingAddressCountry === quote.shippingAddress().countryId) {
return;
}
shippingAddressCountry = quote.shippingAddress().countryId;
fullScreenLoader.startLoader();
// Retrieve adyen payment methods
adyenPaymentService.retrievePaymentMethods().done(function(paymentMethods) {
paymentMethods = JSON.parse(paymentMethods);
adyenPaymentService.setPaymentMethods(paymentMethods);
fullScreenLoader.stopLoader();
}).fail(function() {
})
});
if (this.isGooglePayEnabled()) {
var googlepayscript = document.createElement('script');
googlepayscript.src = "https://pay.google.com/gp/p/js/pay.js";
......
......@@ -14,7 +14,7 @@
*
* Adyen Payment module (https://www.adyen.com/)
*
* Copyright (c) 2015 Adyen BV (https://www.adyen.com/)
* Copyright (c) 2020 Adyen BV (https://www.adyen.com/)
* See LICENSE.txt for license details.
*
* Author: Adyen <magento@adyen.com>
......@@ -25,95 +25,75 @@ define(
'ko',
'Magento_Payment/js/view/payment/cc-form',
'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/quote',
'Adyen_Payment/js/model/installments',
'mage/url',
'Magento_Vault/js/view/payment/vault-enabler',
'Magento_Checkout/js/model/url-builder',
'mage/storage',
'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',
'Adyen_Payment/js/model/adyen-payment-service',
'Adyen_Payment/js/model/adyen-configuration',
'Adyen_Payment/js/bundle'
],
function (
function(
$,
ko,
Component,
customer,
creditCardData,
additionalValidators,
quote,
installmentsHelper,
url,
VaultEnabler,
urlBuilder,
storage,
fullScreenLoader,
setPaymentMethodAction,
selectPaymentMethodAction,
threeDS2Utils,
paymentDetails,
errorProcessor,
adyenPaymentService,
AdyenComponent
adyenConfiguration,
adyenComponent
) {
'use strict';
return Component.extend({
// 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'),
defaults: {
template: 'Adyen_Payment/payment/cc-form',
creditCardOwner: '',
storeCc: false,
installment: '',
creditCardDetailsValid: false,
orderId: 0
installment: '', // keep it until the component implements installments
orderId: 0, // TODO is this the best place to store it?
},
/**
* @returns {exports.initialize}
*/
initialize: function () {
initialize: function() {
this._super();
this.vaultEnabler = new VaultEnabler();
this.vaultEnabler.setPaymentCode(this.getVaultCode());
this.vaultEnabler.isActivePaymentTokenEnabler(false);
// initialize adyen component for general use
this.checkout = new AdyenCheckout({
hasHolderName: true,
locale: this.getLocale(),
originKey: this.getOriginKey(),
environment: this.getCheckoutEnvironment(),
onAdditionalDetails: this.handleOnAdditionalDetails.bind(this)
});
this.checkoutComponent = new AdyenCheckout({
hasHolderName: true,
locale: adyenConfiguration.getLocale(),
originKey: adyenConfiguration.getOriginKey(),
environment: adyenConfiguration.getCheckoutEnvironment(),
paymentMethodsResponse: adyenPaymentService.getPaymentMethods().paymentMethodsResponse,
onAdditionalDetails: this.handleOnAdditionalDetails.bind(this)
},
);
return this;
},
initObservable: function () {
this._super()
.observe([
'creditCardType',
'creditCardOwner',
'creditCardNumber',
'securityCode',
'expiryMonth',
'expiryYear',
'installment',
'installments',
'creditCardDetailsValid',
'placeOrderAllowed'
]);
initObservable: function() {
this._super().observe([
'creditCardType', // TODO check if still needed
'installment',
'installments',
'placeOrderAllowed',
]);
return this;
},
......@@ -121,8 +101,10 @@ define(
* Returns true if card details can be stored
* @returns {*|boolean}
*/
getEnableStoreDetails: function () {
return this.canCreateBillingAgreement() && !this.isVaultEnabled();
getEnableStoreDetails: function() {
// TODO refactor the configuration for this
return this.canCreateBillingAgreement() &&
!this.isVaultEnabled();
},
/**
* Renders the secure fields,
......@@ -130,7 +112,7 @@ define(
* sets up the callbacks for card components and
* set up the installments
*/
renderSecureFields: function () {
renderSecureFields: function() {
var self = this;
if (!self.getOriginKey()) {
......@@ -141,36 +123,24 @@ define(
// installments
var allInstallments = self.getAllInstallments();
var cardNode = document.getElementById('cardContainer');
self.cardComponent = self.checkout.create('card', {
type: 'card',
self.cardComponent = self.checkoutComponent.create('card', {
enableStoreDetails: self.getEnableStoreDetails(),
groupTypes: self.getAvailableCardTypeAltCodes(),
onChange: function (state, component) {
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);
}
onChange: function(state, component) {
self.placeOrderAllowed(!!state.isValid);
},
onBrand: function (state) {
// Keep onBrand as is until checkout component supports installments
onBrand: function(state) {
// Define the 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 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 = [];
if (creditCardType in allInstallments) {
......@@ -180,7 +150,9 @@ define(
var precision = quote.getPriceFormat().precision;
var currencyCode = quote.totals().quote_currency_code;
numberOfInstallments = installmentsHelper.getInstallmentsWithPrices(installmentCreditcard, grandTotal, precision, currencyCode);
numberOfInstallments = installmentsHelper.getInstallmentsWithPrices(
installmentCreditcard, grandTotal,
precision, currencyCode);
}
if (numberOfInstallments) {
......@@ -191,24 +163,25 @@ define(
}
// for BCMC as this is not a core payment method inside magento use maestro as brand detection
if (creditCardType == "BCMC") {
self.creditCardType("MI");
if (creditCardType == 'BCMC') {
self.creditCardType('MI');
} else {
self.creditCardType(creditCardType);
}
} else {
self.creditCardType("")
self.creditCardType('');
self.installments(0);
}
}
}).mount(cardNode);
}).mount('#cardContainer');
},
handleAction: function (action, orderId) {
handleAction: function(action, orderId) {
var self = this;
try {
var component = self.checkout.createFromAction(action).mount('#cc_actionContainer');
self.checkoutComponent.createFromAction(
action).mount('#cc_actionContainer');
} catch (e) {
console.log(e);
}
......@@ -217,44 +190,31 @@ define(
* 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
*/
closeModal: function (popupModal) {
popupModal.modal("closeModal");
closeModal: function(popupModal) {
popupModal.modal('closeModal');
$('.cc_actionModal').remove();
$('.modals-overlay').remove();
$('body').removeClass('_has-modal');
// reconstruct the threeDS2Modal container again otherwise component can not find the threeDS2Modal
$('#cc_actionModalWrapper').append("<div id=\"cc_actionModal\">" +
"<div id=\"cc_actionContainer\"></div>" +
"</div>");
$('#cc_actionModalWrapper').
append('<div id="cc_actionModal">' +
'<div id="cc_actionContainer"></div>' +
'</div>');
},
/**
* Get data for place order
* @returns {{method: *}}
*/
getData: function () {
var browserInfo = threeDS2Utils.getBrowserInfo();
getData: function() {
var data = {
'method': this.item.method,
additional_data: {
'stateData': JSON.stringify(this.cardComponent.data),
'guestEmail': quote.guestEmail,
'cc_type': this.creditCardType(),
'number': this.creditCardNumber(),
'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()
}
'combo_card_type': this.comboCardOption(),
},
};
this.vaultEnabler.visitAdditionalData(data);
return data;
......@@ -263,8 +223,11 @@ define(
* Returns state of place order button
* @returns {boolean}
*/
isButtonActive: function () {
return this.isActive() && this.getCode() == this.isChecked() && this.isPlaceOrderActionAllowed() && this.placeOrderAllowed();
isButtonActive: function() {
// TODO check if isPlaceOrderActionAllowed and placeOrderAllowed are both needed
return this.isActive() && this.getCode() == this.isChecked() &&
this.isPlaceOrderActionAllowed() &&
this.placeOrderAllowed();
},
/**
* Custom place order function
......@@ -275,7 +238,7 @@ define(
* @param event
* @returns {boolean}
*/
placeOrder: function (data, event) {
placeOrder: function(data, event) {
var self = this;
if (event) {
......@@ -286,21 +249,21 @@ define(
fullScreenLoader.startLoader();
self.isPlaceOrderActionAllowed(false);
self.getPlaceOrderDeferredObject()
.fail(
function () {
fullScreenLoader.stopLoader();
self.isPlaceOrderActionAllowed(true);
}
).done(
function (orderId) {
self.getPlaceOrderDeferredObject().fail(
function() {
fullScreenLoader.stopLoader();
self.isPlaceOrderActionAllowed(true);
},
).done(
function(orderId) {
self.afterPlaceOrder();
self.orderId = orderId;
adyenPaymentService.getOrderPaymentStatus(orderId)
.done(function (responseJSON) {
self.handleAdyenResult(responseJSON, orderId)
adyenPaymentService.getOrderPaymentStatus(orderId).
done(function(responseJSON) {
self.handleAdyenResult(responseJSON,
orderId);
});
}
},
);
}
return false;
......@@ -309,21 +272,22 @@ define(
* Based on the response we can start a 3DS2 validation or place the order
* @param responseJSON
*/
handleAdyenResult: function (responseJSON, orderId) {
handleAdyenResult: function(responseJSON, orderId) {
var self = this;
var response = JSON.parse(responseJSON);
if (!!response.isFinal) {
// Status is final redirect to the redirectUrl
window.location.replace(url.build(
window.checkoutConfig.payment[quote.paymentMethod().method].redirectUrl
window.checkoutConfig.payment[quote.paymentMethod().method].redirectUrl,
));
} else {
// Handle action
self.handleAction(response.action, orderId);
}
},
handleOnAdditionalDetails: function (result) {
handleOnAdditionalDetails: function(result) {
var self = this;
var request = result.data;
......@@ -331,6 +295,7 @@ define(
fullScreenLoader.stopLoader();
// TODO outsource creating the modal
var popupModal = $('#cc_actionModal').modal({
// disable user to hide popup
clickableOverlay: false,
......@@ -338,72 +303,54 @@ define(
innerScroll: false,
// empty buttons, we don't need that
buttons: [],
modalClass: 'cc_actionModal'
modalClass: 'cc_actionModal',
});
popupModal.modal("openModal");
popupModal.modal('openModal');
paymentDetails.process(request).done(function (responseJSON) {
self.handleAdyenResult(responseJSON, self.orderId)
}).fail(function (result) {
errorProcessor.process(result, self.messageContainer);
self.isPlaceOrderActionAllowed(true);
fullScreenLoader.stopLoader();
});
adyenPaymentService.paymentDetails(request).
done(function(responseJSON) {
self.handleAdyenResult(responseJSON, self.orderId);
}).
fail(function(response) {
errorProcessor.process(response, self.messageContainer);
self.isPlaceOrderActionAllowed(true);
fullScreenLoader.stopLoader();
});
},
/**
* Validates the payment date when clicking the pay button
*
* @returns {boolean}
*/
validate: function () {
var self = this;
validate: function() {
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) {
this.cardComponent.showValidation();
return false;
}
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
*
* @param altCode
* @returns {*}
*/
getCcCodeByAltCode: function (altCode) {
getCcCodeByAltCode: function(altCode) {
var ccTypes = window.checkoutConfig.payment.ccform.availableTypesByAlt[this.getCode()];
if (ccTypes.hasOwnProperty(altCode)) {
return ccTypes[altCode];
}
return "";
return '';
},
/**
* Get available card types translated to the Adyen card type codes
......@@ -411,7 +358,8 @@ define(
*
* @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()];
return Object.keys(ccTypes);
},
......@@ -420,52 +368,30 @@ define(
*
* @returns {*}
*/
getCode: function () {
getCode: function() {
return window.checkoutConfig.payment.adyenCc.methodCode;
},
getOriginKey: 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 () {
canCreateBillingAgreement: function() {
if (customer.isLoggedIn()) {
return window.checkoutConfig.payment.adyenCc.canCreateBillingAgreement;
}
return false;
},
isShowLegend: function () {
return true;
},
showLogo: function () {
return window.checkoutConfig.payment.adyen.showLogo;
},
getIcons: function (type) {
return window.checkoutConfig.payment.adyenCc.icons.hasOwnProperty(type)
getIcons: function(type) {
return window.checkoutConfig.payment.adyenCc.icons.hasOwnProperty(
type)
? window.checkoutConfig.payment.adyenCc.icons[type]
: false
: false;
},
hasInstallments: function () {
return this.comboCardOption() === 'credit' && window.checkoutConfig.payment.adyenCc.hasInstallments;
hasInstallments: function() {
return this.comboCardOption() === 'credit' &&
window.checkoutConfig.payment.adyenCc.hasInstallments;
},
getAllInstallments: function () {
getAllInstallments: function() {
return window.checkoutConfig.payment.adyenCc.installments;
},
areComboCardsEnabled: function () {
areComboCardsEnabled: function() {
if (quote.billingAddress() === null) {
return false;
}
......@@ -473,32 +399,52 @@ define(
var currencyCode = quote.totals().quote_currency_code;
var allowedCurrenciesByCountry = {
'BR': 'BRL',
'MX': 'MXN'
'MX': 'MXN',
};
return allowedCurrenciesByCountry[countryId] &&
currencyCode === allowedCurrenciesByCountry[countryId];
},
setPlaceOrderHandler: function (handler) {
this.placeOrderHandler = handler;
},
setValidateHandler: function (handler) {
this.validateHandler = handler;
},
context: function () {
return this;
getOriginKey: function() {
return adyenConfiguration.getOriginKey();
},
/**
* @returns {Bool}
*/
isVaultEnabled: function () {
isVaultEnabled: function() {
return this.vaultEnabler.isVaultEnabled();
},
/**
* @returns {String}
*/
getVaultCode: function () {
getVaultCode: function() {
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,31 +14,28 @@
*
* Adyen Payment module (https://www.adyen.com/)
*
* Copyright (c) 2015 Adyen BV (https://www.adyen.com/)
* Copyright (c) 2020 Adyen BV (https://www.adyen.com/)
* See LICENSE.txt for license details.
*
* Author: Adyen <magento@adyen.com>
*/
define(
[
'ko',
'jquery',
'Magento_Checkout/js/view/payment/default',
'Magento_Checkout/js/action/select-payment-method',
'Magento_Checkout/js/model/quote',
'Magento_Checkout/js/checkout-data',
'Magento_Checkout/js/model/payment/additional-validators',
'mage/storage',
'Magento_Checkout/js/model/url-builder',
'Adyen_Payment/js/model/adyen-payment-service',
'Magento_Customer/js/model/customer',
'Magento_Checkout/js/model/full-screen-loader',
'Magento_Checkout/js/action/place-order',
'uiLayout',
'Magento_Ui/js/model/messages',
'Adyen_Payment/js/model/payment-details',
'Magento_Checkout/js/model/error-processor',
'Adyen_Payment/js/bundle'
'ko',
'jquery',
'Magento_Checkout/js/view/payment/default',
'Magento_Checkout/js/action/select-payment-method',
'Magento_Checkout/js/model/quote',
'Magento_Checkout/js/checkout-data',
'Magento_Checkout/js/model/payment/additional-validators',
'Adyen_Payment/js/model/adyen-payment-service',
'Magento_Checkout/js/model/full-screen-loader',
'Magento_Checkout/js/action/place-order',
'uiLayout',
'Magento_Ui/js/model/messages',
'Magento_Checkout/js/model/error-processor',
'Adyen_Payment/js/bundle',
'Adyen_Payment/js/model/adyen-configuration',
],
function(
ko,
......@@ -48,814 +45,540 @@ define(
quote,
checkoutData,
additionalValidators,
storage,
urlBuilder,
adyenPaymentService,
customer,
fullScreenLoader,
placeOrderAction,
layout,
Messages,
paymentDetails,
errorProcessor,
AdyenComponent
AdyenComponent,
adyenConfiguration,
) {
'use strict';
var brandCode = ko.observable(null);
var paymentMethod = ko.observable(null);
var shippingAddressCountryCode = quote.shippingAddress().countryId;
var unsupportedPaymentMethods = [
'scheme',
'boleto',
'bcmc_mobile_QR',
'wechatpay',
/^bcmc$/,
'applepay',
'paywithgoogle'];
var popupModal;
/**
* Shareble adyen checkout component
* @type {AdyenCheckout}
*/
var checkoutComponent;
var orderId;
return Component.extend({
self: this,
defaults: {
template: 'Adyen_Payment/payment/hpp-form',
brandCode: '',
},
initObservable: function() {
this._super().observe([
'brandCode',
'issuer',
'gender',
'dob',
'telephone',
'ownerName',
'ibanNumber',
'ssn',
'bankAccountNumber',
'bankLocationId',
]);
return this;
}, initialize: function() {
var self = this;
this._super();
fullScreenLoader.startLoader();
/**
* Create sherable checkout component
* @type {AdyenCheckout}
*/
self.checkoutComponent = new AdyenCheckout({
locale: self.getLocale(),
onAdditionalDetails: self.handleOnAdditionalDetails.bind(self),
originKey: self.getOriginKey(),
environment: self.getCheckoutEnvironment(),
});
// reset variable:
adyenPaymentService.setPaymentMethods();
adyenPaymentService.retrieveAvailablePaymentMethods(function() {
var paymentMethods = adyenPaymentService.getAvailablePaymentMethods();
if (JSON.stringify(paymentMethods).indexOf('ratepay') > -1) {
var ratePayId = window.checkoutConfig.payment.adyenHpp.ratePayId;
var dfValueRatePay = self.getRatePayDeviceIdentToken();
window.di = {
t: dfValueRatePay.replace(':', ''),
v: ratePayId,
l: 'Checkout',
};
// Load Ratepay script
var ratepayScriptTag = document.createElement('script');
ratepayScriptTag.src = '//d.ratepay.com/' + ratePayId + '/di.js';
ratepayScriptTag.type = 'text/javascript';
document.body.appendChild(ratepayScriptTag);
}
fullScreenLoader.stopLoader();
});
},
getAdyenHppPaymentMethods: function() {
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 paymentList = _.reduce(paymentMethods,
function(accumulator, value) {
if (!self.isPaymentMethodSupported(value.type)) {
return accumulator;
}
'use strict';
var unsupportedPaymentMethods = [
'scheme',
'boleto',
'bcmc_mobile_QR',
'wechatpay',
/^bcmc$/,
'applepay',
'paywithgoogle'];
var popupModal;
var brandCode = ko.observable(null);
var paymentMethod = ko.observable(null);
return Component.extend({
self: this,
defaults: {
template: 'Adyen_Payment/payment/hpp-form',
orderId: 0,
paymentMethods: {}
},
initObservable: function() {
this._super().observe([
'brandCode',
'paymentMethod',
'adyenPaymentMethods'
]);
return this;
},
initialize: function() {
var self = this;
this._super();
var messageContainer = new Messages();
var name = 'messages-' +
self.getBrandCodeFromPaymentMethod(value);
var messagesComponent = {
parent: self.name,
name: name,
displayArea: name,
component: 'Magento_Ui/js/view/messages',
config: {
messageContainer: messageContainer,
},
};
layout([messagesComponent]);
fullScreenLoader.startLoader();
var result = {};
var paymentMethodsObserver = adyenPaymentService.getPaymentMethods();
/**
* Returns the payment method's brand code (in checkout api it is the type)
* @returns {*}
*/
result.getBrandCode = function() {
return self.getBrandCodeFromPaymentMethod(value);
};
paymentMethodsObserver.subscribe(function(paymentMethodsResponse) {
self.loadAdyenPaymentMethods(paymentMethodsResponse);
});
result.value = result.getBrandCode();
result.name = value;
result.method = self.item.method;
/**
* Observable to enable and disable place order buttons for payment methods
* Default value is true to be able to send the real hpp requiests that doesn't require any input
* @type {observable}
*/
result.placeOrderAllowed = ko.observable(true);
result.getCode = function() {
return self.item.method;
};
result.getMessageName = function() {
return 'messages-' +
self.getBrandCodeFromPaymentMethod(value);
};
result.getMessageContainer = function() {
return messageContainer;
};
result.validate = function() {
return self.validate(result.getBrandCode());
};
result.placeRedirectOrder = function placeRedirectOrder(data) {
// Place Order but use our own redirect url after
fullScreenLoader.startLoader();
$('.hpp-message').slideUp();
self.isPlaceOrderActionAllowed(false);
$.when(
placeOrderAction(data, self.currentMessageContainer),
).fail(
function(response) {
self.isPlaceOrderActionAllowed(true);
fullScreenLoader.stopLoader();
self.showErrorMessage(response);
},
).done(
function(orderId) {
self.afterPlaceOrder();
adyenPaymentService.getOrderPaymentStatus(orderId).
done(function(responseJSON) {
self.validateActionOrPlaceOrder(responseJSON,
orderId);
});
},
);
};
/**
* Set and get if the place order action is allowed
* Sets the placeOrderAllowed observable and the original isPlaceOrderActionAllowed as well
* @param bool
* @returns {*}
*/
result.isPlaceOrderAllowed = function(bool) {
self.isPlaceOrderActionAllowed(bool);
return result.placeOrderAllowed(bool);
};
result.afterPlaceOrder = function() {
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;
}
self.loadAdyenPaymentMethods(paymentMethodsObserver());
},
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),
},
);
// Needed until the new ratepay component is released
if (JSON.stringify(paymentMethods).indexOf('ratepay') >
-1) {
var ratePayId = window.checkoutConfig.payment.adyenHpp.ratePayId;
var dfValueRatePay = self.getRatePayDeviceIdentToken();
// TODO check if still needed with checkout component
window.di = {
t: dfValueRatePay.replace(':', ''),
v: ratePayId,
l: 'Checkout',
};
// Load Ratepay script
var ratepayScriptTag = document.createElement('script');
ratepayScriptTag.src = '//d.ratepay.com/' + ratePayId +
'/di.js';
ratepayScriptTag.type = 'text/javascript';
document.body.appendChild(ratepayScriptTag);
}
return false;
};
/**
* Checks if payment method is after pay
* @returns {boolean}
*/
result.isPaymentMethodAfterPay = function() {
if (result.getBrandCode() === 'afterpay_default') {
return true;
}
self.adyenPaymentMethods(self.getAdyenHppPaymentMethods(paymentMethodsResponse));
fullScreenLoader.stopLoader();
}
},
getAdyenHppPaymentMethods: function(paymentMethodsResponse) {
var self = this;
return false;
};
/**
* Checks if payment method is after pay touch
* @returns {boolean}
*/
result.isPaymentMethodAfterPayTouch = function() {
if (result.getBrandCode() === 'afterpaytouch') {
return true;
}
var paymentMethods = paymentMethodsResponse.paymentMethodsResponse.paymentMethods;
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;
}
var paymentList = _.reduce(paymentMethods,
function(accumulator, paymentMethod) {
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;
}
if (!self.isPaymentMethodSupported(
paymentMethod.type)) {
return accumulator;
}
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;
}
var messageContainer = new Messages();
var name = 'messages-' +
self.getBrandCodeFromPaymentMethod(paymentMethod);
var messagesComponent = {
parent: self.name,
name: name,
displayArea: name,
component: 'Magento_Ui/js/view/messages',
config: {
messageContainer: messageContainer,
},
};
layout([messagesComponent]);
var result = {};
/**
* Returns the payment method's brand code (in checkout api it is the type)
* @returns {*}
*/
result.getBrandCode = function() {
return self.getBrandCodeFromPaymentMethod(
paymentMethod);
};
result.brandCode = result.getBrandCode();
result.name = paymentMethod.name;
result.icon = {}; // TODO get icon details
result.method = self.item.method;
/**
* Observable to enable and disable place order buttons for payment methods
* Default value is true to be able to send the real hpp requiests that doesn't require any input
* @type {observable}
*/
result.placeOrderAllowed = ko.observable(true);
result.getCode = function() {
return self.item.method;
};
result.getMessageName = function() {
return 'messages-' +
self.getBrandCodeFromPaymentMethod(
paymentMethod);
};
result.getMessageContainer = function() {
return messageContainer;
};
result.validate = function() {
return self.validate(result.getBrandCode());
};
result.placeRedirectOrder = function placeRedirectOrder(data) {
// Place Order but use our own redirect url after
fullScreenLoader.startLoader();
$('.hpp-message').slideUp();
self.isPlaceOrderActionAllowed(false);
$.when(
placeOrderAction(data,
self.currentMessageContainer),
).fail(
function(response) {
self.isPlaceOrderActionAllowed(true);
fullScreenLoader.stopLoader();
self.showErrorMessage(response);
},
).done(
function(orderId) {
self.afterPlaceOrder();
adyenPaymentService.getOrderPaymentStatus(
orderId).
done(function(responseJSON) {
self.validateActionOrPlaceOrder(
responseJSON,
orderId);
});
},
);
};
/**
* Set and get if the place order action is allowed
* Sets the placeOrderAllowed observable and the original isPlaceOrderActionAllowed as well
* @param bool
* @returns {*}
*/
result.isPlaceOrderAllowed = function(bool) {
self.isPlaceOrderActionAllowed(bool);
return result.placeOrderAllowed(bool);
};
result.afterPlaceOrder = function() {
return self.afterPlaceOrder();
};
/**
* Renders the secure fields,
* creates the ideal component,
* sets up the callbacks for ideal components and
*/
result.renderCheckoutComponent = function() {
result.isPlaceOrderAllowed(false);
return false;
};
/**
* Checks if payment method is ACH
* @returns {boolean}
*/
result.isAch = function() {
if (result.getBrandCode().indexOf('ach') == 0) {
return true;
}
var showPayButton = false;
const showPayButtonPaymentMethods = [
'paypal',
'applePay',
'googlePay'
];
if (showPayButtonPaymentMethods.includes(
paymentMethod.type)) {
showPayButton = true;
}
// TODO take the terms and confitions magento checkbox into account as well
// If the details are empty and the pay button does not needs to be rendered by the component
// simply skip rendering the adyen checkout component
if (!paymentMethod.details && !showPayButton) {
result.isPlaceOrderAllowed(true);
return;
}
var city = '';
var country = '';
var postalCode = '';
var street = '';
var firstName = '';
var lastName = '';
var telephone = '';
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,
},
billingAddress: {
city: city,
country: country,
houseNumberOrName: '',
postalCode: postalCode,
street: street,
},
},
onChange: function(state) {
result.isPlaceOrderAllowed(state.isValid);
}
});
return false;
};
/**
* Checks if payment method is sepa direct debit
*/
result.isSepaDirectDebit = function() {
if (result.getBrandCode().indexOf('sepadirectdebit') >= 0) {
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() {
// set payment method to adyen_hpp
var self = this;
if (this.validate() &&
additionalValidators.validate()) {
var data = {};
data.method = self.method;
var additionalData = {};
additionalData.brand_code = self.brandCode;
let stateData;
if ('component' in self) {
stateData = self.component.data;
} else {
stateData = {
paymentMethod: {
type: self.brandCode
}
};
}
additionalData.stateData = JSON.stringify(stateData);
if (brandCode() == 'ratepay') {
additionalData.df_value = this.getRatePayDeviceIdentToken();
}
data.additional_data = additionalData;
this.placeRedirectOrder(data);
}
return false;
};
result.getRatePayDeviceIdentToken = function() {
return window.checkoutConfig.payment.adyenHpp.deviceIdentToken;
};
accumulator.push(result);
return accumulator;
}, []);
return paymentList;
},
/**
* Some payment methods we do not want to render as it requires extra implementation
* or is already implemented in a separate payment method.
* Using a match as we want to prevent to render all Boleto and most of the WeChat types
* @param paymentMethod
* @returns {boolean}
*/
isPaymentMethodSupported: function(paymentMethod) {
if (paymentMethod == 'wechatpayWeb') {
return true;
}
return false;
};
/**
* Renders the secure fields,
* creates the ideal component,
* sets up the callbacks for ideal components and
*/
result.renderIdealComponent = function() {
result.isPlaceOrderAllowed(false);
var idealNode = document.getElementById('iDealContainer');
var ideal = self.checkoutComponent.create('ideal', {
items: result.getIssuers(),
onChange: function(state) {
if (!!state.isValid) {
result.issuer(state.data.paymentMethod.issuer);
result.isPlaceOrderAllowed(true);
} else {
result.isPlaceOrderAllowed(false);
}
}
for (var i = 0; i < unsupportedPaymentMethods.length; i++) {
var match = paymentMethod.match(
unsupportedPaymentMethods[i]);
if (match) {
return false;
}
}
return true;
},
selectPaymentMethodBrandCode: function() {
var self = this;
// set payment method to adyen_hpp
var data = {
'method': self.method,
'po_number': null,
'additional_data': {
brand_code: self.brandCode,
},
});
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(
'sepadirectdebit', {
countryCode: self.getLocale(),
onChange: function(state) {
if (!!state.isValid) {
result.ownerName(
state.data.paymentMethod['sepa.ownerName']);
result.ibanNumber(
state.data.paymentMethod['sepa.ibanNumber']);
result.isPlaceOrderAllowed(true);
} else {
result.isPlaceOrderAllowed(false);
}
},
});
// set the brandCode
brandCode(self.brandCode);
sepaDirectDebit.mount(sepaDirectDebitNode);
};
// set payment method
paymentMethod(self.method);
/**
* 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;
}
var klarnaNode = document.getElementById('klarnaContainer');
var klarna = self.checkoutComponent.create('klarna', {
countryCode: self.getLocale(),
details: self.filterOutOpenInvoiceComponentDetails(
value.details),
visibility: {
personalDetails: 'editable',
},
onChange: function(state) {
if (!!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(klarnaNode);
};
selectPaymentMethodAction(data);
checkoutData.setSelectedPaymentMethod(self.method);
/**
* 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) {
if (!!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'));
};
return true;
},
/**
* This method is a workaround to close the modal in the right way and reconstruct the ActionModal.
* This will solve issues when you cancel the 3DS2 challenge and retry the payment
*/
closeModal: function(popupModal) {
popupModal.modal('closeModal');
$('.ActionModal').remove();
$('.modals-overlay').remove();
$('body').removeClass('_has-modal');
// reconstruct the ActionModal container again otherwise component can not find the ActionModal
$('#ActionWrapper').append('<div id="ActionModal">' +
'<div id="ActionContainer"></div>' +
'</div>');
},
isBrandCodeChecked: ko.computed(function() {
result.continueToAdyenBrandCode = function() {
// set payment method to adyen_hpp
var self = this;
if (this.validate() && additionalValidators.validate()) {
var data = {};
data.method = self.method;
var additionalData = {};
additionalData.brand_code = self.value;
if (self.hasIssuersAvailable()) {
additionalData.issuer_id = this.issuer();
} else if (self.isPaymentMethodOpenInvoiceMethod()) {
additionalData.gender = this.gender();
additionalData.dob = this.dob();
additionalData.telephone = this.telephone();
additionalData.ssn = this.ssn();
if (brandCode() == 'ratepay') {
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();
}
if (!quote.paymentMethod()) {
return null;
}
data.additional_data = additionalData;
this.placeRedirectOrder(data);
}
if (quote.paymentMethod().method == paymentMethod()) {
return brandCode();
}
return null;
}),
/**
* Based on the response we can start a action component or redirect
* @param responseJSON
*/
validateActionOrPlaceOrder: function(responseJSON, orderId) {
var self = this;
var response = JSON.parse(responseJSON);
if (!!response.isFinal) {
// Status is final redirect to the redirectUrl
$.mage.redirect(
window.checkoutConfig.payment[quote.paymentMethod().method].redirectUrl,
);
} else {
// render component
self.orderId = orderId;
self.renderActionComponent(response.action);
}
},
return false;
};
renderActionComponent: function(action) {
var self = this;
var actionNode = document.getElementById('ActionContainer');
fullScreenLoader.stopLoader();
self.popupModal = $('#ActionModal').modal({
// disable user to hide popup
clickableOverlay: false,
responsive: true,
innerScroll: false,
// empty buttons, we don't need that
buttons: [],
modalClass: 'ActionModal',
});
self.popupModal.modal('openModal');
self.actionComponent = self.checkoutComponent.createFromAction(
action).mount(actionNode);
},
handleOnAdditionalDetails: function(state, component) {
var self = this;
// call endpoint with state.data
var request = state.data;
request.orderId = self.orderId;
// Using the same processor as 3DS2, refactor to generic name in a upcomming release will be breaking change for merchants.
adyenPaymentService.paymentDetails(request).done(function() {
$.mage.redirect(
window.checkoutConfig.payment[quote.paymentMethod().method].redirectUrl,
);
}).fail(function(response) {
fullScreenLoader.stopLoader();
self.closeModal(self.popupModal);
errorProcessor.process(response,
self.currentMessageContainer);
self.isPlaceOrderActionAllowed(true);
self.showErrorMessage(response);
});
},
/**
* Issue with the default currentMessageContainer needs to be resolved for now just throw manually the eror message
* @param response
*/
showErrorMessage: function(response) {
if (!!response['responseJSON'].parameters) {
$('#messages-' + brandCode()).
text((response['responseJSON'].message).replace('%1',
response['responseJSON'].parameters[0])).
slideDown();
} else {
$('#messages-' + brandCode()).
text(response['responseJSON'].message).
slideDown();
}
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() {
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;
}
}
}
setTimeout(function() {
$('#messages-' + brandCode()).slideUp();
}, 10000);
},
validate: function(brandCode) {
var form = '#payment_form_' + this.getCode() + '_' + brandCode;
var validate = $(form).validation() &&
$(form).validation('isValid');
if (!validate) {
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);
return accumulator;
}, []);
return paymentList;
},
/**
* Some payment methods we do not want to render as it requires extra implementation
* or is already implemented in a separate payment method.
* Using a match as we want to prevent to render all Boleto and most of the WeChat types
* @param paymentMethod
* @returns {boolean}
*/
isPaymentMethodSupported: function(paymentMethod) {
if (paymentMethod == 'wechatpayWeb') {
return true;
}
for (var i = 0; i < unsupportedPaymentMethods.length; i++) {
var match = paymentMethod.match(unsupportedPaymentMethods[i]);
if (match) {
return false;
}
}
return true;
},
getGenderTypes: function() {
return _.map(window.checkoutConfig.payment.adyenHpp.genderTypes,
function(value, key) {
return {
'key': key,
'value': value,
};
});
},
selectPaymentMethodBrandCode: function() {
var self = this;
// set payment method to adyen_hpp
var data = {
'method': self.method,
'po_number': null,
'additional_data': {
brand_code: self.value,
return true;
},
};
// set the brandCode
brandCode(self.value);
// set payment method
paymentMethod(self.method);
selectPaymentMethodAction(data);
checkoutData.setSelectedPaymentMethod(self.method);
return true;
},
/**
* This method is a workaround to close the modal in the right way and reconstruct the ActionModal.
* This will solve issues when you cancel the 3DS2 challenge and retry the payment
*/
closeModal: function(popupModal) {
popupModal.modal('closeModal');
$('.ActionModal').remove();
$('.modals-overlay').remove();
$('body').removeClass('_has-modal');
// reconstruct the ActionModal container again otherwise component can not find the ActionModal
$('#ActionWrapper').append('<div id="ActionModal">' +
'<div id="ActionContainer"></div>' +
'</div>');
},
isBrandCodeChecked: ko.computed(function() {
if (!quote.paymentMethod()) {
return null;
}
if (quote.paymentMethod().method == paymentMethod()) {
return brandCode();
}
return null;
}),
isIconEnabled: function() {
return window.checkoutConfig.payment.adyen.showLogo;
},
/**
* Based on the response we can start a action component or redirect
* @param responseJSON
*/
validateActionOrPlaceOrder: function(responseJSON, orderId) {
var self = this;
var response = JSON.parse(responseJSON);
if (!!response.action) {
// render component
self.orderId = orderId;
self.renderActionComponent(response.action);
} else {
$.mage.redirect(
window.checkoutConfig.payment[quote.paymentMethod().method].redirectUrl,
);
}
},
/**
* 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) {
var self = this;
var actionNode = document.getElementById('ActionContainer');
fullScreenLoader.stopLoader();
self.popupModal = $('#ActionModal').modal({
// disable user to hide popup
clickableOverlay: false,
responsive: true,
innerScroll: false,
// empty buttons, we don't need that
buttons: [],
modalClass: 'ActionModal',
});
self.popupModal.modal('openModal');
self.actionComponent = self.checkoutComponent.createFromAction(
action).mount(actionNode);
},
handleOnAdditionalDetails: function(state, component) {
var self = this;
// call endpoint with state.data
var request = state.data;
request.orderId = self.orderId;
// 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() {
$.mage.redirect(
window.checkoutConfig.payment[quote.paymentMethod().method].redirectUrl,
);
}).fail(function(response) {
fullScreenLoader.stopLoader();
self.closeModal(self.popupModal);
errorProcessor.process(response, self.currentMessageContainer);
self.isPlaceOrderActionAllowed(true);
self.showErrorMessage(response);
});
},
/**
* Issue with the default currentMessageContainer needs to be resolved for now just throw manually the eror message
* @param response
*/
showErrorMessage: function(response) {
if (!!response['responseJSON'].parameters) {
$('#messages-' + brandCode()).
text((response['responseJSON'].message).replace('%1',
response['responseJSON'].parameters[0])).
slideDown();
} else {
$('#messages-' + brandCode()).
text(response['responseJSON'].message).
slideDown();
}
setTimeout(function() {
$('#messages-' + brandCode()).slideUp();
}, 10000);
},
validate: function(brandCode) {
var form = '#payment_form_' + this.getCode() + '_' + brandCode;
var validate = $(form).validation() && $(form).validation('isValid');
if (!validate) {
return false;
}
return true;
},
/**
* Returns the payment method's brand code using the payment method from the response object
* (in checkout api it is the type)
* @returns {*}
*/
getBrandCodeFromPaymentMethod: function(paymentMethod) {
if (typeof paymentMethod.type !== 'undefined') {
return paymentMethod.type;
}
return '';
},
getRatePayDeviceIdentToken: function() {
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;
/**
* Returns the payment method's brand code using the payment method from the response object
* (in checkout api it is the type)
* @returns {*}
*/
getBrandCodeFromPaymentMethod: function(paymentMethod) {
if (typeof paymentMethod.type !== 'undefined') {
return paymentMethod.type;
}
});
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;
},
});
return '';
},
getRatePayDeviceIdentToken: function() {
return window.checkoutConfig.payment.adyenHpp.deviceIdentToken;
},
});
},
);
......@@ -33,13 +33,11 @@ define(
'uiLayout',
'Magento_Ui/js/model/messages',
'mage/url',
'Adyen_Payment/js/threeds2-js-utils',
'Magento_Checkout/js/model/full-screen-loader',
'Magento_Paypal/js/action/set-payment-method',
'Magento_Checkout/js/model/url-builder',
'mage/storage',
'Magento_Checkout/js/action/place-order',
'Adyen_Payment/js/model/payment-details',
'Magento_Checkout/js/model/error-processor',
'Adyen_Payment/js/model/adyen-payment-service',
'Adyen_Payment/js/bundle',
......@@ -58,13 +56,11 @@ function(
layout,
Messages,
url,
threeDS2Utils,
fullScreenLoader,
setPaymentMethodAction,
urlBuilder,
storage,
placeOrderAction,
paymentDetails,
errorProcessor,
adyenPaymentService,
AdyenComponent
......@@ -347,7 +343,7 @@ function(
onComplete: function(result) {
var request = result.data;
request.orderId = orderId;
paymentDetails.process(request).
adyenPaymentService.paymentDetails(request).
done(function(responseJSON) {
self.validateThreeDS2OrPlaceOrder(responseJSON,
orderId);
......@@ -386,7 +382,7 @@ function(
fullScreenLoader.startLoader();
var request = result.data;
request.orderId = orderId;
paymentDetails.process(request).
adyenPaymentService.paymentDetails(request).
done(function(responseJSON) {
self.validateThreeDS2OrPlaceOrder(responseJSON,
orderId);
......@@ -413,7 +409,8 @@ function(
*/
getData: function() {
var self = this;
var browserInfo = threeDS2Utils.getBrowserInfo();
// todo use state.data
var browserInfo = [];
return {
'method': self.method,
......
......@@ -15,38 +15,37 @@
*
* Adyen Payment module (https://www.adyen.com/)
*
* Copyright (c) 2015 Adyen BV (https://www.adyen.com/)
* Copyright (c) 2020 Adyen BV (https://www.adyen.com/)
* See LICENSE.txt for license details.
*
* Author: Adyen <magento@adyen.com>
*/
-->
<!-- TODO check if it can be outsources to an adyen-methods template to be used by all the methods-->
<div id="ActionWrapper">
<div id="ActionModal">
<div id="ActionContainer"></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">
<input type="radio"
name="payment[method]"
class="radio"
data-bind="attr: {'id': value}, value: value, checked: $parent.isBrandCodeChecked, click: $parent.selectPaymentMethodBrandCode"/>
<label data-bind="attr: {'for': value}" class="label">
data-bind="attr: {'id': 'adyen_' + brandCode}, value: 'adyen_' + brandCode, checked: brandCode == $parent.isBrandCodeChecked, click: $parent.selectPaymentMethodBrandCode"/>
<label data-bind="attr: {'for': 'adyen_' + brandCode}" class="label">
<!-- ko if: name.icon -->
<!-- ko if: icon -->
<img data-bind="attr: {
'src': name.icon.url,
'width': name.icon.url.width,
'height': name.icon.url.height
'src': icon.url
}">
<!--/ko-->
<span data-bind="text: name.title"></span>
<span data-bind="text: name"></span>
</label>
</div>
<div class="payment-method-content">
......@@ -56,7 +55,7 @@
<!-- ko template: getTemplate() --><!-- /ko -->
<!--/ko-->
<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 class="payment-method-billing-address">
......@@ -65,242 +64,10 @@
<!--/ko-->
</div>
<form class="form" data-role="adyen-hpp-form" action="#" method="post" data-bind="mageInit: { 'validation':[]}, attr: {id: 'payment_form_' + $parent.getCode() + '_' + value}">
<fieldset class="fieldset" data-bind='attr: {id: "payment_fieldset_" + $parent.getCode() + "_" + value}'>
<!-- ko if: hasIssuersAvailable() -->
<!-- ko if: isIdeal() -->
<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-->
<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() + "_" + brandCode}'>
<div data-bind='attr: {id: "adyen-alternative-payment-container-" + brandCode}'
afterRender="renderCheckoutComponent()"></div>
</fieldset>
<div class="checkout-agreements-block">
......@@ -309,15 +76,13 @@
<!--/ko-->
</div>
<div class="actions-toolbar">
<div class="primary">
<button class="action primary checkout"
type="submit"
data-bind="
click: continueToAdyenBrandCode,
enable: placeOrderAllowed() && (value == $parent.isBrandCodeChecked()),
enable: placeOrderAllowed() && (brandCode == $parent.isBrandCodeChecked()),
css: {disabled: !$parent.isPlaceOrderActionAllowed()}"
disabled>
<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