We will be off from 27/1 (Monday) to 31/1 (Friday) (GMT +7) for our Tet Holiday (Lunar New Year) in our country

Commit 10062ad9 authored by Alexandros Moraitis's avatar Alexandros Moraitis Committed by GitHub

[PW-3431] Merge generic component to 7.0.0-rc.1 (#888)

* Add bundle.js with checkout component version 3.12.1 (#828)

Add the AdyenComponent var in the requireJS paramas

* [PW-3133] Refactor threeds2 to paymentDetails (#829)

* Refactor threeds2 to paymentDetails

* paymentDetails.paymentDetails -> paymentDetails.process

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

* Processing generic payment details requests
Co-authored-by: default avatarmarcoss <marcos.silvagarcia@adyen.com>

* [PW-3137] Refactor Payment Status endpoint (#839)

* [PW-3127] Validate payment methods (#838)

* payment methods response validation

* Remove getConnectedTerminals

* Refactor

* Fix suggestions

* Refactor paymentmethods

* Refactor method names

* Refactor payment method request

* [PW-3174] Use the generic component for card payments (#840)

* Use bundle instead of the component

* Store action and additionalData from /payments response

* [WIP] handle paymentDetails for card payments

* Remove custom redirect page

The checkout redirect component does the redirect for both 3DS1(now) and
alternative payment(WIP)

* Store and return details

Store action, resultCode, additionalData, pspReference, nad paymentData

* Show challenge in a popup for 3DS2

* [PW-3180] Remove generalResponseValidator (#841)

* Use bundle instead of the component

* Store action and additionalData from /payments response

* [WIP] handle paymentDetails for card payments

* Remove custom redirect page

The checkout redirect component does the redirect for both 3DS1(now) and
alternative payment(WIP)

* Store and return details

Store action, resultCode, additionalData, pspReference, nad paymentData

* Show challenge in a popup for 3DS2

* Remove GeneralResponseValidator.php

Change wrong phpdocs as well

* Use Generic Components for Google Pay (#845)
Co-authored-by: default avatarmarcoss <marcos.silvagarcia@adyen.com>

* [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>

* Fix bug sonar after sonarcloud check

* Add legend field

* Add legend field in the correct place

* Update Helper/Requests.php
Co-authored-by: default avatarAttila Kiss <42297201+cyattilakiss@users.noreply.github.com>
Co-authored-by: default avatarAttila Kiss <42297201+cyattilakiss@users.noreply.github.com>
Co-authored-by: default avatarÁngel Campos <angel.campos@adyen.com>
Co-authored-by: default avatarmarcoss <marcos.silvagarcia@adyen.com>
Co-authored-by: default avatarMarcos Garcia <marcos.asgarcia@gmail.com>
Co-authored-by: default avatarattilak <attila.kiss@adyen.com>
parent 43fb5b0c
......@@ -24,7 +24,7 @@
namespace Adyen\Payment\Api;
interface AdyenThreeDS2ProcessInterface
interface AdyenPaymentDetailsInterface
{
/**
* @param string $payload
......
<?php
/**
* ######
* ######
* ############ ####( ###### #####. ###### ############ ############
* ############# #####( ###### #####. ###### ############# #############
* ###### #####( ###### #####. ###### ##### ###### ##### ######
* ###### ###### #####( ###### #####. ###### ##### ##### ##### ######
* ###### ###### #####( ###### #####. ###### ##### ##### ######
* ############# ############# ############# ############# ##### ######
* ############ ############ ############# ############ ##### ######
* ######
* #############
* ############
*
* Adyen Payment module (https://www.adyen.com/)
*
* Copyright (c) 2015 Adyen BV (https://www.adyen.com/)
* See LICENSE.txt for license details.
*
* Author: Adyen <magento@adyen.com>
*/
namespace Adyen\Payment\Block\Redirect;
use Adyen\AdyenException;
use Symfony\Component\Config\Definition\Exception\Exception;
class Redirect extends \Magento\Payment\Block\Form
{
/**
* @var \Magento\Sales\Model\OrderFactory
*/
protected $_orderFactory;
/**
* @var \Magento\Checkout\Model\Session
*/
protected $_checkoutSession;
/**
* @var \Magento\Sales\Model\Order
*/
protected $_order;
/**
* @var \Adyen\Payment\Helper\Data
*/
protected $_adyenHelper;
/**
* @var ResolverInterface
*/
protected $_resolver;
/**
* @var \Adyen\Payment\Logger\AdyenLogger
*/
protected $_adyenLogger;
/**
* @var \Magento\Tax\Model\Config
*/
protected $_taxConfig;
/**
* @var \Magento\Tax\Model\Calculation
*/
protected $_taxCalculation;
/**
* Request object
*/
protected $_request;
/**
* Redirect constructor.
*
* @param \Magento\Framework\View\Element\Template\Context $context
* @param array $data
* @param \Magento\Sales\Model\OrderFactory $orderFactory
* @param \Magento\Checkout\Model\Session $checkoutSession
* @param \Adyen\Payment\Helper\Data $adyenHelper
* @param \Magento\Framework\Locale\ResolverInterface $resolver
* @param \Adyen\Payment\Logger\AdyenLogger $adyenLogger
* @param \Magento\Tax\Model\Config $taxConfig
* @param \Magento\Tax\Model\Calculation $taxCalculation
*/
public function __construct(
\Magento\Framework\View\Element\Template\Context $context,
\Magento\Sales\Model\OrderFactory $orderFactory,
\Magento\Checkout\Model\Session $checkoutSession,
\Adyen\Payment\Helper\Data $adyenHelper,
\Magento\Framework\Locale\ResolverInterface $resolver,
\Adyen\Payment\Logger\AdyenLogger $adyenLogger,
\Magento\Tax\Model\Config $taxConfig,
\Magento\Tax\Model\Calculation $taxCalculation,
array $data = []
) {
$this->_orderFactory = $orderFactory;
$this->_checkoutSession = $checkoutSession;
parent::__construct($context, $data);
$this->_adyenHelper = $adyenHelper;
$this->_resolver = $resolver;
$this->_adyenLogger = $adyenLogger;
$this->_getOrder();
$this->_taxConfig = $taxConfig;
$this->_taxCalculation = $taxCalculation;
$this->_request = $context->getRequest();
}
/**
* @return mixed|string[]
* @throws AdyenException
*/
public function getRedirectMethod()
{
if ($redirectMethod = $this->getPayment()->getAdditionalInformation('redirectMethod')) {
return $redirectMethod;
}
throw new AdyenException("No redirect method is provided.");
}
/**
* @return Redirect
*/
public function _prepareLayout()
{
return parent::_prepareLayout();
}
/**
* Retrieves redirect url for the flow of checkout API
*
* @return string[]
* @throws AdyenException
*/
public function getRedirectUrl()
{
if ($redirectUrl = $this->getPayment()->getAdditionalInformation('redirectUrl')) {
return $redirectUrl;
}
throw new AdyenException("No redirect url is provided.");
}
/**
* @return string
*/
public function getFormUrl()
{
$url = "";
try {
if ($this->_order->getPayment()) {
switch ($this->_adyenHelper->isDemoMode()) {
case true:
if ($this->_adyenHelper->doesPaymentMethodSkipDetails(
$this->_order->getPayment()->getAdditionalInformation('brand_code')
)
) {
$url = "https://test.adyen.com/hpp/skipDetails.shtml";
} else {
$url = "https://test.adyen.com/hpp/details.shtml";
}
break;
default:
if ($this->_adyenHelper->doesPaymentMethodSkipDetails(
$this->_order->getPayment()->getAdditionalInformation('brand_code')
)
) {
$url = "https://live.adyen.com/hpp/skipDetails.shtml";
} else {
$url = "https://live.adyen.com/hpp/details.shtml";
}
break;
}
}
} catch (Exception $e) {
// do nothing for now
throw($e);
}
return $url;
}
/**
* @return mixed
*/
private function getBrandCode()
{
return $this->getPayment()->getAdditionalInformation('brand_code');
}
/**
* Set Billing Address data
*
* @param $formFields
*/
protected function setBillingAddressData($formFields)
{
$billingAddress = $this->_order->getBillingAddress();
if ($billingAddress) {
$formFields['shopper.firstName'] = trim($billingAddress->getFirstname());
$middleName = trim($billingAddress->getMiddlename());
if ($middleName != "") {
$formFields['shopper.infix'] = trim($middleName);
}
$formFields['shopper.lastName'] = trim($billingAddress->getLastname());
$formFields['shopper.telephoneNumber'] = trim($billingAddress->getTelephone());
if ($this->_adyenHelper->isSeparateHouseNumberRequired($billingAddress->getCountryId())) {
$street = $this->_adyenHelper->getStreet($billingAddress);
if (!empty($street['name'])) {
$formFields['billingAddress.street'] = $street['name'];
}
if (!empty($street['house_number'])) {
$formFields['billingAddress.houseNumberOrName'] = $street['house_number'];
} else {
$formFields['billingAddress.houseNumberOrName'] = "NA";
}
} else {
$formFields['billingAddress.street'] = implode(" ", $billingAddress->getStreet());
}
if (trim($billingAddress->getCity()) == "") {
$formFields['billingAddress.city'] = "NA";
} else {
$formFields['billingAddress.city'] = trim($billingAddress->getCity());
}
if (trim($billingAddress->getPostcode()) == "") {
$formFields['billingAddress.postalCode'] = "";
} else {
$formFields['billingAddress.postalCode'] = trim($billingAddress->getPostcode());
}
if (trim($billingAddress->getRegionCode()) == "") {
$formFields['billingAddress.stateOrProvince'] = "";
} else {
$formFields['billingAddress.stateOrProvince'] = trim($billingAddress->getRegionCode());
}
if (trim($billingAddress->getCountryId()) == "") {
$formFields['billingAddress.country'] = "ZZ";
} else {
$formFields['billingAddress.country'] = trim($billingAddress->getCountryId());
}
}
return $formFields;
}
/**
* Set Shipping Address data
*
* @param $formFields
*/
protected function setShippingAddressData($formFields)
{
$shippingAddress = $this->_order->getShippingAddress();
if ($shippingAddress) {
if ($this->_adyenHelper->isSeparateHouseNumberRequired($shippingAddress->getCountryId())) {
$street = $this->_adyenHelper->getStreet($shippingAddress);
if (isset($street['name']) && $street['name'] != "") {
$formFields['deliveryAddress.street'] = $street['name'];
}
if (isset($street['house_number']) && $street['house_number'] != "") {
$formFields['deliveryAddress.houseNumberOrName'] = $street['house_number'];
} else {
$formFields['deliveryAddress.houseNumberOrName'] = "NA";
}
} else {
$formFields['deliveryAddress.street'] = implode(" ", $shippingAddress->getStreet());
}
if (trim($shippingAddress->getCity()) == "") {
$formFields['deliveryAddress.city'] = "NA";
} else {
$formFields['deliveryAddress.city'] = trim($shippingAddress->getCity());
}
if (trim($shippingAddress->getPostcode()) == "") {
$formFields['deliveryAddress.postalCode'] = "";
} else {
$formFields['deliveryAddress.postalCode'] = trim($shippingAddress->getPostcode());
}
if (trim($shippingAddress->getRegionCode()) == "") {
$formFields['deliveryAddress.stateOrProvince'] = "";
} else {
$formFields['deliveryAddress.stateOrProvince'] = trim($shippingAddress->getRegionCode());
}
if (trim($shippingAddress->getCountryId()) == "") {
$formFields['deliveryAddress.country'] = "ZZ";
} else {
$formFields['deliveryAddress.country'] = trim($shippingAddress->getCountryId());
}
}
return $formFields;
}
/**
* @param $genderId
* @return string
*/
protected function getGenderText($genderId)
{
$result = "";
if ($genderId == '1') {
$result = 'MALE';
} elseif ($genderId == '2') {
$result = 'FEMALE';
}
return $result;
}
/**
* The character escape function is called from the array_map function in _signRequestParams
*
* @param $val
* @return mixed
*/
protected function escapeString($val)
{
return str_replace(':', '\\:', str_replace('\\', '\\\\', $val));
}
/**
* Get frontend checkout session object
*
* @return \Magento\Checkout\Model\Session
*/
protected function _getCheckout()
{
return $this->_checkoutSession;
}
/**
* Retrieve request object
*
* @return \Magento\Framework\App\RequestInterface
*/
protected function _getRequest()
{
return $this->_request;
}
/**
* Get order object
*
* @return \Magento\Sales\Model\Order
*/
protected function _getOrder()
{
if (!$this->_order) {
$incrementId = $this->_getCheckout()->getLastRealOrderId();
$this->_order = $this->_orderFactory->create()->loadByIncrementId($incrementId);
}
return $this->_order;
}
/**
* @return mixed
*/
public function getPaReq()
{
if ($paReq = $this->getPayment()->getAdditionalInformation('paRequest')) {
return $paReq;
}
throw new AdyenException("No paRequest is provided.");
}
/**
* @return string[]
* @throws AdyenException
*/
public function getMd()
{
if ($md = $this->getPayment()->getAdditionalInformation('md')) {
return $md;
}
throw new AdyenException("No MD is provided.");
}
/**
* @return mixed
*/
public function getTermUrl()
{
return $this->getUrl('adyen/transparent/redirect', ['_secure' => $this->_getRequest()->isSecure()]);
}
/**
* Retrieve payment object if available
*
* @return \Magento\Framework\DataObject|\Magento\Sales\Api\Data\OrderPaymentInterface|mixed|null
* @throws AdyenException
*/
private function getPayment()
{
try {
$paymentObject = $this->_order->getPayment();
if (!empty($paymentObject)) {
return $paymentObject;
}
} catch (Exception $e) {
// do nothing for now
throw($e);
}
throw new AdyenException("No payment object is found.");
}
}
......@@ -148,7 +148,6 @@ class Redirect extends \Magento\Framework\App\Action\Action
if ($order->getPayment()) {
$active = $order->getPayment()->getAdditionalInformation('3dActive');
$success = $order->getPayment()->getAdditionalInformation('3dSuccess');
$checkoutAPM = $order->getPayment()->getAdditionalInformation('checkoutAPM');
}
// check if 3D secure is active. If not just go to success page
......@@ -257,10 +256,6 @@ class Redirect extends \Magento\Framework\App\Action\Action
$this->_view->getLayout()->initMessages();
$this->_view->renderLayout();
}
} elseif (!empty($checkoutAPM)) {
$this->_view->loadLayout();
$this->_view->getLayout()->initMessages();
$this->_view->renderLayout();
} else {
$this->_redirect('checkout/onepage/success', ['_query' => ['utm_nooverride' => '1']]);
}
......
<?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,7 +23,9 @@
namespace Adyen\Payment\Gateway\Request;
use Adyen\Payment\Helper\ChargedCurrency;
use Adyen\Payment\Observer\AdyenCcDataAssignObserver;
use Magento\Payment\Gateway\Request\BuilderInterface;
use Adyen\Payment\Observer\AdyenHppDataAssignObserver;
......@@ -85,22 +87,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';
......@@ -118,46 +111,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(
......@@ -221,12 +174,55 @@ class CheckoutDataBuilder implements BuilderInterface
$order->setCanSendNewEmailFlag(true);
}
// if installments is set and card type is credit card add it into the request
$numberOfInstallments = $payment->getAdditionalInformation(
AdyenCcDataAssignObserver::NUMBER_OF_INSTALLMENTS
) ?: 0;
$comboCardType = $payment->getAdditionalInformation(AdyenCcDataAssignObserver::COMBO_CARD_TYPE) ?: 'credit';
if ($numberOfInstallments > 0) {
$requestBody['installments']['value'] = $numberOfInstallments;
}
// if card type is debit then change the issuer type and unset the installments field
if ($comboCardType == 'debit') {
if ($selectedDebitBrand = $this->getSelectedDebitBrand($payment->getAdditionalInformation('cc_type'))) {
$requestBody['additionalData']['overwriteBrand'] = true;
$requestBody['selectedBrand'] = $selectedDebitBrand;
$requestBody['paymentMethod']['type'] = $selectedDebitBrand;
}
unset($requestBody['installments']);
}
if ($this->adyenHelper->isCreditCardThreeDS2Enabled($storeId)) {
$requestBody['additionalData']['allow3DS2'] = true;
}
$requestBody['origin'] = $this->adyenHelper->getOrigin($storeId);
$requestBody['channel'] = 'web';
if (isset($requestBodyPaymentMethod)) {
$requestBody['paymentMethod'] = $requestBodyPaymentMethod;
}
$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
*
......@@ -267,7 +263,6 @@ class CheckoutDataBuilder implements BuilderInterface
$formFields['lineItems'][] = [
'id' => $item->getId(),
'itemId' => $item->getId(),
'amountExcludingTax' => $formattedPriceExcludingTax,
'taxAmount' => $formattedTaxAmount,
'description' => $item->getName(),
......@@ -318,7 +313,7 @@ class CheckoutDataBuilder implements BuilderInterface
}
$formFields['lineItems'][] = [
'itemId' => 'shippingCost',
'id' => 'shippingCost',
'amountExcludingTax' => $formattedPriceExcludingTax,
'taxAmount' => $formattedTaxAmount,
'description' => $order->getShippingDescription(),
......
......@@ -33,7 +33,7 @@ class CancelResponseValidator extends AbstractValidator
private $adyenLogger;
/**
* GeneralResponseValidator constructor.
* CancelResponseValidator constructor.
*
* @param \Magento\Payment\Gateway\Validator\ResultInterfaceFactory $resultFactory
* @param \Adyen\Payment\Logger\AdyenLogger $adyenLogger
......
......@@ -33,7 +33,7 @@ class CaptureResponseValidator extends AbstractValidator
private $adyenLogger;
/**
* GeneralResponseValidator constructor.
* CaptureResponseValidator constructor.
*
* @param \Magento\Payment\Gateway\Validator\ResultInterfaceFactory $resultFactory
* @param \Adyen\Payment\Logger\AdyenLogger $adyenLogger
......
......@@ -38,10 +38,11 @@ class CheckoutResponseValidator extends AbstractValidator
private $adyenHelper;
/**
* GeneralResponseValidator constructor.
* CheckoutResponseValidator constructor.
*
* @param \Magento\Payment\Gateway\Validator\ResultInterfaceFactory $resultFactory
* @param \Adyen\Payment\Logger\AdyenLogger $adyenLogger
* @param \Adyen\Payment\Helper\Data $adyenHelper
*/
public function __construct(
\Magento\Payment\Gateway\Validator\ResultInterfaceFactory $resultFactory,
......@@ -66,29 +67,32 @@ 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);
switch ($resultCode) {
case "IdentifyShopper":
$payment->setAdditionalInformation('threeDSType', $resultCode);
$payment->setAdditionalInformation(
'threeDS2Token',
$response['authentication']['threeds2.fingerprintToken']
);
$payment->setAdditionalInformation('adyenPaymentData', $response['paymentData']);
break;
case "ChallengeShopper":
$payment->setAdditionalInformation('threeDSType', $resultCode);
$payment->setAdditionalInformation(
'threeDS2Token',
$response['authentication']['threeds2.challengeToken']
);
if (!empty($response['action'])) {
$payment->setAdditionalInformation('action', $response['action']);
}
if (!empty($response['additionalData'])) {
$payment->setAdditionalInformation('additionalData', $response['additionalData']);
}
if (!empty($response['pspReference'])) {
$payment->setAdditionalInformation('pspReference', $response['pspReference']);
}
if (!empty($response['paymentData'])) {
$payment->setAdditionalInformation('adyenPaymentData', $response['paymentData']);
break;
}
switch ($resultCode) {
case "Authorised":
case "Received":
// TODO refactor since the full additionalData is stored in additionalInformation already
// For banktransfers store all bankTransfer details
if (!empty($response['additionalData']['bankTransfer.owner'])) {
foreach ($response['additionalData'] as $key => $value) {
......@@ -106,6 +110,31 @@ class CheckoutResponseValidator extends AbstractValidator
}
}
// TODO doudle check this
if (isset($response['additionalData']) && is_array($response['additionalData'])) {
$additionalData = $response['additionalData'];
if (isset($additionalData['boletobancario.dueDate'])) {
$payment->setAdditionalInformation(
'dueDate',
$additionalData['boletobancario.dueDate']
);
}
if (isset($additionalData['boletobancario.expirationDate'])) {
$payment->setAdditionalInformation(
'expirationDate',
$additionalData['boletobancario.expirationDate']
);
}
if (isset($additionalData['boletobancario.url'])) {
$payment->setAdditionalInformation(
'url',
$additionalData['boletobancario.url']
);
}
}
// Save cc_type if available in the response
if (!empty($response['additionalData']['paymentMethod'])) {
$ccType = $this->adyenHelper->getMagentoCreditCartType(
......@@ -114,90 +143,15 @@ class CheckoutResponseValidator extends AbstractValidator
$payment->setAdditionalInformation('cc_type', $ccType);
$payment->setCcType($ccType);
}
$payment->setAdditionalInformation('pspReference', $response['pspReference']);
break;
case "IdentifyShopper":
case "ChallengeShopper":
case "PresentToShopper":
if (!empty($response['action'])) {
$payment->setAdditionalInformation('action', $response['action']);
}
if (!empty($response['pspReference'])) {
$payment->setAdditionalInformation('pspReference', $response['pspReference']);
}
break;
case 'Pending':
$payment->setAdditionalInformation('adyenPaymentData', $response['paymentData']);
if (!empty($response['action'])) {
$payment->setAdditionalInformation('action', $response['action']);
}
break;
case "RedirectShopper":
$payment->setAdditionalInformation('threeDSType', $resultCode);
$redirectUrl = null;
$paymentData = null;
if (!empty($response['redirect']['url'])) {
$redirectUrl = $response['redirect']['url'];
}
if (!empty($response['redirect']['method'])) {
$redirectMethod = $response['redirect']['method'];
}
if (!empty($response['paymentData'])) {
$paymentData = $response['paymentData'];
}
// If the redirect data is there then the payment is a card payment with 3d secure
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.');
......
<?php
/**
* ######
* ######
* ############ ####( ###### #####. ###### ############ ############
* ############# #####( ###### #####. ###### ############# #############
* ###### #####( ###### #####. ###### ##### ###### ##### ######
* ###### ###### #####( ###### #####. ###### ##### ##### ##### ######
* ###### ###### #####( ###### #####. ###### ##### ##### ######
* ############# ############# ############# ############# ##### ######
* ############ ############ ############# ############ ##### ######
* ######
* #############
* ############
*
* Adyen Payment module (https://www.adyen.com/)
*
* Copyright (c) 2015 Adyen BV (https://www.adyen.com/)
* See LICENSE.txt for license details.
*
* Author: Adyen <magento@adyen.com>
*/
namespace Adyen\Payment\Gateway\Validator;
use Magento\Payment\Gateway\Validator\AbstractValidator;
class GeneralResponseValidator extends AbstractValidator
{
/**
* @var \Adyen\Payment\Logger\AdyenLogger
*/
private $adyenLogger;
/**
* @var \Adyen\Payment\Helper\Data
*/
private $adyenHelper;
/**
* GeneralResponseValidator constructor.
*
* @param \Magento\Payment\Gateway\Validator\ResultInterfaceFactory $resultFactory
* @param \Adyen\Payment\Logger\AdyenLogger $adyenLogger
*/
public function __construct(
\Magento\Payment\Gateway\Validator\ResultInterfaceFactory $resultFactory,
\Adyen\Payment\Logger\AdyenLogger $adyenLogger,
\Adyen\Payment\Helper\Data $adyenHelper
) {
$this->adyenLogger = $adyenLogger;
$this->adyenHelper = $adyenHelper;
parent::__construct($resultFactory);
}
/**
* @param array $validationSubject
* @return \Magento\Payment\Gateway\Validator\ResultInterface
*/
public function validate(array $validationSubject)
{
$response = \Magento\Payment\Gateway\Helper\SubjectReader::readResponse($validationSubject);
$paymentDataObjectInterface = \Magento\Payment\Gateway\Helper\SubjectReader::readPayment($validationSubject);
$payment = $paymentDataObjectInterface->getPayment();
$payment->setAdditionalInformation('3dActive', false);
$isValid = true;
$errorMessages = [];
// validate result
if (!empty($response['resultCode'])) {
switch ($response['resultCode']) {
case "Authorised":
$payment->setAdditionalInformation('pspReference', $response['pspReference']);
// Save cc_type if available in the response
if (!empty($response['additionalData']['paymentMethod'])) {
$ccType = $this->adyenHelper->getMagentoCreditCartType(
$response['additionalData']['paymentMethod']
);
$payment->setAdditionalInformation('cc_type', $ccType);
$payment->setCcType($ccType);
}
break;
case "Received":
$payment->setAdditionalInformation('pspReference', $response['pspReference']);
// set additionalData
if (isset($response['additionalData']) && is_array($response['additionalData'])) {
$additionalData = $response['additionalData'];
if (isset($additionalData['boletobancario.dueDate'])) {
$payment->setAdditionalInformation(
'dueDate',
$additionalData['boletobancario.dueDate']
);
}
if (isset($additionalData['boletobancario.expirationDate'])) {
$payment->setAdditionalInformation(
'expirationDate',
$additionalData['boletobancario.expirationDate']
);
}
if (isset($additionalData['boletobancario.url'])) {
$payment->setAdditionalInformation(
'url',
$additionalData['boletobancario.url']
);
}
}
break;
case "RedirectShopper":
$payment->setAdditionalInformation('3dActive', true);
$payment->setAdditionalInformation('pspReference', $response['pspReference']);
$redirectUrl = $response['issuerUrl'];
$paReq = $response['paRequest'];
$md = $response['md'];
if (!empty($paReq) && !empty($md) && !empty($redirectUrl)) {
$payment->setAdditionalInformation('redirectUrl', $redirectUrl);
$payment->setAdditionalInformation('paRequest', $response['paRequest']);
$payment->setAdditionalInformation('md', $response['md']);
} else {
$isValid = false;
$errorMsg = __('3D secure is not valid.');
$this->adyenLogger->error($errorMsg);
$errorMessages[] = $errorMsg;
}
break;
case "Refused":
$errorMsg = __('The payment is REFUSED.');
// this will result the specific error
throw new \Magento\Framework\Exception\LocalizedException(__($errorMsg));
break;
default:
$errorMsg = __('Error with payment method please select different payment method.');
throw new \Magento\Framework\Exception\LocalizedException(__($errorMsg));
break;
}
} else {
$errorMsg = __('Error with payment method please select different payment method.');
if (!empty($response['error'])) {
$this->adyenLogger->error($response['error']);
}
throw new \Magento\Framework\Exception\LocalizedException(__($errorMsg));
}
return $this->createResult($isValid, $errorMessages);
}
}
......@@ -33,7 +33,7 @@ class RefundResponseValidator extends AbstractValidator
private $adyenLogger;
/**
* GeneralResponseValidator constructor.
* RefundResponseValidator constructor.
*
* @param \Magento\Payment\Gateway\Validator\ResultInterfaceFactory $resultFactory
* @param \Adyen\Payment\Logger\AdyenLogger $adyenLogger
......
......@@ -15,59 +15,69 @@
*
* 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;
namespace Adyen\Payment\Helper;
use Magento\Payment\Gateway\Request\BuilderInterface;
class ThreeDS2DataBuilder implements BuilderInterface
class ConnectedTerminals
{
/**
* @var \Magento\Framework\App\State
* @var \Magento\Checkout\Model\Session
*/
private $appState;
protected $session;
/**
* @var \Adyen\Payment\Helper\Requests
* @var \Adyen\Payment\Helper\Data
*/
private $adyenRequestsHelper;
protected $adyenHelper;
/**
* 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
\Adyen\Payment\Helper\Data $adyenHelper,
\Magento\Checkout\Model\Session $session
) {
$this->appState = $context->getAppState();
$this->adyenRequestsHelper = $adyenRequestsHelper;
$this->adyenHelper = $adyenHelper;
$this->session = $session;
}
/**
* @param array $buildSubject
* @return array
* @return array|mixed
* @throws \Adyen\AdyenException
* @throws \Magento\Framework\Exception\LocalizedException
* @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function build(array $buildSubject)
public function getConnectedTerminals()
{
/** @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(),
[]
$storeId = $this->session->getQuote()->getStoreId();
// initialize the adyen client
$client = $this->adyenHelper->initializeAdyenClient($storeId, $this->adyenHelper->getPosApiKey($storeId));
// initialize service
$service = $this->adyenHelper->createAdyenPosPaymentService($client);
$requestParams = [
"merchantAccount" => $this->adyenHelper->getAdyenMerchantAccount('adyen_pos_cloud', $storeId),
];
// In case the POS store id is set, provide in the request
if (!empty($this->adyenHelper->getPosStoreId($storeId))) {
$requestParams['store'] = $this->adyenHelper->getPosStoreId($storeId);
}
try {
$responseData = $service->getConnectedTerminals($requestParams);
} catch (\Adyen\AdyenException $e) {
$this->adyenLogger->error(
"The getConnectedTerminals response is empty check your Adyen configuration in Magento."
);
return $request;
// return empty result
return [];
}
return $responseData;
}
}
......@@ -45,11 +45,6 @@ class PaymentMethods extends AbstractHelper
*/
protected $adyenHelper;
/**
* @var \Magento\Checkout\Model\Session
*/
protected $session;
/**
* @var \Magento\Framework\Locale\ResolverInterface
*/
......@@ -101,7 +96,6 @@ class PaymentMethods extends AbstractHelper
* @param \Magento\Quote\Api\CartRepositoryInterface $quoteRepository
* @param \Magento\Framework\App\Config\ScopeConfigInterface $config
* @param Data $adyenHelper
* @param \Magento\Checkout\Model\Session $session
* @param \Magento\Framework\Locale\ResolverInterface $localeResolver
* @param \Adyen\Payment\Logger\AdyenLogger $adyenLogger
* @param \Magento\Framework\View\Asset\Repository $assetRepo
......@@ -114,7 +108,6 @@ class PaymentMethods extends AbstractHelper
\Magento\Quote\Api\CartRepositoryInterface $quoteRepository,
\Magento\Framework\App\Config\ScopeConfigInterface $config,
\Adyen\Payment\Helper\Data $adyenHelper,
\Magento\Checkout\Model\Session $session,
\Magento\Framework\Locale\ResolverInterface $localeResolver,
\Adyen\Payment\Logger\AdyenLogger $adyenLogger,
\Magento\Framework\View\Asset\Repository $assetRepo,
......@@ -127,7 +120,6 @@ class PaymentMethods extends AbstractHelper
$this->quoteRepository = $quoteRepository;
$this->config = $config;
$this->adyenHelper = $adyenHelper;
$this->session = $session;
$this->localeResolver = $localeResolver;
$this->adyenLogger = $adyenLogger;
$this->assetRepo = $assetRepo;
......@@ -139,7 +131,12 @@ class PaymentMethods extends AbstractHelper
}
/**
* {@inheritDoc}
* @param $quoteId
* @param null $country
* @return array
* @throws \Adyen\AdyenException
* @throws \Magento\Framework\Exception\LocalizedException
* @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function getPaymentMethods($quoteId, $country = null)
{
......@@ -153,15 +150,16 @@ class PaymentMethods extends AbstractHelper
$this->setQuote($quote);
$paymentMethods = $this->fetchAlternativeMethods($country);
return $paymentMethods;
return $this->fetchPaymentMethods($country);
}
/**
* @param $country
* @return array
* @throws \Adyen\AdyenException
* @throws \Magento\Framework\Exception\LocalizedException
*/
protected function fetchAlternativeMethods($country)
protected function fetchPaymentMethods($country)
{
$quote = $this->getQuote();
$store = $quote->getStore();
......@@ -172,95 +170,28 @@ class PaymentMethods extends AbstractHelper
return [];
}
$amountCurrency = $this->chargedCurrency->getQuoteAmountCurrency($quote);
$adyFields = [
"channel" => "Web",
"merchantAccount" => $merchantAccount,
"countryCode" => $this->getCurrentCountryCode($store, $country),
"amount" => [
"currency" => $amountCurrency->getCurrencyCode(),
"value" => $this->adyenHelper->formatAmount(
$this->getCurrentPaymentAmount(),
$amountCurrency->getCurrencyCode()
),
],
"shopperReference" => $this->getCurrentShopperReference(),
"shopperLocale" => $this->adyenHelper->getCurrentLocaleCode($store->getId())
];
$billingAddress = $quote->getBillingAddress();
if (!empty($billingAddress)) {
if ($customerTelephone = trim($billingAddress->getTelephone())) {
$adyFields['telephoneNumber'] = $customerTelephone;
}
}
$responseData = $this->getPaymentMethodsResponse($adyFields, $store);
$paymentMethods = [];
if (isset($responseData['paymentMethods'])) {
foreach ($responseData['paymentMethods'] as $paymentMethod) {
$paymentMethodCode = $paymentMethod['type'];
$paymentMethod = $this->fieldMapPaymentMethod($paymentMethod);
// check if payment method is an openinvoice method
$paymentMethod['isPaymentMethodOpenInvoiceMethod'] =
$this->adyenHelper->isPaymentMethodOpenInvoiceMethod($paymentMethodCode);
// add icon location in result
if ($this->adyenHelper->showLogos()) {
// Fix for MAGETWO-70402 https://github.com/magento/magento2/pull/7686
// Explicitly setting theme
$themeCode = "Magento/blank";
$paymentMethodRequest = $this->getPaymentMethodsRequest($merchantAccount, $store, $country, $quote);
$responseData = $this->getPaymentMethodsResponse($paymentMethodRequest, $store);
$themeId = $this->design->getConfigurationDesignTheme(\Magento\Framework\App\Area::AREA_FRONTEND);
if (!empty($themeId)) {
$theme = $this->themeProvider->getThemeById($themeId);
if ($theme && !empty($theme->getCode())) {
$themeCode = $theme->getCode();
}
if (empty($responseData['paymentMethods'])) {
return [];
}
$params = [];
$params = array_merge(
[
'area' => \Magento\Framework\App\Area::AREA_FRONTEND,
'_secure' => $this->request->isSecure(),
'theme' => $themeCode
],
$params
);
$asset = $this->assetRepo->createAsset(
'Adyen_Payment::images/logos/' .
$paymentMethodCode . '.png',
$params
);
$placeholder = $this->assetSource->findSource($asset);
$paymentMethods = $responseData['paymentMethods'];
$response['paymentMethodsResponse'] = $responseData;
$icon = null;
if ($placeholder) {
list($width, $height) = getimagesize($asset->getSourceFile());
$icon = [
'url' => $asset->getUrl(),
'width' => $width,
'height' => $height
];
}
$paymentMethod['icon'] = $icon;
}
$paymentMethods[$paymentMethodCode] = $paymentMethod;
}
}
// Add extra details per payment method
$paymentMethodsExtraDetails = [];
$paymentMethodsExtraDetails = $this->showLogosPaymentMethods($paymentMethods, $paymentMethodsExtraDetails);
$response['paymentMethodsExtraDetails'] = $paymentMethodsExtraDetails;
return $paymentMethods;
//TODO this should be the implemented with an interface
return json_encode($response);
}
/**
* @return float
* @throws \Exception
*/
protected function getCurrentPaymentAmount()
{
......@@ -324,28 +255,6 @@ class PaymentMethods extends AbstractHelper
return "";
}
/**
* @var array
*/
protected $fieldMapPaymentMethod = [
'name' => 'title'
];
/**
* @param $paymentMethod
* @return mixed
*/
protected function fieldMapPaymentMethod($paymentMethod)
{
foreach ($this->fieldMapPaymentMethod as $field => $newField) {
if (isset($paymentMethod[$field])) {
$paymentMethod[$newField] = $paymentMethod[$field];
unset($paymentMethod[$field]);
}
}
return $paymentMethod;
}
/**
* @param $requestParams
* @param $store
......@@ -398,38 +307,115 @@ class PaymentMethods extends AbstractHelper
}
/**
* @return array|mixed
* @throws \Adyen\AdyenException
* @param $merchantAccount
* @param \Magento\Store\Model\Store $store
* @param $country
* @param \Magento\Quote\Model\Quote $quote
* @return array
* @throws \Exception
*/
public function getConnectedTerminals()
{
$storeId = $this->session->getQuote()->getStoreId();
protected function getPaymentMethodsRequest(
$merchantAccount,
\Magento\Store\Model\Store $store,
$country,
\Magento\Quote\Model\Quote $quote
) {
$currencyCode = $this->chargedCurrency->getQuoteAmountCurrency($quote);
// initialize the adyen client
$client = $this->adyenHelper->initializeAdyenClient($storeId, $this->adyenHelper->getPosApiKey($storeId));
$paymentMethodRequest = [
"channel" => "Web",
"merchantAccount" => $merchantAccount,
"countryCode" => $this->getCurrentCountryCode($store, $country),
"shopperLocale" => $this->adyenHelper->getCurrentLocaleCode($store->getId()),
"amount" => [
"currency" => $currencyCode
]
];
// initialize service
$service = $this->adyenHelper->createAdyenPosPaymentService($client);
if (!empty($this->getCurrentShopperReference())) {
$paymentMethodRequest["shopperReference"] = $this->getCurrentShopperReference();
}
$requestParams = [
"merchantAccount" => $this->adyenHelper->getAdyenMerchantAccount('adyen_pos_cloud', $storeId),
];
$amountValue = $this->adyenHelper->formatAmount($this->getCurrentPaymentAmount(), $currencyCode);
// In case the POS store id is set, provide in the request
if (!empty($this->adyenHelper->getPosStoreId($storeId))) {
$requestParams['store'] = $this->adyenHelper->getPosStoreId($storeId);
if (!empty($amountValue)) {
$paymentMethodRequest["amount"]["value"] = $amountValue;
}
try {
$responseData = $service->getConnectedTerminals($requestParams);
} catch (\Adyen\AdyenException $e) {
$this->adyenLogger->error(
"The getConnectedTerminals response is empty check your Adyen configuration in Magento."
$billingAddress = $quote->getBillingAddress();
if (!empty($billingAddress)) {
if ($customerTelephone = trim($billingAddress->getTelephone())) {
$paymentMethodRequest['telephoneNumber'] = $customerTelephone;
}
}
return $paymentMethodRequest;
}
/**
* @param $paymentMethods
* @param array $paymentMethodsExtraDetails
* @return array
* @throws \Magento\Framework\Exception\LocalizedException
*/
protected function showLogosPaymentMethods($paymentMethods, array $paymentMethodsExtraDetails)
{
if ($this->adyenHelper->showLogos()) {
// Explicitly setting theme
$themeCode = "Magento/blank";
$themeId = $this->design->getConfigurationDesignTheme(\Magento\Framework\App\Area::AREA_FRONTEND);
if (!empty($themeId)) {
$theme = $this->themeProvider->getThemeById($themeId);
if ($theme && !empty($theme->getCode())) {
$themeCode = $theme->getCode();
}
}
$params = [];
$params = array_merge(
[
'area' => \Magento\Framework\App\Area::AREA_FRONTEND,
'_secure' => $this->request->isSecure(),
'theme' => $themeCode
],
$params
);
// return empty result
return [];
foreach ($paymentMethods as $paymentMethod) {
$paymentMethodCode = $paymentMethod['type'];
$asset = $this->assetRepo->createAsset(
'Adyen_Payment::images/logos/' .
$paymentMethodCode . '.png',
$params
);
$placeholder = $this->assetSource->findSource($asset);
if ($placeholder) {
list($width, $height) = getimagesize($asset->getSourceFile());
$icon = [
'url' => $asset->getUrl(),
'width' => $width,
'height' => $height
];
} else {
$icon = [
'url' => 'https://checkoutshopper-live.adyen.com/checkoutshopper/images/logos/medium/' . $paymentMethodCode . '.png',
'width' => 77,
'height' => 50
];
}
return $responseData;
$paymentMethodsExtraDetails[$paymentMethodCode]['icon'] = $icon;
//todo check if it is needed
// check if payment method is an open invoice method
$paymentMethodsExtraDetails[$paymentMethodCode]['isOpenInvoice'] =
$this->adyenHelper->isPaymentMethodOpenInvoiceMethod($paymentMethodCode);
}
}
return $paymentMethodsExtraDetails;
}
}
<?php
/**
* ######
* ######
* ############ ####( ###### #####. ###### ############ ############
* ############# #####( ###### #####. ###### ############# #############
* ###### #####( ###### #####. ###### ##### ###### ##### ######
* ###### ###### #####( ###### #####. ###### ##### ##### ##### ######
* ###### ###### #####( ###### #####. ###### ##### ##### ######
* ############# ############# ############# ############# ##### ######
* ############ ############ ############# ############ ##### ######
* ######
* #############
* ############
*
* Adyen Payment module (https://www.adyen.com/)
*
* Copyright (c) 2020 Adyen BV (https://www.adyen.com/)
* See LICENSE.txt for license details.
*
* Author: Adyen <magento@adyen.com>
*/
namespace Adyen\Payment\Helper;
use Adyen\Payment\Logger\AdyenLogger;
use Magento\Sales\Api\Data\OrderInterface;
use Magento\Sales\Api\Data\OrderPaymentInterface;
use Magento\Sales\Model\Order;
class PaymentResponseHandler
{
const AUTHORISED = 'Authorised';
const REFUSED = 'Refused';
const REDIRECT_SHOPPER = 'RedirectShopper';
const IDENTIFY_SHOPPER = 'IdentifyShopper';
const CHALLENGE_SHOPPER = 'ChallengeShopper';
const RECEIVED = 'Received';
const PENDING = 'Pending';
const PRESENT_TO_SHOPPER = 'PresentToShopper';
const ERROR = 'Error';
const CANCELLED = 'Cancelled';
private $adyenLogger;
public function __construct(
AdyenLogger $adyenLogger
) {
$this->adyenLogger = $adyenLogger;
}
public function formatPaymentResponse($resultCode, $action = null, $additionalData = null)
{
switch ($resultCode) {
case self::AUTHORISED:
case self::REFUSED:
case self::ERROR:
return [
"isFinal" => true,
"resultCode" => $resultCode,
];
case self::REDIRECT_SHOPPER:
case self::IDENTIFY_SHOPPER:
case self::CHALLENGE_SHOPPER:
case self::PENDING:
return [
"isFinal" => false,
"resultCode" => $resultCode,
"action" => $action
];
case self::PRESENT_TO_SHOPPER:
return [
"isFinal" => true,
"resultCode" => $resultCode,
"action" => $action
];
case self::RECEIVED:
return [
"isFinal" => true,
"resultCode" => $resultCode,
"additionalData" => $additionalData
];
default:
return [
"isFinal" => true,
"resultCode" => self::ERROR,
];
}
}
/**
* @param $paymentsResponse
* @param OrderPaymentInterface $payment
* @param OrderInterface|null $order
* @return bool
*/
public function handlePaymentResponse($paymentsResponse, $payment, $order = null)
{
if (empty($paymentsResponse)) {
$this->adyenLogger->error("Payment details call failed, paymentsResponse is empty");
return false;
}
if (!empty($paymentsResponse['resultCode']))
$payment->setAdditionalInformation('resultCode', $paymentsResponse['resultCode']);
if (!empty($paymentsResponse['action'])) {
$payment->setAdditionalInformation('action', $paymentsResponse['action']);
}
if (!empty($paymentsResponse['additionalData'])) {
$payment->setAdditionalInformation('additionalData', $paymentsResponse['additionalData']);
}
if (!empty($paymentsResponse['pspReference'])) {
$payment->setAdditionalInformation('pspReference', $paymentsResponse['pspReference']);
}
if (!empty($paymentsResponse['paymentData'])) {
$payment->setAdditionalInformation('adyenPaymentData', $paymentsResponse['paymentData']);
}
switch ($paymentsResponse['resultCode']) {
case self::PRESENT_TO_SHOPPER:
case self::PENDING:
case self::RECEIVED:
break;
//We don't need to handle these resultCodes
case self::REDIRECT_SHOPPER:
$this->adyenLogger->addAdyenResult("Customer was redirected.");
if ($order) {
$order->addStatusHistoryComment(
__(
'Customer was redirected to an external payment page. (In case of card payments the shopper is redirected to the bank for 3D-secure validation.) Once the shopper is authenticated,
the order status will be updated accordingly.
<br />Make sure that your notifications are being processed!
<br />If the order is stuck on this status, the shopper abandoned the session.
The payment can be seen as unsuccessful.
<br />The order can be automatically cancelled based on the OFFER_CLOSED notification.
Please contact Adyen Support to enable this.'
)
)->save();
}
break;
case self::AUTHORISED:
case self::IDENTIFY_SHOPPER:
case self::CHALLENGE_SHOPPER:
break;
//These resultCodes cancel the order and log an error
case self::REFUSED:
case self::ERROR:
default:
if (!$order->canCancel()) {
$order->setState(Order::STATE_NEW);
}
//TODO check if order gets cancelled
$order->cancel();
$this->adyenLogger->error(
sprintf("Payment details call failed for action, resultCode is %s Raw API responds: %s",
$paymentsResponse['resultCode'],
print_r($paymentsResponse, true)
));
return false;
}
return true;
}
}
......@@ -116,36 +116,10 @@ class Requests extends AbstractHelper
// In case of virtual product and guest checkout there is a workaround to get the guest's email address
if (!empty($additionalData['guestEmail'])) {
if ($this->adyenHelper->isPaymentMethodOpenInvoiceMethod($paymentMethod) &&
!$this->adyenHelper->isPaymentMethodAfterpayTouchMethod($paymentMethod)
) {
$request['paymentMethod']['personalDetails']['shopperEmail'] = $additionalData['guestEmail'];
} else {
$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;
}
......@@ -161,7 +135,6 @@ class Requests extends AbstractHelper
if ($lastName = $billingAddress->getLastname()) {
$request['shopperName']['lastName'] = $lastName;
}
}
if ($countryId = $billingAddress->getCountryId()) {
$request['countryCode'] = $countryId;
......@@ -353,8 +326,6 @@ class Requests extends AbstractHelper
$request['origin'] = $this->adyenHelper->getOrigin($storeId);
$request['channel'] = 'web';
}
return $request;
}
/**
......
......@@ -24,43 +24,54 @@
namespace Adyen\Payment\Model;
use Adyen\Payment\Model\Ui\AdyenCcConfigProvider;
use Adyen\Payment\Api\AdyenOrderPaymentStatusInterface;
use Adyen\Payment\Helper\Data;
use Adyen\Payment\Helper\PaymentResponseHandler;
use Adyen\Payment\Logger\AdyenLogger;
use Magento\Sales\Api\OrderRepositoryInterface;
use Adyen\Payment\Model\Ui\AdyenGooglePayConfigProvider;
use Adyen\Payment\Model\Ui\AdyenHppConfigProvider;
use Adyen\Payment\Model\Ui\AdyenOneclickConfigProvider;
use \Magento\Framework\Exception\NoSuchEntityException;
class AdyenOrderPaymentStatus implements \Adyen\Payment\Api\AdyenOrderPaymentStatusInterface
class AdyenOrderPaymentStatus implements AdyenOrderPaymentStatusInterface
{
/**
* @var \Magento\Sales\Api\OrderRepositoryInterface
* @var OrderRepositoryInterface
*/
protected $orderRepository;
/**
* @var \Adyen\Payment\Logger\AdyenLogger
* @var AdyenLogger
*/
protected $adyenLogger;
/**
* @var \Adyen\Payment\Helper\Data
* @var Data
*/
protected $adyenHelper;
/**
* @var PaymentResponseHandler
*/
private $paymentResponseHandler;
/**
* AdyenOrderPaymentStatus constructor.
*
* @param \Magento\Sales\Api\OrderRepositoryInterface $orderRepository
* @param \Adyen\Payment\Logger\AdyenLogger $adyenLogger
* @param \Adyen\Payment\Helper\Data $adyenHelper
* @param OrderRepositoryInterface $orderRepository
* @param AdyenLogger $adyenLogger
* @param Data $adyenHelper
* @param PaymentResponseHandler $paymentResponseHandler
*/
public function __construct(
\Magento\Sales\Api\OrderRepositoryInterface $orderRepository,
\Adyen\Payment\Logger\AdyenLogger $adyenLogger,
\Adyen\Payment\Helper\Data $adyenHelper
OrderRepositoryInterface $orderRepository,
AdyenLogger $adyenLogger,
Data $adyenHelper,
PaymentResponseHandler $paymentResponseHandler
) {
$this->orderRepository = $orderRepository;
$this->adyenLogger = $adyenLogger;
$this->adyenHelper = $adyenHelper;
$this->paymentResponseHandler = $paymentResponseHandler;
}
/**
......@@ -69,39 +80,23 @@ class AdyenOrderPaymentStatus implements \Adyen\Payment\Api\AdyenOrderPaymentSta
*/
public function getOrderPaymentStatus($orderId)
{
try {
$order = $this->orderRepository->get($orderId);
$payment = $order->getPayment();
if ($payment->getMethod() === AdyenCcConfigProvider::CODE ||
$payment->getMethod() === AdyenOneclickConfigProvider::CODE
) {
$additionalInformation = $payment->getAdditionalInformation();
$type = null;
if (!empty($additionalInformation['threeDSType'])) {
$type = $additionalInformation['threeDSType'];
}
$token = null;
if (!empty($additionalInformation['threeDS2Token'])) {
$token = $additionalInformation['threeDS2Token'];
}
return $this->adyenHelper->buildThreeDS2ProcessResponseJson($type, $token);
} catch (NoSuchEntityException $exception) {
$this->adyenLogger->addError('Order not found.');
return json_encode(
$this->paymentResponseHandler->formatPaymentResponse(PaymentResponseHandler::ERROR)
);
}
/**
* If payment method result is Pending and action is provided provide component action back to checkout
*/
if ($payment->getMethod() === AdyenHppConfigProvider::CODE) {
$payment = $order->getPayment();
$additionalInformation = $payment->getAdditionalInformation();
if (
!empty($additionalInformation['action']) &&
$additionalInformation['resultCode'] == 'Pending'
) {
return json_encode(['action' => $additionalInformation['action']]);
}
if (empty($additionalInformation['resultCode'])) {
$this->adyenLogger->addInfo('resultCode is empty in the payment\'s additional information');
return json_encode(
$this->paymentResponseHandler->formatPaymentResponse(PaymentResponseHandler::ERROR)
);
}
/**
* If payment method is google pay
......@@ -113,6 +108,10 @@ class AdyenOrderPaymentStatus implements \Adyen\Payment\Api\AdyenOrderPaymentSta
}
}
return true;
return json_encode($this->paymentResponseHandler->formatPaymentResponse(
$additionalInformation['resultCode'],
!empty($additionalInformation['action']) ? $additionalInformation['action'] : null,
!empty($additionalInformation['additionalData']) ? $additionalInformation['additionalData'] : null
));
}
}
......@@ -23,14 +23,17 @@
namespace Adyen\Payment\Model;
use Adyen\Payment\Api\AdyenThreeDS2ProcessInterface;
use Adyen\AdyenException;
use Adyen\Payment\Api\AdyenPaymentDetailsInterface;
use Adyen\Payment\Helper\Data;
use Adyen\Payment\Helper\PaymentResponseHandler;
use Adyen\Payment\Helper\Vault;
use Adyen\Payment\Logger\AdyenLogger;
use Magento\Checkout\Model\Session;
use Magento\Sales\Model\OrderFactory;
use Magento\Framework\Exception\LocalizedException;
use Magento\Sales\Api\OrderRepositoryInterface;
class AdyenThreeDS2Process implements AdyenThreeDS2ProcessInterface
class AdyenPaymentDetails implements AdyenPaymentDetailsInterface
{
/**
* @var Session
......@@ -42,11 +45,6 @@ class AdyenThreeDS2Process implements AdyenThreeDS2ProcessInterface
*/
private $adyenHelper;
/**
* @var OrderFactory
*/
private $orderFactory;
/**
* @var AdyenLogger
*/
......@@ -58,26 +56,39 @@ class AdyenThreeDS2Process implements AdyenThreeDS2ProcessInterface
private $vaultHelper;
/**
* AdyenThreeDS2Process constructor.
* @var OrderRepositoryInterface
*/
private $orderRepository;
/**
* @var PaymentResponseHandler
*/
private $paymentResponseHandler;
/**
* AdyenPaymentDetails constructor.
*
* @param Session $checkoutSession
* @param Data $adyenHelper
* @param OrderFactory $orderFactory
* @param AdyenLogger $adyenLogger
* @param Vault $vaultHelper
* @param OrderRepositoryInterface $orderRepository
* @param PaymentResponseHandler $paymentResponseHandler
*/
public function __construct(
Session $checkoutSession,
Data $adyenHelper,
OrderFactory $orderFactory,
AdyenLogger $adyenLogger,
Vault $vaultHelper
Vault $vaultHelper,
OrderRepositoryInterface $orderRepository,
PaymentResponseHandler $paymentResponseHandler
) {
$this->checkoutSession = $checkoutSession;
$this->adyenHelper = $adyenHelper;
$this->orderFactory = $orderFactory;
$this->adyenLogger = $adyenLogger;
$this->vaultHelper = $vaultHelper;
$this->orderRepository = $orderRepository;
$this->paymentResponseHandler = $paymentResponseHandler;
}
/**
......@@ -92,130 +103,65 @@ class AdyenThreeDS2Process implements AdyenThreeDS2ProcessInterface
// Validate JSON that has just been parsed if it was in a valid format
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \Magento\Framework\Exception\LocalizedException(
__('3D secure 2.0 failed because the request was not a valid JSON')
);
throw new LocalizedException(__('Payment details call failed because the request was not a valid JSON'));
}
//Get order from payload and remove orderId from the array
if (empty($payload['orderId'])) {
$order = $this->getOrder();
// In the next major release remove support for retrieving order from session and throw exception instead
//throw new \Magento\Framework\Exception\LocalizedException
//(__('3D secure 2.0 failed because of a missing order id'));
throw new LocalizedException
(__('Payment details call failed because of a missing order ID'));
} else {
// Create order by order id
$order = $this->orderFactory->create()->load($payload['orderId']);
// don't send orderId to adyen. Improve that orderId and state.data are separated in payload
$order = $this->orderRepository->get($payload['orderId']);
//TODO send state.data from frontend so no unsetting is necessary
unset($payload['orderId']);
}
$payment = $order->getPayment();
// Init payments/details request
$result = [];
if ($paymentData = $payment->getAdditionalInformation("adyenPaymentData")) {
// Add payment data into the request object
$request = [
"paymentData" => $paymentData
];
$payload["paymentData"] = $paymentData;
// unset payment data from additional information
$payment->unsAdditionalInformation("adyenPaymentData");
} else {
$this->adyenLogger->error("3D secure 2.0 failed, payment data not found");
throw new \Magento\Framework\Exception\LocalizedException(
__('3D secure 2.0 failed, payment data not found')
);
}
// Depends on the component's response we send a fingerprint or the challenge result
if (!empty($payload['details']['threeds2.fingerprint'])) {
$request['details']['threeds2.fingerprint'] = $payload['details']['threeds2.fingerprint'];
} elseif (!empty($payload['details']['threeds2.challengeResult'])) {
$request['details']['threeds2.challengeResult'] = $payload['details']['threeds2.challengeResult'];
} elseif (!empty($payload)) {
$request = $payload;
$message = "Payment details call failed, payment data not found";
$this->adyenLogger->error($message);
throw new LocalizedException(__($message));
}
// Send the request
try {
$client = $this->adyenHelper->initializeAdyenClient($order->getStoreId());
$service = $this->adyenHelper->createAdyenCheckoutService($client);
$result = $service->paymentsDetails($request);
} catch (\Adyen\AdyenException $e) {
$this->adyenLogger->error("3D secure 2.0 failed" . $e->getMessage());
throw new \Magento\Framework\Exception\LocalizedException(__('3D secure 2.0 failed'));
$paymentDetails = $service->paymentsDetails($payload);
} catch (AdyenException $e) {
$this->adyenLogger->error("Payment details call failed: " . $e->getMessage());
throw new LocalizedException(__('Payment details call failed'));
}
// Check if result is challenge shopper, if yes return the token
if (!empty($result['resultCode']) &&
$result['resultCode'] === 'ChallengeShopper' &&
!empty($result['authentication']['threeds2.challengeToken'])
) {
return $this->adyenHelper->buildThreeDS2ProcessResponseJson(
$result['resultCode'],
$result['authentication']['threeds2.challengeToken']
);
//TODO test this with payments that return additionalData
//TODO check for Authorized result code and move to the handler
if (!empty($paymentDetails['additionalData'])) {
$this->vaultHelper->saveRecurringDetails($payment, $paymentDetails['additionalData']);
}
//Fallback for 3DS in case of redirect
if (!empty($result['resultCode']) &&
$result['resultCode'] === 'RedirectShopper'
) {
$response['type'] = $result['resultCode'];
$response['action']= $result['action'];
return json_encode($response);
}
// Save the payments response because we are going to need it during the place order flow
$payment->setAdditionalInformation("paymentsResponse", $result);
if (!empty($result['additionalData'])) {
$this->vaultHelper->saveRecurringDetails($payment, $result['additionalData']);
}
// To actually save the additional info changes into the quote
$order->save();
//TODO check if order save is necessary to save additionalData
$response = [];
if ($result['resultCode'] != 'Authorised') {
if (!$this->paymentResponseHandler->handlePaymentResponse($paymentDetails, $payment, $order)) {
$this->checkoutSession->restoreQuote();
// Always cancel the order if the paymenth has failed
if (!$order->canCancel()) {
$order->setState(\Magento\Sales\Model\Order::STATE_NEW);
throw new LocalizedException(__('The payment is REFUSED.'));
}
$order->cancel()->save();
$this->adyenLogger->error(
sprintf("Payment details call failed for action or 3ds2 payment method, resultcode is %s Raw API responds: %s",
$result['resultCode'],
print_r($result, true)
));
throw new \Magento\Framework\Exception\LocalizedException(__('The payment is REFUSED.'));
$action = null;
if (!empty($paymentDetails['action'])) {
$action = $paymentDetails['action'];
}
$response['result'] = $result['resultCode'];
return json_encode($response);
$additionalData = null;
if (!empty($paymentDetails['additionalData'])) {
$additionalData = $paymentDetails['additionalData'];
}
/**
* Get order object
*
* @return \Magento\Sales\Model\Order
* @deprecated Will be removed in 7.0.0
*/
protected function getOrder()
{
$incrementId = $this->checkoutSession->getLastRealOrderId();
$order = $this->orderFactory->create()->loadByIncrementId($incrementId);
return $order;
return json_encode($this->paymentResponseHandler->formatPaymentResponse($paymentDetails['resultCode'], $action, $additionalData));
}
}
......@@ -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;
}
......
......@@ -43,9 +43,9 @@ class AdyenPosCloudConfigProvider implements ConfigProviderInterface
protected $urlBuilder;
/**
* @var \Adyen\Payment\Helper\PaymentMethods
* @var \Adyen\Payment\Helper\ConnectedTerminals
*/
protected $paymentMethodsHelper;
protected $connectedTerminalsHelper;
/**
* @var \Adyen\Payment\Helper\Data
......@@ -62,20 +62,20 @@ class AdyenPosCloudConfigProvider implements ConfigProviderInterface
*
* @param \Magento\Framework\App\RequestInterface $request
* @param \Magento\Framework\UrlInterface $urlBuilder
* @param \Adyen\Payment\Helper\PaymentMethods $paymentMethodsHelper
* @param \Adyen\Payment\Helper\ConnectedTerminals $connectedTerminalsHelper
* @param \Adyen\Payment\Helper\Data $adyenHelper
* @param \Magento\Framework\Serialize\SerializerInterface $serializer
*/
public function __construct(
\Magento\Framework\App\RequestInterface $request,
\Magento\Framework\UrlInterface $urlBuilder,
\Adyen\Payment\Helper\PaymentMethods $paymentMethodsHelper,
\Adyen\Payment\Helper\ConnectedTerminals $connectedTerminalsHelper,
\Adyen\Payment\Helper\Data $adyenHelper,
\Magento\Framework\Serialize\SerializerInterface $serializer
) {
$this->request = $request;
$this->urlBuilder = $urlBuilder;
$this->paymentMethodsHelper = $paymentMethodsHelper;
$this->connectedTerminalsHelper = $connectedTerminalsHelper;
$this->adyenHelper = $adyenHelper;
$this->serializer = $serializer;
}
......@@ -137,7 +137,7 @@ class AdyenPosCloudConfigProvider implements ConfigProviderInterface
*/
protected function getConnectedTerminals()
{
$connectedTerminals = $this->paymentMethodsHelper->getConnectedTerminals();
$connectedTerminals = $this->connectedTerminalsHelper->getConnectedTerminals();
if (!empty($connectedTerminals['uniqueTerminalIds'])) {
return $connectedTerminals['uniqueTerminalIds'];
......
......@@ -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);
}
foreach ($this->additionalInformationList as $additionalInformationKey) {
if (isset($additionalData[$additionalInformationKey])) {
$paymentInfo->setAdditionalInformation(
$additionalInformationKey,
$additionalData[$additionalInformationKey]
// 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);
}
// 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);
}
foreach ($this->additionalInformationList as $additionalInformationKey) {
if (isset($additionalData[$additionalInformationKey])) {
$paymentInfo->setAdditionalInformation(
$additionalInformationKey,
$additionalData[$additionalInformationKey]
// 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);
}
// set ccType
if (!empty($additionalData[self::BRAND_CODE])) {
$paymentInfo->setCcType($additionalData[self::BRAND_CODE]);
}
}
}
......@@ -14,10 +14,11 @@
}
],
"require": {
"adyen/php-api-library": "^7.0",
"adyen/php-api-library": "^8",
"magento/framework": ">=101.0.8 <102 || >=102.0.1",
"magento/module-vault": "101.*",
"magento/module-paypal": ">=100.2.6"
"magento/module-paypal": ">=100.2.6",
"ext-json": "*"
},
"require-dev": {
"phpunit/phpunit": "~6.5.0",
......
......@@ -367,7 +367,7 @@
<argument name="transferFactory" xsi:type="object">Adyen\Payment\Gateway\Http\TransferFactory</argument>
<argument name="client" xsi:type="object">Adyen\Payment\Gateway\Http\Client\TransactionAuthorization
</argument>
<argument name="validator" xsi:type="object">GeneralResponseValidator</argument>
<argument name="validator" xsi:type="object">CheckoutResponseValidator</argument>
<argument name="handler" xsi:type="object">AdyenPaymentCcVaultResponseHandlerComposite</argument>
</arguments>
</virtualType>
......@@ -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>
</argument>
</arguments>
</virtualType>
......@@ -885,14 +884,6 @@
</arguments>
</virtualType>
<!--General Response validator-->
<virtualType name="GeneralResponseValidator" type="Adyen\Payment\Gateway\Validator\GeneralResponseValidator">
<arguments>
<argument name="loggerInterface" xsi:type="object">Adyen\Payment\Logger\AdyenLogger</argument>
</arguments>
</virtualType>
<!--Checkout Response validator-->
<virtualType name="CheckoutResponseValidator" type="Adyen\Payment\Gateway\Validator\CheckoutResponseValidator">
<arguments>
......@@ -1017,8 +1008,8 @@
type="Adyen\Payment\Model\AdyenRequestMerchantSession"/>
<preference for="Adyen\Payment\Api\AdyenInitiateTerminalApiInterface"
type="Adyen\Payment\Model\AdyenInitiateTerminalApi"/>
<preference for="Adyen\Payment\Api\AdyenThreeDS2ProcessInterface"
type="Adyen\Payment\Model\AdyenThreeDS2Process"/>
<preference for="Adyen\Payment\Api\AdyenPaymentDetailsInterface"
type="Adyen\Payment\Model\AdyenPaymentDetails"/>
<preference for="Adyen\Payment\Api\AdyenOriginKeyInterface"
type="Adyen\Payment\Model\AdyenOriginKey"/>
<preference for="Adyen\Payment\Api\AdyenOrderPaymentStatusInterface"
......
......@@ -58,8 +58,8 @@
</resources>
</route>
<route url="/V1/adyen/threeDS2Process" method="POST">
<service class="Adyen\Payment\Api\AdyenThreeDS2ProcessInterface" method="initiate"/>
<route url="/V1/adyen/paymentDetails" method="POST">
<service class="Adyen\Payment\Api\AdyenPaymentDetailsInterface" method="initiate"/>
<resources>
<resource ref="anonymous"/>
</resources>
......
<?php
/**
* ######
* ######
* ############ ####( ###### #####. ###### ############ ############
* ############# #####( ###### #####. ###### ############# #############
* ###### #####( ###### #####. ###### ##### ###### ##### ######
* ###### ###### #####( ###### #####. ###### ##### ##### ##### ######
* ###### ###### #####( ###### #####. ###### ##### ##### ######
* ############# ############# ############# ############# ##### ######
* ############ ############ ############# ############ ##### ######
* ######
* #############
* ############
*
* Adyen Payment module (https://www.adyen.com/)
*
* Copyright (c) 2015 Adyen BV (https://www.adyen.com/)
* See LICENSE.txt for license details.
*
* Author: Adyen <magento@adyen.com>
*/
/** @var Adyen\Payment\Block\Redirect\Redirect $block */
?>
<?php
if ($block->getRedirectMethod() == "GET") { ?>
<body>
<script>
window.location.replace("<?= $block->escapeJs($block->getRedirectUrl()); ?>");
</script>
</body>
<?php
} else { ?>
<body onload="document.getElementById('3dform').submit();">
<form method="POST" action="<?= $block->escapeHtml($block->getRedirectUrl()); ?>" id="3dform">
<input type="hidden" name="PaReq" value="<?= $block->escapeHtml($block->getPaReq()); ?>"/>
<input type="hidden" name="MD" value="<?= $block->escapeHtml($block->getMd()); ?>"/>
<input type="hidden" name="TermUrl" value="<?= $block->escapeHtml($block->getTermUrl()); ?>"/>
<noscript>
<br>
<br>
<div style="text-align: center">
<h1>Processing your 3-D Secure Transaction</h1>
<p>Please click continue to continue the processing of your 3-D Secure transaction.</p>
<input type="submit" class="button" value="continue"/>
</div>
</noscript>
</form>
</body>
<?php
} ?>
......@@ -1638,3 +1638,15 @@
overflow:auto;
padding-top: 0;
}
/**
* Configuration for Google Pay
*/
#googlePay.disabled {
cursor: pointer;
opacity: 0.75;
}
#googlePay.disabled > * {
pointer-events: none;
}
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.
<?xml version="1.0"?>
<!--
/**
* ######
* ######
......@@ -16,15 +14,29 @@
*
* 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>
*/
-->
<layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd">
<container name="root">
<block class="Adyen\Payment\Block\Redirect\Redirect" name="adyen-redirect-form"
template="redirect/redirect.phtml" cacheable="false"/>
</container>
</layout>
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;
},
};
}
);
/**
* 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),
);
},
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
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 3DS2 components returns in the onComplete callback needs to be sent to the
* backend to the /adyen/threeDS2Process endpoint and based on the response render a new threeDS2
* component or place the order (validateThreeDS2OrPlaceOrder)
* @param response
*/
processThreeDS2: function (data) {
var payload = {
"payload": JSON.stringify(data)
};
var serviceUrl = urlBuilder.createUrl('/adyen/threeDS2Process', {});
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,91 +25,74 @@ 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/threeds2',
'Magento_Checkout/js/model/error-processor',
'Adyen_Payment/js/model/adyen-payment-service',
'adyenCheckout'
'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,
threeds2,
errorProcessor,
adyenPaymentService,
AdyenCheckout
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
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({
locale: this.getLocale(),
originKey: this.getOriginKey(),
environment: this.getCheckoutEnvironment()
});
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',
initObservable: function() {
this._super().observe([
'creditCardType', // TODO check if still needed
'installment',
'installments',
'creditCardDetailsValid',
'placeOrderAllowed'
'placeOrderAllowed',
]);
return this;
......@@ -118,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,
......@@ -127,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()) {
......@@ -138,40 +123,24 @@ define(
// installments
var allInstallments = self.getAllInstallments();
var cardNode = document.getElementById('cardContainer');
self.cardComponent = self.checkout.create('card', {
originKey: self.getOriginKey(),
environment: self.getCheckoutEnvironment(),
type: 'card',
hasHolderName: true,
holderNameRequired: true,
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) {
......@@ -181,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) {
......@@ -192,142 +163,58 @@ 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);
},
/**
* 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
*/
renderThreeDS2Component: function (type, token, orderId) {
var self = this;
var threeDS2Node = document.getElementById('threeDS2Container');
if (type == "IdentifyShopper") {
self.threeDS2IdentifyComponent = self.checkout
.create('threeDS2DeviceFingerprint', {
fingerprintToken: token,
onComplete: function (result) {
self.threeDS2IdentifyComponent.unmount();
var request = result.data;
request.orderId = orderId;
threeds2.processThreeDS2(request).done(function (responseJSON) {
self.validateThreeDS2OrPlaceOrder(responseJSON, orderId)
}).fail(function (result) {
errorProcessor.process(result, self.messageContainer);
self.isPlaceOrderActionAllowed(true);
fullScreenLoader.stopLoader();
});
}).mount('#cardContainer');
},
onError: function (error) {
console.log(JSON.stringify(error));
}
});
self.threeDS2IdentifyComponent.mount(threeDS2Node);
} else if (type == "ChallengeShopper") {
fullScreenLoader.stopLoader();
var popupModal = $('#threeDS2Modal').modal({
// disable user to hide popup
clickableOverlay: false,
responsive: true,
innerScroll: false,
// empty buttons, we don't need that
buttons: [],
modalClass: 'threeDS2Modal'
});
popupModal.modal("openModal");
handleAction: function(action, orderId) {
var self = this;
self.threeDS2ChallengeComponent = self.checkout
.create('threeDS2Challenge', {
challengeToken: token,
size: '05',
onComplete: function (result) {
self.threeDS2ChallengeComponent.unmount();
self.closeModal(popupModal);
fullScreenLoader.startLoader();
var request = result.data;
request.orderId = orderId;
threeds2.processThreeDS2(request).done(function (responseJSON) {
self.validateThreeDS2OrPlaceOrder(responseJSON, orderId);
}).fail(function (result) {
errorProcessor.process(result, self.messageContainer);
self.isPlaceOrderActionAllowed(true);
fullScreenLoader.stopLoader();
});
},
onError: function (error) {
console.log(JSON.stringify(error));
}
});
self.threeDS2ChallengeComponent.mount(threeDS2Node);
try {
self.checkoutComponent.createFromAction(
action).mount('#cc_actionContainer');
} catch (e) {
console.log(e);
}
},
threedsfallback: function (action) {
var self = this;
var actionNode = document.getElementById('ActionContainer');
self.threedsfallbackComponent = self.checkout.createFromAction(action).mount(actionNode);
},
/**
* 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");
$('.threeDS2Modal').remove();
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
$('#threeDS2Wrapper').append("<div id=\"threeDS2Modal\">" +
"<div id=\"threeDS2Container\"></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;
......@@ -336,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
......@@ -348,7 +238,7 @@ define(
* @param event
* @returns {boolean}
*/
placeOrder: function (data, event) {
placeOrder: function(data, event) {
var self = this;
if (event) {
......@@ -359,20 +249,21 @@ define(
fullScreenLoader.startLoader();
self.isPlaceOrderActionAllowed(false);
self.getPlaceOrderDeferredObject()
.fail(
function () {
self.getPlaceOrderDeferredObject().fail(
function() {
fullScreenLoader.stopLoader();
self.isPlaceOrderActionAllowed(true);
}
},
).done(
function (orderId) {
function(orderId) {
self.afterPlaceOrder();
adyenPaymentService.getOrderPaymentStatus(orderId)
.done(function (responseJSON) {
self.validateThreeDS2OrPlaceOrder(responseJSON, orderId)
self.orderId = orderId;
adyenPaymentService.getOrderPaymentStatus(orderId).
done(function(responseJSON) {
self.handleAdyenResult(responseJSON,
orderId);
});
}
},
);
}
return false;
......@@ -381,76 +272,85 @@ define(
* Based on the response we can start a 3DS2 validation or place the order
* @param responseJSON
*/
validateThreeDS2OrPlaceOrder: function (responseJSON, orderId) {
handleAdyenResult: function(responseJSON, orderId) {
var self = this;
var response = JSON.parse(responseJSON);
if (!!response.threeDS2) {
// render component
self.renderThreeDS2Component(response.type, response.token, orderId);
} else {
if (response.type === 'RedirectShopper' && response.action) {
self.threedsfallback(response.action);
} else {
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);
}
},
/**
* Validates the payment date when clicking the pay button
*
* @returns {boolean}
*/
validate: function () {
handleOnAdditionalDetails: function(result) {
var self = this;
var form = 'form[data-role=adyen-cc-form]';
var request = result.data;
request.orderId = self.orderId;
fullScreenLoader.stopLoader();
var validate = $(form).validation() && $(form).validation('isValid');
// TODO outsource creating the modal
var popupModal = $('#cc_actionModal').modal({
// disable user to hide popup
clickableOverlay: false,
responsive: true,
innerScroll: false,
// empty buttons, we don't need that
buttons: [],
modalClass: 'cc_actionModal',
});
if (!validate) {
return false;
}
popupModal.modal('openModal');
return true;
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 if the typed in card holder is valid
* - length validation, can not be empty
* Validates the payment date when clicking the pay button
*
* @returns {boolean}
*/
isCardOwnerValid: function () {
if (this.creditCardOwner().length == 0) {
validate: function() {
var form = 'form[data-role=adyen-cc-form]';
var validate = $(form).validation() &&
$(form).validation('isValid') &&
this.cardComponent.isValid;
if (!validate) {
this.cardComponent.showValidation();
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
......@@ -458,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);
},
......@@ -467,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;
}
......@@ -520,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()];
},
});
}
},
);
......@@ -35,51 +35,57 @@ define(
'mage/url',
'Magento_Vault/js/view/payment/vault-enabler',
'Adyen_Payment/js/model/adyen-payment-service',
'adyenCheckout'
'Adyen_Payment/js/bundle',
],
function (ko, $, Component, placeOrderAction, additionalValidators, quote, urlBuilder, fullScreenLoader, url, VaultEnabler, adyenPaymentService,AdyenCheckout) {
function (
ko,
$,
Component,
placeOrderAction,
additionalValidators,
quote,
urlBuilder,
fullScreenLoader,
url,
VaultEnabler,
adyenPaymentService,
AdyenComponent
) {
'use strict';
/**
* Shareble adyen checkout component
* @type {AdyenCheckout}
*/
var checkoutComponent;
return Component.extend({
self: this,
defaults: {
template: 'Adyen_Payment/payment/google-pay-form',
googlePayToken: null,
googlePayAllowed: null
googlePayAllowed: null,
},
/**
* @returns {Boolean}
*/
isShowLegend: function () {
isShowLegend: function() {
return true;
},
setPlaceOrderHandler: function (handler) {
setPlaceOrderHandler: function(handler) {
this.placeOrderHandler = handler;
},
setValidateHandler: function (handler) {
setValidateHandler: function(handler) {
this.validateHandler = handler;
},
getCode: function () {
getCode: function() {
return 'adyen_google_pay';
},
isActive: function () {
isActive: function() {
return true;
},
initObservable: function () {
this._super()
.observe([
initObservable: function() {
this._super().observe([
'googlePayToken',
'googlePayAllowed'
'googlePayAllowed',
]);
return this;
}, initialize: function () {
var self = this;
},
initialize: function() {
this.additionalValidators = additionalValidators;
this.vaultEnabler = new VaultEnabler();
this.vaultEnabler.setPaymentCode(this.getVaultCode());
......@@ -87,7 +93,7 @@ define(
this._super();
},
renderGooglePay: function () {
renderGooglePay: function() {
this.googlePayNode = document.getElementById('googlePay');
var self = this;
......@@ -96,8 +102,8 @@ define(
originKey: self.getOriginKey(),
environment: self.getCheckoutEnvironment(),
risk: {
enabled: false
}
enabled: false,
},
});
var googlepay = self.checkoutComponent.create('paywithgoogle', {
showPayButton: true,
......@@ -109,25 +115,24 @@ define(
// https://developers.google.com/pay/api/web/reference/object#MerchantInfo
merchantIdentifier: self.getMerchantIdentifier(),
merchantName: self.getMerchantAccount()
merchantName: self.getMerchantAccount(),
},
// Payment
amount: self.formatAmount(quote.totals().grand_total, self.getFormat()),
currency: quote.totals().quote_currency_code,
totalPriceStatus: 'FINAL',
// empty onSubmit to resolve javascript issues.
onSubmit: function() {},
onSubmit: function () {
},
onChange: function (state) {
if (!!state.isValid) {
self.googlePayToken(state.data.paymentMethod.googlePayToken);
self.getPlaceOrderDeferredObject()
.fail(
self.getPlaceOrderDeferredObject().fail(
function () {
fullScreenLoader.stopLoader();
self.isPlaceOrderActionAllowed(true);
}
},
).done(
function (orderId) {
self.afterPlaceOrder();
......@@ -135,21 +140,21 @@ define(
.done(function (responseJSON) {
self.validateThreeDSOrPlaceOrder(responseJSON)
});
}
},
);
}
},
buttonColor: 'black', // default/black/white
buttonType: 'long', // long/short
showButton: true // show or hide the Google Pay button
showButton: true, // show or hide the Google Pay button
});
var promise = googlepay.isAvailable();
promise.then(function (success) {
googlepay.isAvailable().then(function() {
self.googlePayAllowed(true);
googlepay.mount(self.googlePayNode);
$(self.googlePayNode).find('button').prop('disabled', true);
}, function (error) {
self.googlePayNode.addEventListener('click', function () {
self.validate();
});
}, function(error) {
console.log(error);
self.googlePayAllowed(false);
});
......@@ -171,59 +176,55 @@ define(
}
},
isGooglePayAllowed: function () {
if (this.googlePayAllowed()) {
return true;
}
return false;
return !!this.googlePayAllowed();
},
getMerchantAccount: function () {
getMerchantAccount: function() {
return window.checkoutConfig.payment.adyenGooglePay.merchantAccount;
},
showLogo: function () {
showLogo: function() {
return window.checkoutConfig.payment.adyen.showLogo;
},
getLocale: function () {
getLocale: function() {
return window.checkoutConfig.payment.adyenGooglePay.locale;
},
getFormat: function () {
getFormat: function() {
return window.checkoutConfig.payment.adyenGooglePay.format;
},
getMerchantIdentifier: function () {
getMerchantIdentifier: function() {
return window.checkoutConfig.payment.adyenGooglePay.merchantIdentifier;
},
context: function () {
context: function() {
return this;
},
validate: function (hideErrors) {
validate: function(hideErrors) {
return this.additionalValidators.validate(hideErrors);
},
getControllerName: function () {
getControllerName: function() {
return window.checkoutConfig.payment.iframe.controllerName[this.getCode()];
},
getPlaceOrderUrl: function () {
getPlaceOrderUrl: function() {
return window.checkoutConfig.payment.iframe.placeOrderUrl[this.getCode()];
},
/**
* Get data for place order
* @returns {{method: *}}
*/
getData: function () {
getData: function() {
return {
'method': "adyen_google_pay",
'method': 'adyen_google_pay',
'additional_data': {
'token': this.googlePayToken()
}
'token': this.googlePayToken(),
},
};
},
/**
* Return the formatted currency. Adyen accepts the currency in multiple formats.
* @param $amount
* @param $currency
* @param amount
* @param format
* @return string
*/
formatAmount: function (amount, format) {
return Math.round(amount * (Math.pow(10, format)))
return Math.round(amount * (Math.pow(10, format))).toString();
},
isVaultEnabled: function () {
return this.vaultEnabler.isVaultEnabled();
......@@ -237,8 +238,8 @@ define(
getCheckoutEnvironment: function () {
return window.checkoutConfig.payment.adyenGooglePay.checkoutEnvironment;
},
onPaymentMethodContentChange: function (data, event) {
$(this.googlePayNode).find('button').prop('disabled', !this.validate());
onPaymentMethodContentChange: function () {
$(this.googlePayNode).toggleClass('disabled', !this.validate());
}
});
}
......
......@@ -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>
......@@ -28,143 +28,159 @@ define(
'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/threeds2',
'Magento_Checkout/js/model/error-processor',
'adyenCheckout'
'Adyen_Payment/js/bundle',
'Adyen_Payment/js/model/adyen-configuration',
],
function (ko, $, Component, selectPaymentMethodAction, quote, checkoutData, additionalValidators, storage, urlBuilder, adyenPaymentService, customer, fullScreenLoader, placeOrderAction, layout, Messages, threeds2, errorProcessor, AdyenCheckout) {
function(
ko,
$,
Component,
selectPaymentMethodAction,
quote,
checkoutData,
additionalValidators,
adyenPaymentService,
fullScreenLoader,
placeOrderAction,
layout,
Messages,
errorProcessor,
AdyenComponent,
adyenConfiguration,
) {
'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);
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: ''
orderId: 0,
paymentMethods: {}
},
initObservable: function () {
this._super()
.observe([
initObservable: function() {
this._super().observe([
'brandCode',
'issuer',
'gender',
'dob',
'telephone',
'ownerName',
'ibanNumber',
'ssn',
'bankAccountNumber',
'bankLocationId'
'paymentMethod',
'adyenPaymentMethods'
]);
return this;
}, initialize: function () {
},
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()
var paymentMethodsObserver = adyenPaymentService.getPaymentMethods();
paymentMethodsObserver.subscribe(function(paymentMethodsResponse) {
self.loadAdyenPaymentMethods(paymentMethodsResponse);
});
// reset variable:
adyenPaymentService.setPaymentMethods();
self.loadAdyenPaymentMethods(paymentMethodsObserver());
},
loadAdyenPaymentMethods: function(paymentMethodsResponse) {
var self = this;
adyenPaymentService.retrieveAvailablePaymentMethods(function () {
var paymentMethods = adyenPaymentService.getAvailablePaymentMethods();
if (JSON.stringify(paymentMethods).indexOf("ratepay") > -1) {
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'
l: 'Checkout',
};
// Load Ratepay script
var ratepayScriptTag = document.createElement('script');
ratepayScriptTag.src = "//d.ratepay.com/" + ratePayId + "/di.js";
ratepayScriptTag.type = "text/javascript";
ratepayScriptTag.src = '//d.ratepay.com/' + ratePayId +
'/di.js';
ratepayScriptTag.type = 'text/javascript';
document.body.appendChild(ratepayScriptTag);
}
self.adyenPaymentMethods(self.getAdyenHppPaymentMethods(paymentMethodsResponse));
fullScreenLoader.stopLoader();
});
}
},
getAdyenHppPaymentMethods: function () {
getAdyenHppPaymentMethods: function(paymentMethodsResponse) {
var self = this;
var currentShippingAddressCountryCode = quote.shippingAddress().countryId;
// retrieve new payment methods if country code changed
if (shippingAddressCountryCode != currentShippingAddressCountryCode) {
fullScreenLoader.startLoader();
adyenPaymentService.retrieveAvailablePaymentMethods();
shippingAddressCountryCode = currentShippingAddressCountryCode;
fullScreenLoader.stopLoader();
}
var paymentMethods = adyenPaymentService.getAvailablePaymentMethods();
var paymentMethods = paymentMethodsResponse.paymentMethodsResponse.paymentMethods;
var paymentList = _.reduce(paymentMethods, function (accumulator, value) {
var paymentList = _.reduce(paymentMethods,
function(accumulator, paymentMethod) {
if (!self.isPaymentMethodSupported(value.type)) {
if (!self.isPaymentMethodSupported(
paymentMethod.type)) {
return accumulator;
}
var messageContainer = new Messages();
var name = 'messages-' + self.getBrandCodeFromPaymentMethod(value);
var name = 'messages-' +
self.getBrandCodeFromPaymentMethod(paymentMethod);
var messagesComponent = {
parent: self.name,
name: name,
displayArea: name,
component: 'Magento_Ui/js/view/messages',
config: {
messageContainer: messageContainer
}
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(value);
result.getBrandCode = function() {
return self.getBrandCodeFromPaymentMethod(
paymentMethod);
};
result.value = result.getBrandCode();
result.name = value;
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
......@@ -172,16 +188,18 @@ define(
* @type {observable}
*/
result.placeOrderAllowed = ko.observable(true);
result.getCode = function () {
result.getCode = function() {
return self.item.method;
};
result.getMessageName = function () {
return 'messages-' + self.getBrandCodeFromPaymentMethod(value)
result.getMessageName = function() {
return 'messages-' +
self.getBrandCodeFromPaymentMethod(
paymentMethod);
};
result.getMessageContainer = function () {
result.getMessageContainer = function() {
return messageContainer;
}
result.validate = function () {
};
result.validate = function() {
return self.validate(result.getBrandCode());
};
result.placeRedirectOrder = function placeRedirectOrder(data) {
......@@ -192,22 +210,26 @@ define(
self.isPlaceOrderActionAllowed(false);
$.when(
placeOrderAction(data, self.currentMessageContainer)
placeOrderAction(data,
self.currentMessageContainer),
).fail(
function (response) {
function(response) {
self.isPlaceOrderActionAllowed(true);
fullScreenLoader.stopLoader();
self.showErrorMessage(response);
}
},
).done(
function (orderId) {
function(orderId) {
self.afterPlaceOrder();
adyenPaymentService.getOrderPaymentStatus(orderId)
.done(function (responseJSON) {
self.validateActionOrPlaceOrder(responseJSON, orderId);
adyenPaymentService.getOrderPaymentStatus(
orderId).
done(function(responseJSON) {
self.validateActionOrPlaceOrder(
responseJSON,
orderId);
});
}
)
},
);
};
/**
......@@ -216,352 +238,163 @@ define(
* @param bool
* @returns {*}
*/
result.isPlaceOrderAllowed = function (bool) {
result.isPlaceOrderAllowed = function(bool) {
self.isPlaceOrderActionAllowed(bool);
return result.placeOrderAllowed(bool);
};
result.afterPlaceOrder = function () {
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;
}
return false;
};
/**
* Checks if payment method is after pay
* @returns {boolean}
*/
result.isPaymentMethodAfterPay = function () {
if (result.getBrandCode() === "afterpay_default") {
return true;
}
return false;
};
/**
* Checks if payment method is after pay touch
* @returns {boolean}
*/
result.isPaymentMethodAfterPayTouch = function () {
if (result.getBrandCode() === "afterpaytouch") {
return true;
}
return false;
};
/**
* Get personal number (SSN) length based on the buyer's country
* @returns {number}
*/
result.getSsnLength = function () {
if (quote.billingAddress().countryId == "NO") {
//14 digits for Norway ÅÅÅÅMMDD-XXXXX
return 14;
} else {
//13 digits for other Nordic countries ÅÅÅÅMMDD-XXXX
return 13;
}
};
/**
* Get max length for the Bank account number
*/
result.getBankAccountNumberMaxLength = function () {
return 17;
};
/**
* Finds the issuer property in the payment method's response and if available returns it's index
* @returns
*/
result.findIssuersProperty = function () {
var issuerKey = false;
if (typeof value.details !== 'undefined') {
$.each(value.details, function (key, detail) {
if (typeof detail.items !== 'undefined' && detail.key == 'issuer') {
issuerKey = key;
}
});
}
return issuerKey;
}
/**
* Checks if the payment method has issuers property available
* @returns {boolean}
*/
result.hasIssuersProperty = function () {
if (result.findIssuersProperty() !== false) {
return true;
}
return false;
};
/**
* Checks if the payment method has issuer(s) available
* @returns {boolean}
*/
result.hasIssuersAvailable = function () {
if (result.hasIssuersProperty() && value.details[result.findIssuersProperty()].items.length > 0) {
return true;
}
return false;
};
/**
* Returns the issuers for a payment method
* @returns {*}
*/
result.getIssuers = function () {
if (result.hasIssuersAvailable()) {
return value.details[result.findIssuersProperty()].items;
}
return [];
};
/**
* Checks if payment method is iDeal
* @returns {boolean}
*/
result.isIdeal = function () {
if (result.getBrandCode().indexOf("ideal") >= 0) {
return true;
}
return false;
};
/**
* Checks if payment method is ACH
* @returns {boolean}
*/
result.isAch = function () {
if (result.getBrandCode().indexOf("ach") == 0) {
return true;
}
return false;
};
/**
* Checks if payment method is sepa direct debit
*/
result.isSepaDirectDebit = function () {
if (result.getBrandCode().indexOf("sepadirectdebit") >= 0) {
return true;
}
return false;
};
/**
* Renders the secure fields,
* creates the ideal component,
* sets up the callbacks for ideal components and
*/
result.renderIdealComponent = function () {
result.renderCheckoutComponent = function() {
result.isPlaceOrderAllowed(false);
var idealNode = document.getElementById('iDealContainer');
var showPayButton = false;
const showPayButtonPaymentMethods = [
'paypal',
'applePay',
'googlePay'
];
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);
if (showPayButtonPaymentMethods.includes(
paymentMethod.type)) {
showPayButton = true;
}
}
});
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"]);
// 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);
} else {
result.isPlaceOrderAllowed(false);
}
}
});
sepaDirectDebit.mount(sepaDirectDebitNode);
};
/**
* Creates the klarna component,
* sets up the callbacks for klarna components
*/
result.renderKlarnaComponent = function () {
/* The new Klarna integration doesn't return details and the component does not handle it */
if (!value.details) {
return;
}
var klarnaNode = document.getElementById('klarnaContainer');
var klarna = self.checkoutComponent.create('klarna', {
countryCode: self.getLocale(),
details: self.filterOutOpenInvoiceComponentDetails(value.details),
visibility: {
personalDetails: "editable"
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,
},
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);
};
/**
* 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"
billingAddress: {
city: city,
country: country,
houseNumberOrName: '',
postalCode: postalCode,
street: street,
},
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);
},
onChange: function(state) {
result.isPlaceOrderAllowed(state.isValid);
}
});
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
}
}).mount(document.getElementById('afterPayContainer'));
};
result.continueToAdyenBrandCode = function () {
// 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()) {
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();
additionalData.brand_code = self.brandCode;
let stateData;
if ('component' in self) {
stateData = self.component.data;
} else {
stateData = {
paymentMethod: {
type: self.brandCode
}
} else if (self.isSepaDirectDebit()) {
additionalData.ownerName = this.ownerName();
additionalData.ibanNumber = this.ibanNumber();
} else if (self.isAch()) {
additionalData.bankAccountOwnerName = this.ownerName();
additionalData.bankAccountNumber = this.bankAccountNumber();
additionalData.bankLocationId = this.bankLocationId();
};
}
data.additional_data = additionalData;
this.placeRedirectOrder(data);
}
additionalData.stateData = JSON.stringify(stateData);
return false;
if (brandCode() == 'ratepay') {
additionalData.df_value = this.getRatePayDeviceIdentToken();
}
if (result.hasIssuersProperty()) {
if (!result.hasIssuersAvailable()) {
return false;
data.additional_data = additionalData;
this.placeRedirectOrder(data);
}
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();
return false;
};
result.getRatePayDeviceIdentToken = function () {
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;
}
}
}
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;
......@@ -576,40 +409,33 @@ define(
* @param paymentMethod
* @returns {boolean}
*/
isPaymentMethodSupported: function (paymentMethod) {
isPaymentMethodSupported: function(paymentMethod) {
if (paymentMethod == 'wechatpayWeb') {
return true;
}
for (var i = 0; i < unsupportedPaymentMethods.length; i++) {
var match = paymentMethod.match(unsupportedPaymentMethods[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 () {
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
}
'method': self.method,
'po_number': null,
'additional_data': {
brand_code: self.brandCode,
},
};
// set the brandCode
brandCode(self.value);
brandCode(self.brandCode);
// set payment method
paymentMethod(self.method);
......@@ -623,18 +449,18 @@ define(
* 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");
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>");
$('#ActionWrapper').append('<div id="ActionModal">' +
'<div id="ActionContainer"></div>' +
'</div>');
},
isBrandCodeChecked: ko.computed(function () {
isBrandCodeChecked: ko.computed(function() {
if (!quote.paymentMethod()) {
return null;
......@@ -645,43 +471,30 @@ define(
}
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) {
validateActionOrPlaceOrder: function(responseJSON, orderId) {
var self = this;
var response = JSON.parse(responseJSON);
if (!!response.action) {
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);
} 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) {
renderActionComponent: function(action) {
var self = this;
var actionNode = document.getElementById('ActionContainer');
fullScreenLoader.stopLoader();
self.popupModal = $('#ActionModal').modal({
......@@ -691,13 +504,14 @@ define(
innerScroll: false,
// empty buttons, we don't need that
buttons: [],
modalClass: 'ActionModal'
modalClass: 'ActionModal',
});
self.popupModal.modal("openModal");
self.actionComponent = self.checkoutComponent.createFromAction(action).mount(actionNode);
self.popupModal.modal('openModal');
self.actionComponent = self.checkoutComponent.createFromAction(
action).mount(actionNode);
},
handleOnAdditionalDetails: function (state, component) {
handleOnAdditionalDetails: function(state, component) {
var self = this;
// call endpoint with state.data
......@@ -705,14 +519,15 @@ define(
request.orderId = self.orderId;
// Using the same processor as 3DS2, refactor to generic name in a upcomming release will be breaking change for merchants.
threeds2.processThreeDS2(request).done(function () {
adyenPaymentService.paymentDetails(request).done(function() {
$.mage.redirect(
window.checkoutConfig.payment[quote.paymentMethod().method].redirectUrl
window.checkoutConfig.payment[quote.paymentMethod().method].redirectUrl,
);
}).fail(function (response) {
}).fail(function(response) {
fullScreenLoader.stopLoader();
self.closeModal(self.popupModal);
errorProcessor.process(response, self.currentMessageContainer);
errorProcessor.process(response,
self.currentMessageContainer);
self.isPlaceOrderActionAllowed(true);
self.showErrorMessage(response);
});
......@@ -721,20 +536,26 @@ define(
* Issue with the default currentMessageContainer needs to be resolved for now just throw manually the eror message
* @param response
*/
showErrorMessage: function (response) {
showErrorMessage: function(response) {
if (!!response['responseJSON'].parameters) {
$("#messages-" + brandCode()).text((response['responseJSON'].message).replace('%1', response['responseJSON'].parameters[0])).slideDown();
$('#messages-' + brandCode()).
text((response['responseJSON'].message).replace('%1',
response['responseJSON'].parameters[0])).
slideDown();
} else {
$("#messages-" + brandCode()).text(response['responseJSON'].message).slideDown();
$('#messages-' + brandCode()).
text(response['responseJSON'].message).
slideDown();
}
setTimeout(function () {
$("#messages-" + brandCode()).slideUp();
setTimeout(function() {
$('#messages-' + brandCode()).slideUp();
}, 10000);
},
validate: function (brandCode) {
validate: function(brandCode) {
var form = '#payment_form_' + this.getCode() + '_' + brandCode;
var validate = $(form).validation() && $(form).validation('isValid');
var validate = $(form).validation() &&
$(form).validation('isValid');
if (!validate) {
return false;
......@@ -747,65 +568,17 @@ define(
* (in checkout api it is the type)
* @returns {*}
*/
getBrandCodeFromPaymentMethod: function (paymentMethod) {
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;
}
});
if (!!detailObject) {
return {
"key": parentDetail.key,
"type": parentDetail.type,
"details": self.filterUndefinedItemsInArray(detailObject)
};
}
}
});
return self.filterUndefinedItemsInArray(filteredDetails);
getRatePayDeviceIdentToken: function() {
return window.checkoutConfig.payment.adyenHpp.deviceIdentToken;
},
/**
* Helper function to filter out the undefined items from an array
* @param arr
* @returns {*}
*/
filterUndefinedItemsInArray: function (arr) {
return arr.filter(function (item) {
return typeof item !== 'undefined';
});
},
getOriginKey: function () {
return window.checkoutConfig.payment.adyen.originKey;
},
getCheckoutEnvironment: function () {
return window.checkoutConfig.payment.adyen.checkoutEnvironment;
}
});
}
);
......@@ -33,18 +33,17 @@ 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/threeds2',
'Magento_Checkout/js/model/error-processor',
'Adyen_Payment/js/model/adyen-payment-service',
'adyenCheckout'
],
function (
'Adyen_Payment/js/bundle',
],
function(
ko,
_,
$,
......@@ -57,17 +56,15 @@ define(
layout,
Messages,
url,
threeDS2Utils,
fullScreenLoader,
setPaymentMethodAction,
urlBuilder,
storage,
placeOrderAction,
threeds2,
errorProcessor,
adyenPaymentService,
AdyenCheckout
) {
AdyenComponent
) {
'use strict';
......@@ -85,26 +82,26 @@ define(
template: 'Adyen_Payment/payment/oneclick-form',
recurringDetailReference: '',
variant: '',
numberOfInstallments: ''
numberOfInstallments: '',
},
initObservable: function () {
this._super()
.observe([
initObservable: function() {
this._super().observe([
'recurringDetailReference',
'creditCardType',
'encryptedCreditCardVerificationNumber',
'variant',
'numberOfInstallments'
'numberOfInstallments',
]);
return this;
},
initialize: function () {
initialize: function() {
var self = this;
this._super();
// create component needs to be in initialize method
var messageComponents = {};
_.map(window.checkoutConfig.payment.adyenOneclick.billingAgreements, function (value) {
_.map(window.checkoutConfig.payment.adyenOneclick.billingAgreements,
function(value) {
var messageContainer = new Messages();
var name = 'messages-' + value.reference_id;
......@@ -115,8 +112,8 @@ define(
displayArea: 'messages-' + value.reference_id,
component: 'Magento_Ui/js/view/messages',
config: {
messageContainer: messageContainer
}
messageContainer: messageContainer,
},
};
layout([messagesComponent]);
......@@ -130,7 +127,7 @@ define(
*
* @returns {Array}
*/
getAdyenBillingAgreements: function () {
getAdyenBillingAgreements: function() {
var self = this;
// shareable adyen checkout component
......@@ -139,12 +136,14 @@ define(
originKey: self.getOriginKey(),
environment: self.getCheckoutEnvironment(),
risk: {
enabled: false
}
enabled: false,
},
});
// convert to list so you can iterate
var paymentList = _.map(window.checkoutConfig.payment.adyenOneclick.billingAgreements, function (value) {
var paymentList = _.map(
window.checkoutConfig.payment.adyenOneclick.billingAgreements,
function(value) {
var creditCardExpMonth, creditCardExpYear = false;
......@@ -156,22 +155,26 @@ define(
// pre-define installments if they are set
var i, installments = [];
var grandTotal = quote.totals().grand_total;
var dividedString = "";
var dividedString = '';
var dividedAmount = 0;
if (value.number_of_installments) {
for (i = 0; i < value.number_of_installments.length; i++) {
dividedAmount = (grandTotal / value.number_of_installments[i]).toFixed(quote.getPriceFormat().precision);
dividedString = value.number_of_installments[i] + " x " + dividedAmount + " " + quote.totals().quote_currency_code;
dividedAmount = (grandTotal /
value.number_of_installments[i]).toFixed(
quote.getPriceFormat().precision);
dividedString = value.number_of_installments[i] + ' x ' +
dividedAmount + ' ' + quote.totals().quote_currency_code;
installments.push({
key: [dividedString],
value: value.number_of_installments[i]
value: value.number_of_installments[i],
});
}
}
var messageContainer = self.messageComponents['messages-' + value.reference_id];
var messageContainer = self.messageComponents['messages-' +
value.reference_id];
// for recurring enable the placeOrder button at all times
var placeOrderAllowed = true;
......@@ -196,9 +199,11 @@ define(
'getInstallments': ko.observableArray(installments),
'placeOrderAllowed': ko.observable(placeOrderAllowed),
isButtonActive: function () {
return self.isActive() && this.getCode() == self.isChecked() && self.isBillingAgreementChecked() && this.placeOrderAllowed() && self.isPlaceOrderActionAllowed();
isButtonActive: function() {
return self.isActive() && this.getCode() == self.isChecked() &&
self.isBillingAgreementChecked() &&
this.placeOrderAllowed() &&
self.isPlaceOrderActionAllowed();
},
/**
* Custom place order function
......@@ -209,7 +214,7 @@ define(
* @param event
* @returns {boolean}
*/
placeOrder: function (data, event) {
placeOrder: function(data, event) {
var self = this;
if (event) {
......@@ -227,20 +232,20 @@ define(
fullScreenLoader.startLoader();
self.isPlaceOrderActionAllowed(false);
self.getPlaceOrderDeferredObject()
.fail(
function () {
self.getPlaceOrderDeferredObject().fail(
function() {
fullScreenLoader.stopLoader();
self.isPlaceOrderActionAllowed(true);
}
},
).done(
function (orderId) {
function(orderId) {
self.afterPlaceOrder();
adyenPaymentService.getOrderPaymentStatus(orderId)
.done(function (responseJSON) {
self.validateThreeDS2OrPlaceOrder(responseJSON, orderId)
adyenPaymentService.getOrderPaymentStatus(orderId).
done(function(responseJSON) {
self.validateThreeDS2OrPlaceOrder(responseJSON,
orderId);
});
}
},
);
}
return false;
......@@ -251,13 +256,14 @@ define(
* creates the card component,
* sets up the callbacks for card components
*/
renderSecureCVC: function () {
renderSecureCVC: function() {
var self = this;
if (!self.getOriginKey()) {
return;
}
var oneClickCardNode = document.getElementById('cvcContainer-' + self.value);
var oneClickCardNode = document.getElementById(
'cvcContainer-' + self.value);
var hideCVC = false;
// hide cvc if contract has been stored as recurring
......@@ -265,51 +271,52 @@ define(
hideCVC = true;
}
var oneClickCard = checkout
.create('card', {
var oneClickCard = checkout.create('card', {
hideCVC: hideCVC,
brand: self.agreement_data.variant,
storedPaymentMethodId: this.value,
expiryMonth: self.agreement_data.card.expiryMonth,
expiryYear: self.agreement_data.card.expiryYear,
holderName: self.agreement_data.card.holderName,
onChange: function (state, component) {
onChange: function(state, component) {
if (state.isValid) {
self.placeOrderAllowed(true);
isValid(true);
if (typeof state.data !== 'undefined' &&
typeof state.data.paymentMethod !== 'undefined' &&
typeof state.data.paymentMethod.encryptedSecurityCode !== 'undefined'
typeof state.data.paymentMethod.encryptedSecurityCode !==
'undefined'
) {
self.encryptedCreditCardVerificationNumber = state.data.paymentMethod.encryptedSecurityCode;
}
} else {
self.encryptedCreditCardVerificationNumber = '';
if (self.agreement_data.variant != "maestro") {
if (self.agreement_data.variant != 'maestro') {
self.placeOrderAllowed(false);
isValid(false);
}
}
}
})
.mount(oneClickCardNode);
},
}).mount(oneClickCardNode);
window.adyencheckout = oneClickCard;
},
/**
* Based on the response we can start a 3DS2 validation or place the order
* @param responseJSON
*/
validateThreeDS2OrPlaceOrder: function (responseJSON, orderId) {
validateThreeDS2OrPlaceOrder: function(responseJSON, orderId) {
var self = this;
var response = JSON.parse(responseJSON);
if (!!response.threeDS2) {
// render component
self.renderThreeDS2Component(response.type, response.token, orderId);
self.renderThreeDS2Component(response.type, response.token,
orderId);
} else {
window.location.replace(url.build(window.checkoutConfig.payment[quote.paymentMethod().method].redirectUrl));
window.location.replace(url.build(
window.checkoutConfig.payment[quote.paymentMethod().method].redirectUrl));
}
},
/**
......@@ -323,33 +330,38 @@ define(
* @param type
* @param token
*/
renderThreeDS2Component: function (type, token, orderId) {
renderThreeDS2Component: function(type, token, orderId) {
var self = this;
var threeDS2Node = document.getElementById('threeDS2ContainerOneClick');
var threeDS2Node = document.getElementById(
'threeDS2ContainerOneClick');
if (type == "IdentifyShopper") {
self.threeDS2Component = checkout.create('threeDS2DeviceFingerprint', {
if (type == 'IdentifyShopper') {
self.threeDS2Component = checkout.create(
'threeDS2DeviceFingerprint', {
fingerprintToken: token,
onComplete: function (result) {
onComplete: function(result) {
var request = result.data;
request.orderId = orderId;
threeds2.processThreeDS2(request).done(function (responseJSON) {
self.validateThreeDS2OrPlaceOrder(responseJSON, orderId)
}).fail(function (result) {
errorProcessor.process(result, self.getMessageContainer());
adyenPaymentService.paymentDetails(request).
done(function(responseJSON) {
self.validateThreeDS2OrPlaceOrder(responseJSON,
orderId);
}).
fail(function(result) {
errorProcessor.process(result,
self.getMessageContainer());
self.isPlaceOrderActionAllowed(true);
fullScreenLoader.stopLoader();
});
},
onError: function (error) {
onError: function(error) {
console.log(JSON.stringify(error));
}
},
});
} else if (type == "ChallengeShopper") {
} else if (type == 'ChallengeShopper') {
fullScreenLoader.stopLoader();
var popupModal = $('#threeDS2ModalOneClick').modal({
// disable user to hide popup
clickableOverlay: false,
......@@ -357,30 +369,34 @@ define(
innerScroll: false,
// empty buttons, we don't need that
buttons: [],
modalClass: 'threeDS2Modal'
modalClass: 'threeDS2Modal',
});
popupModal.modal("openModal");
popupModal.modal('openModal');
self.threeDS2Component = checkout
.create('threeDS2Challenge', {
self.threeDS2Component = checkout.create('threeDS2Challenge',
{
challengeToken: token,
onComplete: function (result) {
popupModal.modal("closeModal");
onComplete: function(result) {
popupModal.modal('closeModal');
fullScreenLoader.startLoader();
var request = result.data;
request.orderId = orderId;
threeds2.processThreeDS2(request).done(function (responseJSON) {
self.validateThreeDS2OrPlaceOrder(responseJSON, orderId)
}).fail(function (result) {
errorProcessor.process(result, self.getMessageContainer());
adyenPaymentService.paymentDetails(request).
done(function(responseJSON) {
self.validateThreeDS2OrPlaceOrder(responseJSON,
orderId);
}).
fail(function(result) {
errorProcessor.process(result,
self.getMessageContainer());
self.isPlaceOrderActionAllowed(true);
fullScreenLoader.stopLoader();
});
},
onError: function (error) {
onError: function(error) {
console.log(JSON.stringify(error));
}
},
});
}
......@@ -391,12 +407,13 @@ define(
*
* @returns {{method: *, additional_data: {variant: *, recurring_detail_reference: *, number_of_installments: *, cvc: (string|*), expiryMonth: *, expiryYear: *}}}
*/
getData: function () {
getData: function() {
var self = this;
var browserInfo = threeDS2Utils.getBrowserInfo();
// todo use state.data
var browserInfo = [];
return {
"method": self.method,
'method': self.method,
additional_data: {
variant: variant(),
recurring_detail_reference: recurringDetailReference(),
......@@ -408,11 +425,11 @@ define(
screen_width: browserInfo.screenWidth,
screen_height: browserInfo.screenHeight,
timezone_offset: browserInfo.timeZoneOffset,
language: browserInfo.language
}
language: browserInfo.language,
},
};
},
validate: function () {
validate: function() {
var code = self.item.method;
var value = this.value;
......@@ -420,42 +437,45 @@ define(
var form = 'form[data-role=' + codeValue + ']';
var validate = $(form).validation() && $(form).validation('isValid');
var validate = $(form).validation() &&
$(form).validation('isValid');
// bcmc does not have any cvc
if (!validate || (isValid() == false && variant() != "bcmc" && variant() != "maestro")) {
if (!validate ||
(isValid() == false && variant() != 'bcmc' && variant() !=
'maestro')) {
return false;
}
return true;
},
getCode: function () {
getCode: function() {
return self.item.method;
},
hasVerification: function () {
return self.hasVerification()
hasVerification: function() {
return self.hasVerification();
},
getMessageName: function () {
getMessageName: function() {
return 'messages-' + value.reference_id;
},
getMessageContainer: function () {
getMessageContainer: function() {
return messageContainer;
},
getOriginKey: function () {
getOriginKey: function() {
return self.getOriginKey();
},
isPlaceOrderActionAllowed: function () {
isPlaceOrderActionAllowed: function() {
return self.isPlaceOrderActionAllowed(); // needed for placeOrder method
},
afterPlaceOrder: function () {
afterPlaceOrder: function() {
return self.afterPlaceOrder(); // needed for placeOrder method
},
getPlaceOrderDeferredObject: function () {
getPlaceOrderDeferredObject: function() {
return $.when(
placeOrderAction(this.getData(), this.getMessageContainer())
placeOrderAction(this.getData(), this.getMessageContainer()),
);
}
}
},
};
});
return paymentList;
......@@ -465,16 +485,16 @@ define(
*
* @returns {boolean}
*/
selectBillingAgreement: function () {
selectBillingAgreement: function() {
var self = this;
// set payment method data
var data = {
"method": self.method,
"po_number": null,
"additional_data": {
recurring_detail_reference: self.value
}
'method': self.method,
'po_number': null,
'additional_data': {
recurring_detail_reference: self.value,
},
};
// set the brandCode
......@@ -489,7 +509,7 @@ define(
return true;
},
isBillingAgreementChecked: ko.computed(function () {
isBillingAgreementChecked: ko.computed(function() {
if (!quote.paymentMethod()) {
return null;
......@@ -502,45 +522,47 @@ define(
}),
placeOrderHandler: null,
validateHandler: null,
setPlaceOrderHandler: function (handler) {
setPlaceOrderHandler: function(handler) {
this.placeOrderHandler = handler;
},
setValidateHandler: function (handler) {
setValidateHandler: function(handler) {
this.validateHandler = handler;
},
getPlaceOrderUrl: function () {
getPlaceOrderUrl: function() {
return window.checkoutConfig.payment.iframe.placeOrderUrl[this.getCode()];
},
getCode: function () {
getCode: function() {
return window.checkoutConfig.payment.adyenOneclick.methodCode;
},
isActive: function () {
isActive: function() {
return true;
},
getControllerName: function () {
getControllerName: function() {
return window.checkoutConfig.payment.iframe.controllerName[this.getCode()];
},
context: function () {
context: function() {
return this;
},
canCreateBillingAgreement: function () {
canCreateBillingAgreement: function() {
return window.checkoutConfig.payment.adyenCc.canCreateBillingAgreement;
},
isShowLegend: function () {
isShowLegend: function() {
return true;
},
hasVerification: function () {
hasVerification: function() {
return window.checkoutConfig.payment.adyenOneclick.hasCustomerInteraction;
},
getLocale: function () {
getLocale: function() {
return window.checkoutConfig.payment.adyenOneclick.locale;
},
getOriginKey: function () {
getOriginKey: function() {
return window.checkoutConfig.payment.adyen.originKey;
},
getCheckoutEnvironment: function () {
getCheckoutEnvironment: function() {
return window.checkoutConfig.payment.adyen.checkoutEnvironment;
}
},
});
}
);
}
)
;
......@@ -95,9 +95,9 @@
<div class="checkout-component-dock" afterRender="renderSecureFields()" data-bind="attr: { id: 'cardContainer'}"></div>
</div>
<div id="threeDS2Wrapper">
<div id="threeDS2Modal">
<div id="threeDS2Container"></div>
<div id="cc_actionModalWrapper">
<div id="cc_actionModal">
<div id="cc_actionContainer"></div>
</div>
</div>
......
......@@ -51,7 +51,7 @@
<!--/ko-->
</div>
<div class="field number googlePay">
<div class="checkout-component-dock" afterRender="renderGooglePay()" data-bind="attr: { id: 'googlePay'}">
<div class="checkout-component-dock disabled" afterRender="renderGooglePay()" data-bind="attr: { id: 'googlePay'}">
<!-- ko ifnot: isGooglePayAllowed() -->
Google Pay is not available
<!--/ko--></div>
......
......@@ -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,13 @@
<!--/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}'>
<legend>Brand Code</legend>
<div data-bind='attr: {id: "adyen-alternative-payment-container-" + brandCode}'
afterRender="renderCheckoutComponent()"></div>
</fieldset>
<div class="checkout-agreements-block">
......@@ -309,15 +79,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