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

Commit 4d1745b6 authored by cyattilakiss's avatar cyattilakiss Committed by GitHub

Merge pull request #478 from Adyen/develop

Release 4.3.0
parents 31da74f6 578230a5
......@@ -28,7 +28,8 @@ interface AdyenInitiateTerminalApiInterface
{
/**
* Trigger sync call on terminal
* @param string $payload
* @return mixed
*/
public function initiate();
public function initiate($payload);
}
......@@ -41,7 +41,19 @@ class Installment extends \Magento\Framework\View\Element\Html\Select
'9' => '9x',
'10' => '10x',
'11' => '11x',
'12' => '12x'
'12' => '12x',
'13' => '13x',
'14' => '14x',
'15' => '15x',
'16' => '16x',
'17' => '17x',
'18' => '18x',
'19' => '19x',
'20' => '20x',
'21' => '21x',
'22' => '22x',
'23' => '23x',
'24' => '24x'
];
/**
......
......@@ -87,9 +87,9 @@ class Cc extends \Magento\Payment\Block\Form\Cc
/**
* @return string
*/
public function getCheckoutContextUrl()
public function getCheckoutEnvironment()
{
return $this->adyenHelper->getCheckoutContextUrl($this->checkoutSession->getQuote()->getStore()->getId());
return $this->adyenHelper->getCheckoutEnvironment($this->checkoutSession->getQuote()->getStore()->getId());
}
/**
......
......@@ -217,11 +217,11 @@ class Redirect extends \Magento\Payment\Block\Form
if ($this->_adyenHelper->isSeparateHouseNumberRequired($billingAddress->getCountryId())) {
$street = $this->_adyenHelper->getStreet($billingAddress);
if (isset($street['name']) && $street['name'] != "") {
if (!empty($street['name'])) {
$formFields['billingAddress.street'] = $street['name'];
}
if (isset($street['house_number']) && $street['house_number'] != "") {
if (!empty($street['house_number'])) {
$formFields['billingAddress.houseNumberOrName'] = $street['house_number'];
} else {
$formFields['billingAddress.houseNumberOrName'] = "NA";
......
......@@ -75,6 +75,7 @@ class Json extends \Magento\Framework\App\Action\Action
$request = $this->getRequest();
if ($request instanceof Http && $request->isPost()) {
$request->setParam('isAjax', true);
$request->getHeaders()->addHeaderLine('X_REQUESTED_WITH', 'XMLHttpRequest');
}
}
}
......
......@@ -249,7 +249,7 @@ class Result extends \Magento\Framework\App\Action\Action
break;
case Notification::RECEIVED:
$result = true;
if (strpos($paymentMethod, "alipay_hk_web") !== false) {
if (strpos($paymentMethod, "alipay_hk") !== false) {
$result = false;
}
$this->_adyenLogger->addAdyenResult('Do nothing wait for the notification');
......@@ -382,7 +382,10 @@ class Result extends \Magento\Framework\App\Action\Action
$request = [];
if (!empty($this->_session->getLastRealOrder()->getPayment()->getAdditionalInformation("paymentData"))) {
if (!empty($this->_session->getLastRealOrder()) &&
!empty($this->_session->getLastRealOrder()->getPayment()) &&
!empty($this->_session->getLastRealOrder()->getPayment()->getAdditionalInformation("paymentData"))
) {
$request['paymentData'] = $this->_session->getLastRealOrder()->getPayment()->getAdditionalInformation("paymentData");
}
......
......@@ -92,7 +92,7 @@ class TransactionPosCloudSync implements ClientInterface
//always do status call and return the response of the status call
$service = $this->adyenHelper->createAdyenPosPaymentService($this->client);
$poiId = $this->adyenHelper->getPoiId($this->storeId);
$poiId = $request['terminalID'];
$newServiceID = date("dHis");
$statusDate = date("U");
......
......@@ -25,7 +25,6 @@ namespace Adyen\Payment\Gateway\Request;
use Magento\Payment\Gateway\Request\BuilderInterface;
use Adyen\Payment\Observer\AdyenHppDataAssignObserver;
use Adyen\Payment\Observer\AdyenBoletoDataAssignObserver;
class CheckoutDataBuilder implements BuilderInterface
{
......@@ -40,39 +39,23 @@ class CheckoutDataBuilder implements BuilderInterface
private $storeManager;
/**
* @var \Magento\Checkout\Model\Session
* @var \Magento\Quote\Api\CartRepositoryInterface
*/
private $checkoutSession;
private $cartRepository;
/**
* @var \Magento\Quote\Model\Quote
*/
private $quote;
/**
* @var \Magento\Tax\Model\Config
*/
protected $taxConfig;
/**
* CheckoutDataBuilder constructor.
* @param \Adyen\Payment\Helper\Data $adyenHelper
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
* @param \Magento\Checkout\Model\Session $checkoutSession
* @param \Magento\Tax\Model\Config $taxConfig
* @param \Magento\Quote\Api\CartRepositoryInterface $cartRepository
*/
public function __construct(
\Adyen\Payment\Helper\Data $adyenHelper,
\Magento\Store\Model\StoreManagerInterface $storeManager,
\Magento\Checkout\Model\Session $checkoutSession,
\Magento\Tax\Model\Config $taxConfig
)
{
\Magento\Quote\Api\CartRepositoryInterface $cartRepository
) {
$this->adyenHelper = $adyenHelper;
$this->storeManager = $storeManager;
$this->checkoutSession = $checkoutSession;
$this->quote = $checkoutSession->getQuote();
$this->taxConfig = $taxConfig;
$this->cartRepository = $cartRepository;
}
/**
......@@ -204,21 +187,24 @@ class CheckoutDataBuilder implements BuilderInterface
}
/**
* @param $formFields
* @return mixed
* @param \Magento\Sales\Model\Order $order
*
* @throws \Magento\Framework\Exception\NoSuchEntityException
*
* @return array
*/
protected function getOpenInvoiceData($order)
protected function getOpenInvoiceData($order): array
{
$formFields = [
'lineItems' => []
];
$currency = $this->quote->getCurrency();
/** @var \Magento\Quote\Model\Quote $cart */
$cart = $this->cartRepository->get($order->getQuoteId());
$currency = $cart->getCurrency();
$discountAmount = 0;
foreach ($this->quote->getAllVisibleItems() as $item) {
foreach ($cart->getAllVisibleItems() as $item) {
$numberOfItems = (int)$item->getQty();
// Summarize the discount amount item by item
......@@ -263,20 +249,18 @@ class CheckoutDataBuilder implements BuilderInterface
}
// Shipping cost
if ($this->quote->getShippingAddress()->getShippingAmount() > 0 || $this->quote->getShippingAddress()->getShippingTaxAmount() > 0) {
if ($cart->getShippingAddress()->getShippingAmount() > 0 || $cart->getShippingAddress()->getShippingTaxAmount() > 0) {
$priceExcludingTax = $this->quote->getShippingAddress()->getShippingAmount() - $this->quote->getShippingAddress()->getShippingTaxAmount();
$priceExcludingTax = $cart->getShippingAddress()->getShippingAmount() - $cart->getShippingAddress()->getShippingTaxAmount();
$formattedTaxAmount = $this->adyenHelper->formatAmount($this->quote->getShippingAddress()->getShippingTaxAmount(), $currency);
$formattedTaxAmount = $this->adyenHelper->formatAmount($cart->getShippingAddress()->getShippingTaxAmount(), $currency);
$formattedPriceExcludingTax = $this->adyenHelper->formatAmount($priceExcludingTax, $currency);
$taxClassId = $this->taxConfig->getShippingTaxClass($this->storeManager->getStore()->getId());
$formattedTaxPercentage = 0;
if ($priceExcludingTax !== 0) {
$formattedTaxPercentage = $this->quote->getShippingAddress()->getShippingTaxAmount() / $priceExcludingTax * 100 * 100;
$formattedTaxPercentage = $cart->getShippingAddress()->getShippingTaxAmount() / $priceExcludingTax * 100 * 100;
}
$formFields['lineItems'][] = [
......
......@@ -41,7 +41,8 @@ class PosCloudBuilder implements BuilderInterface
return [
"response" => $payment->getAdditionalInformation("terminalResponse"),
"serviceID" => $payment->getAdditionalInformation("serviceID"),
"initiateDate" => $payment->getAdditionalInformation("initiateDate")
"initiateDate" => $payment->getAdditionalInformation("initiateDate"),
"terminalID" => $payment->getAdditionalInformation("terminal_id")
];
}
}
......@@ -32,6 +32,11 @@ class CheckoutResponseValidator extends AbstractValidator
*/
private $adyenLogger;
/**
* @var \Adyen\Payment\Helper\Data
*/
private $adyenHelper;
/**
* GeneralResponseValidator constructor.
*
......@@ -40,9 +45,11 @@ class CheckoutResponseValidator extends AbstractValidator
*/
public function __construct(
\Magento\Payment\Gateway\Validator\ResultInterfaceFactory $resultFactory,
\Adyen\Payment\Logger\AdyenLogger $adyenLogger
\Adyen\Payment\Logger\AdyenLogger $adyenLogger,
\Adyen\Payment\Helper\Data $adyenHelper
) {
$this->adyenLogger = $adyenLogger;
$this->adyenHelper = $adyenHelper;
parent::__construct($resultFactory);
}
......@@ -80,6 +87,13 @@ class CheckoutResponseValidator extends AbstractValidator
}
}
// 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);
}
$payment->setAdditionalInformation('pspReference', $response['pspReference']);
break;
case "Received":
......
......@@ -32,6 +32,11 @@ class GeneralResponseValidator extends AbstractValidator
*/
private $adyenLogger;
/**
* @var \Adyen\Payment\Helper\Data
*/
private $adyenHelper;
/**
* GeneralResponseValidator constructor.
*
......@@ -40,9 +45,11 @@ class GeneralResponseValidator extends AbstractValidator
*/
public function __construct(
\Magento\Payment\Gateway\Validator\ResultInterfaceFactory $resultFactory,
\Adyen\Payment\Logger\AdyenLogger $adyenLogger
\Adyen\Payment\Logger\AdyenLogger $adyenLogger,
\Adyen\Payment\Helper\Data $adyenHelper
) {
$this->adyenLogger = $adyenLogger;
$this->adyenHelper = $adyenHelper;
parent::__construct($resultFactory);
}
......@@ -65,6 +72,14 @@ class GeneralResponseValidator extends AbstractValidator
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']);
......
......@@ -33,10 +33,8 @@ class Data extends AbstractHelper
const MODULE_NAME = 'adyen-magento2';
const TEST = 'test';
const LIVE = 'live';
const CHECKOUT_CONTEXT_URL_LIVE = 'https://checkoutshopper-live.adyen.com/checkoutshopper/';
const CHECKOUT_CONTEXT_URL_TEST = 'https://checkoutshopper-test.adyen.com/checkoutshopper/';
const CHECKOUT_COMPONENT_JS_LIVE = 'https://checkoutshopper-live.adyen.com/checkoutshopper/sdk/2.5.0/adyen.js';
const CHECKOUT_COMPONENT_JS_TEST = 'https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/2.5.0/adyen.js';
const CHECKOUT_COMPONENT_JS_LIVE = 'https://checkoutshopper-live.adyen.com/checkoutshopper/sdk/3.0.0/adyen.js';
const CHECKOUT_COMPONENT_JS_TEST = 'https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/3.0.0/adyen.js';
/**
* @var \Magento\Framework\Encryption\EncryptorInterface
......@@ -128,6 +126,12 @@ class Data extends AbstractHelper
*/
private $config;
/**
* @var \Magento\Backend\Helper\Data $helperBackend
*/
private $helperBackend;
/**
* Data constructor.
* @param \Magento\Framework\App\Helper\Context $context
......@@ -147,6 +151,9 @@ class Data extends AbstractHelper
* @param \Magento\Framework\App\CacheInterface $cache
* @param \Adyen\Payment\Model\Billing\AgreementFactory $billingAgreementFactory
* @param \Adyen\Payment\Model\ResourceModel\Billing\Agreement $agreementResourceModel
* @param \Magento\Framework\Locale\ResolverInterface $localeResolver
* @param \Magento\Framework\App\Config\ScopeConfigInterface $config
* @param \Magento\Backend\Helper\Data $helperBackend
*/
public function __construct(
\Magento\Framework\App\Helper\Context $context,
......@@ -167,7 +174,8 @@ class Data extends AbstractHelper
\Adyen\Payment\Model\Billing\AgreementFactory $billingAgreementFactory,
\Adyen\Payment\Model\ResourceModel\Billing\Agreement $agreementResourceModel,
\Magento\Framework\Locale\ResolverInterface $localeResolver,
\Magento\Framework\App\Config\ScopeConfigInterface $config
\Magento\Framework\App\Config\ScopeConfigInterface $config,
\Magento\Backend\Helper\Data $helperBackend
) {
parent::__construct($context);
$this->_encryptor = $encryptor;
......@@ -188,6 +196,7 @@ class Data extends AbstractHelper
$this->agreementResourceModel = $agreementResourceModel;
$this->localeResolver = $localeResolver;
$this->config = $config;
$this->helperBackend = $helperBackend;
}
/**
......@@ -249,39 +258,34 @@ class Data extends AbstractHelper
public function formatAmount($amount, $currency)
{
switch ($currency) {
case "JPY":
case "IDR":
case "KRW":
case "BYR":
case "VND":
case "CVE":
case "DJF":
case "GNF":
case "IDR":
case "JPY":
case "KMF":
case "KRW":
case "PYG":
case "RWF":
case "UGX":
case "VND":
case "VUV":
case "XAF":
case "XOF":
case "XPF":
case "GHC":
case "KMF":
$format = 0;
break;
case "MRO":
$format = 1;
break;
case "BHD":
case "IQD":
case "JOD":
case "KWD":
case "OMR":
case "LYD":
case "OMR":
case "TND":
$format = 3;
break;
default:
$format = 2;
break;
}
return (int)number_format($amount, $format, '', '');
......@@ -389,6 +393,9 @@ class Data extends AbstractHelper
if (count($street) != 1) {
return $street;
}
$street['0'] = trim($street['0']);
preg_match('/((\s\d{0,10})|(\s\d{0,10}\w{1,3}))$/i', $street['0'], $houseNumber, PREG_OFFSET_CAPTURE);
if (!empty($houseNumber['0'])) {
$_houseNumber = trim($houseNumber['0']['0']);
......@@ -460,18 +467,6 @@ class Data extends AbstractHelper
return $this->getConfigData($field, 'adyen_cc_vault', $storeId, true);
}
/**
* Gives back adyen_cc_threeds2 configuration values as flag
*
* @param $field
* @param null $storeId
* @return mixed
*/
public function getAdyenCcThreeDS2ConfigDataFlag($field, $storeId = null)
{
return $this->getConfigData($field, 'adyen_cc_threeds2', $storeId, true);
}
/**
* Gives back adyen_hpp configuration values
*
......@@ -1353,14 +1348,14 @@ class Data extends AbstractHelper
}
/**
* Return the Terminal ID for the current store/mode
* Return the Store ID for the current store/mode
*
* @param int|null $storeId
* @return mixed
*/
public function getPoiId($storeId = null)
public function getPosStoreId($storeId = null)
{
return $this->getAdyenPosCloudConfigData('pos_terminal_id', $storeId);
return $this->getAdyenPosCloudConfigData('pos_store_id', $storeId);
}
/**
......@@ -1481,9 +1476,17 @@ class Data extends AbstractHelper
* @return string
*/
public function getOrigin() {
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$state = $objectManager->get('Magento\Framework\App\State');
$baseUrl = $this->storeManager->getStore()->getBaseUrl(\Magento\Framework\UrlInterface::URL_TYPE_WEB);
if ('adminhtml' === $state->getAreaCode()) {
$baseUrl = $this->helperBackend->getHomePageUrl();
}
$parsed = parse_url($baseUrl);
$origin = $parsed['scheme'] . "://" . $parsed['host'];
if (!empty($parsed['port'])) {
$origin .= ":" . $parsed['port'];
}
return $origin;
}
......@@ -1546,13 +1549,13 @@ class Data extends AbstractHelper
* @param int|null $storeId
* @return string
*/
public function getCheckoutContextUrl($storeId = null)
public function getCheckoutEnvironment($storeId = null)
{
if ($this->isDemoMode($storeId)) {
return self::CHECKOUT_CONTEXT_URL_TEST;
return self::TEST;
}
return self::CHECKOUT_CONTEXT_URL_LIVE;
return self::LIVE;
}
/**
......@@ -1724,7 +1727,7 @@ class Data extends AbstractHelper
*/
public function isCreditCardThreeDS2Enabled($storeId = null)
{
return $this->getAdyenCcThreeDS2ConfigDataFlag('active', $storeId);
return $this->getAdyenCcConfigDataFlag('threeds2_enabled', $storeId);
}
/**
......
......@@ -390,4 +390,41 @@ class PaymentMethods extends AbstractHelper
{
return $this->getQuote()->getCustomerId();
}
/**
* @return array|mixed
* @throws \Adyen\AdyenException
*/
public function getConnectedTerminals()
{
$storeId = $this->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 empty result
return [];
}
return $responseData;
}
}
\ No newline at end of file
......@@ -450,7 +450,7 @@ class Requests extends AbstractHelper
// Parse address into street and house number where possible
$address = $this->adyenHelper->getStreetFromString($address->getStreetFull());
} else {
$address = $this->adyenHelper->getStreetFromString($address->getStreetLine1());
$address = $this->adyenHelper->getStreetFromString(implode(' ', [$address->getStreetLine1(), $address->getStreetLine2()]));
}
return $address;
......
......@@ -93,8 +93,22 @@ class AdyenInitiateTerminalApi implements AdyenInitiateTerminalApiInterface
* @return mixed
* @throws \Exception
*/
public function initiate()
public function initiate($payload)
{
// Decode payload from frontend
$payload = json_decode($payload, true);
// 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(__('Terminal API initiate request was not a valid JSON'));
}
if (empty($payload['terminal_id'])) {
throw new \Adyen\AdyenException("Terminal ID is empty in initiate request");
}
$poiId = $payload['terminal_id'];
$quote = $this->checkoutSession->getQuote();
$payment = $quote->getPayment();
$payment->setMethod(AdyenPosCloudConfigProvider::CODE);
......@@ -102,7 +116,7 @@ class AdyenInitiateTerminalApi implements AdyenInitiateTerminalApiInterface
$service = $this->adyenHelper->createAdyenPosPaymentService($this->client);
$transactionType = \Adyen\TransactionType::NORMAL;
$poiId = $this->adyenHelper->getPoiId($this->storeId);
$serviceID = date("dHis");
$initiateDate = date("U");
$timeStamper = date("Y-m-d") . "T" . date("H:i:s+00:00");
......
......@@ -56,7 +56,7 @@ class CcType extends \Magento\Payment\Model\Source\Cctype
*/
public function getAllowedTypes()
{
return ['VI', 'MC', 'AE', 'DI', 'JCB', 'UN', 'MI', 'DN', 'BCMC', 'HIPERCARD','ELO', 'TROY'];
return ['VI', 'MC', 'AE', 'DI', 'JCB', 'UN', 'MI', 'DN', 'BCMC', 'HIPERCARD','ELO', 'TROY', 'DANKORT'];
}
/**
......
......@@ -1495,6 +1495,9 @@ class Cron
case 'laser':
case 'paypal':
case 'sepadirectdebit':
case 'dankort':
case 'elo':
case 'hipercard':
$manualCaptureAllowed = true;
break;
default:
......
......@@ -146,7 +146,7 @@ class AdyenCcConfigProvider implements ConfigProviderInterface
$config['payment']['adyenCc']['icons'] = $this->getIcons();
$config['payment']['adyenCc']['originKey'] = $this->_adyenHelper->getOriginKeyForBaseUrl();
$config['payment']['adyenCc']['checkoutUrl'] = $this->_adyenHelper->getCheckoutContextUrl($this->storeManager->getStore()->getId());
$config['payment']['adyenCc']['checkoutEnvironment'] = $this->_adyenHelper->getCheckoutEnvironment($this->storeManager->getStore()->getId());
// has installments by default false
$config['payment']['adyenCc']['hasInstallments'] = false;
......
......@@ -143,7 +143,7 @@ class AdyenOneclickConfigProvider implements ConfigProviderInterface
$config['payment']['adyenOneclick']['methodCode'] = self::CODE;
$config['payment']['adyenOneclick']['originKey'] = $this->_adyenHelper->getOriginKeyForBaseUrl();
$config['payment']['adyenOneclick']['checkoutUrl'] = $this->_adyenHelper->getCheckoutContextUrl($this->_storeManager->getStore()->getId());
$config['payment']['adyenOneclick']['checkoutEnvironment'] = $this->_adyenHelper->getCheckoutEnvironment($this->_storeManager->getStore()->getId());
$config['payment']['adyenOneclick']['locale'] = $this->_adyenHelper->getStoreLocale($this->_storeManager->getStore()->getId());
$enableOneclick = $this->_adyenHelper->getAdyenAbstractConfigData('enable_oneclick');
......
......@@ -43,6 +43,11 @@ class AdyenPosCloudConfigProvider implements ConfigProviderInterface
*/
protected $urlBuilder;
/**
* @var \Adyen\Payment\Helper\PaymentMethods
*/
protected $paymentMethodsHelper;
/**
* AdyenHppConfigProvider constructor.
*
......@@ -51,10 +56,12 @@ class AdyenPosCloudConfigProvider implements ConfigProviderInterface
*/
public function __construct(
\Magento\Framework\App\RequestInterface $request,
\Magento\Framework\UrlInterface $urlBuilder
\Magento\Framework\UrlInterface $urlBuilder,
\Adyen\Payment\Helper\PaymentMethods $paymentMethodsHelper
) {
$this->request = $request;
$this->urlBuilder = $urlBuilder;
$this->paymentMethodsHelper = $paymentMethodsHelper;
}
/**
......@@ -77,6 +84,8 @@ class AdyenPosCloudConfigProvider implements ConfigProviderInterface
]
];
$config['payment']['adyenPos']['connectedTerminals'] = $this->getConnectedTerminals();
return $config;
}
......@@ -89,4 +98,19 @@ class AdyenPosCloudConfigProvider implements ConfigProviderInterface
{
return $this->request;
}
/**
* @return array|mixed
* @throws \Adyen\AdyenException
*/
protected function getConnectedTerminals()
{
$connectedTerminals = $this->paymentMethodsHelper->getConnectedTerminals();
if (!empty($connectedTerminals['uniqueTerminalIds'])) {
return $connectedTerminals['uniqueTerminalIds'];
}
return [];
}
}
<?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\Observer;
use Magento\Framework\Event\Observer;
use Magento\Payment\Observer\AbstractDataAssignObserver;
use Magento\Quote\Api\Data\PaymentInterface;
/**
* Class DataAssignObserver
*/
class AdyenPosCloudDataAssignObserver extends AbstractDataAssignObserver
{
const TERMINAL_ID = 'terminal_id';
/**
* @var array
*/
protected $additionalInformationList = [
self::TERMINAL_ID
];
/**
* @param Observer $observer
* @return void
*/
public function execute(Observer $observer)
{
$data = $this->readDataArgument($observer);
$additionalData = $data->getData(PaymentInterface::KEY_ADDITIONAL_DATA);
if (!is_array($additionalData)) {
return;
}
$paymentInfo = $this->readPaymentModelArgument($observer);
foreach ($this->additionalInformationList as $additionalInformationKey) {
if (!empty($additionalData[$additionalInformationKey])) {
$paymentInfo->setAdditionalInformation(
$additionalInformationKey,
$additionalData[$additionalInformationKey]
);
}
}
}
}
......@@ -37,7 +37,7 @@ If you need to setup your cronjob in Magento <a href="http://devdocs.magento.com
We have defined this:
```
<group id="index">
<group id="adyen_payment">
<job name="adyen_payment_process_notification" instance="Adyen\Payment\Model\Cron" method="processNotification">
<schedule>*/1 * * * *</schedule>
</job>
......
......@@ -2,7 +2,7 @@
"name": "adyen/module-payment",
"description": "Official Magento2 Plugin to connect to Payment Service Provider Adyen.",
"type": "magento2-module",
"version": "4.2.1",
"version": "4.3.0",
"license": [
"OSL-3.0",
"AFL-3.0"
......@@ -14,7 +14,7 @@
}
],
"require": {
"adyen/php-api-library": ">=2.0.0",
"adyen/php-api-library": "~2.1",
"magento/framework": ">=101.0.8 <102 || >=102.0.1",
"magento/module-vault": "101.*"
},
......
......@@ -63,7 +63,7 @@
showInStore="1">
<label>3DS2.0 Enabled</label>
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
<config_path>payment/adyen_cc_threeds2/active</config_path>
<config_path>payment/adyen_cc/threeds2_enabled</config_path>
</field>
<group id="adyen_cc_advanced_settings" translate="label" showInDefault="1" showInWebsite="1" showInStore="1"
......
......@@ -41,24 +41,25 @@
<frontend_class>validate-number</frontend_class>
<config_path>payment/adyen_pos_cloud/sort_order</config_path>
</field>
<field id="pos_terminal_id" translate="label" type="text" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="0">
<label>Unique Terminal ID for Cloud API</label>
<tooltip>Copy this from the Adyen Customer Area => Point of sale => Terminal Fleet Manager => Unique Terminal Id </tooltip>
<config_path>payment/adyen_pos_cloud/pos_terminal_id</config_path>
</field>
<field id="pos_merchant_account" translate="label" type="text" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="0">
<label>Merchant Account for Cloud API</label>
<can_be_empty>1</can_be_empty>
<tooltip>Please insert your Merchant Account name used by the Cloud API. Please leave it blank if the Merchant Account is the same as the one configured in the Required Settings</tooltip>
<config_path>payment/adyen_pos_cloud/pos_merchant_account</config_path>
</field>
<field id="api_key_test" translate="label" type="obscure" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="0">
<field id="pos_store_id" translate="label" type="text" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Store ID for Cloud API</label>
<can_be_empty>1</can_be_empty>
<tooltip>Please insert your store ID used by the Cloud API. Please leave it blank if you want to retrieve all the terminals connected to your merchant account</tooltip>
<config_path>payment/adyen_pos_cloud/pos_store_id</config_path>
</field>
<field id="api_key_test" translate="label" type="obscure" sortOrder="60" showInDefault="1" showInWebsite="1" showInStore="0">
<label>API key for Cloud API TEST</label>
<tooltip>Copy this from the Test Adyen Customer Area => Settings => Users => System => [web service user]=> Checkout API Key.</tooltip>
<backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model>
<config_path>payment/adyen_pos_cloud/api_key_test</config_path>
</field>
<field id="api_key_live" translate="label" type="obscure" sortOrder="60" showInDefault="1" showInWebsite="1" showInStore="0">
<field id="api_key_live" translate="label" type="obscure" sortOrder="70" showInDefault="1" showInWebsite="1" showInStore="0">
<label>API key for Cloud API LIVE</label>
<tooltip>Copy this from the Live Adyen Customer Area => Settings => Users => System => [web service user]=> Checkout API Key.</tooltip>
<backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model>
......
......@@ -73,9 +73,13 @@
<label>Elo</label>
<code_alt>elo</code_alt>
</type>
<type id="TROY" order="130">
<type id="TROY" order="120">
<label>Troy</label>
<code_alt>troy</code_alt>
</type>
<type id="DANKORT" order="130">
<label>Dankort</label>
<code_alt>dankort</code_alt>
</type>
</adyen_credit_cards>
</payment>
......@@ -62,6 +62,7 @@
<can_cancel>1</can_cancel>
<can_authorize_vault>1</can_authorize_vault>
<can_capture_vault>1</can_capture_vault>
<threeds2_enabled>1</threeds2_enabled>
<group>adyen</group>
</adyen_cc>
<adyen_cc_vault>
......
......@@ -32,6 +32,9 @@
<event name="payment_method_assign_data_adyen_hpp">
<observer name="adyen_hpp_gateway_data_assign" instance="Adyen\Payment\Observer\AdyenHppDataAssignObserver" />
</event>
<event name="payment_method_assign_data_adyen_pos_cloud">
<observer name="adyen_pos_cloud_gateway_data_assign" instance="Adyen\Payment\Observer\AdyenPosCloudDataAssignObserver" />
</event>
<event name="payment_method_assign_data_adyen_boleto">
<observer name="adyen_boleto_gateway_data_assign" instance="Adyen\Payment\Observer\AdyenBoletoDataAssignObserver" />
</event>
......
......@@ -24,7 +24,7 @@
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Adyen_Payment" setup_version="4.2.1">
<module name="Adyen_Payment" setup_version="4.3.0">
<sequence>
<module name="Magento_Sales"/>
<module name="Magento_Quote"/>
......
......@@ -113,7 +113,7 @@ echo $code; ?>" style="display:none">
var card = checkout.create('card', {
originKey: "<?php echo $block->getCheckoutOriginKeys(); ?>",
loadingContext: "<?php echo $block->getCheckoutContextUrl(); ?>",
environment: "<?php echo $block->getCheckoutEnvironment(); ?>",
type: 'card',
groupTypes: ccTypes,
hideCVC: hideCVC,
......@@ -122,10 +122,14 @@ echo $code; ?>" style="display:none">
// When the state is valid update the input fields
if (state.isValid) {
// Here we enable the button if the component is now valid
jQuery("#<?php /* @noEscape */ echo $code; ?>-number").val(state.data.encryptedCardNumber);
jQuery("#<?php /* @noEscape */ echo $code; ?>-expiryMonth").val(state.data.encryptedExpiryMonth);
jQuery("#<?php /* @noEscape */ echo $code; ?>-expiryYear").val(state.data.encryptedExpiryYear);
jQuery("#<?php /* @noEscape */ echo $code; ?>-cvc").val(state.data.encryptedSecurityCode);
jQuery("#<?php /* @noEscape */ echo $code; ?>-number").val(state.data.paymentMethod.encryptedCardNumber);
jQuery("#<?php /* @noEscape */ echo $code; ?>-expiryMonth").val(state.data.paymentMethod.encryptedExpiryMonth);
jQuery("#<?php /* @noEscape */ echo $code; ?>-expiryYear").val(state.data.paymentMethod.encryptedExpiryYear);
jQuery("#<?php /* @noEscape */ echo $code; ?>-cvc").val(state.data.paymentMethod.encryptedSecurityCode);
}
},
onBrand: function (state) {
if (state.isValid) {
jQuery("#<?php /* @noEscape */ echo $code; ?>-cc_type").val(getCcCodeByAltCode(state.brand));
}
},
......
This diff is collapsed.
......@@ -7,8 +7,11 @@ 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'
],
function (_, quote, methodList) {
function (_, quote, methodList, customer, urlBuilder, storage) {
'use strict';
return {
......@@ -25,6 +28,43 @@ define(
*/
getAvailablePaymentMethods: function () {
return methodList();
},
/**
* Retrieve the list of available payment methods from the server
*/
retrieveAvailablePaymentMethods: function (callback = null) {
var self = this;
// retrieve payment methods
var serviceUrl,
payload;
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()
});
}
payload = {
cartId: quote.getQuoteId(),
shippingAddress: quote.shippingAddress()
};
storage.post(
serviceUrl, JSON.stringify(payload)
).done(
function (response) {
self.setPaymentMethods(response);
if (callback !== null) {
callback();
}
}
).fail(
function (response) {
self.setPaymentMethods([]);
}
)
}
};
}
......
......@@ -84,7 +84,6 @@ define(
'expiryYear',
'installment',
'creditCardDetailsValid',
'variant',
'placeOrderAllowed'
]);
......@@ -119,7 +118,7 @@ define(
self.cardComponent = self.checkout.create('card', {
originKey: self.getOriginKey(),
loadingContext: self.getLoadingContext(),
environment: self.getCheckoutEnvironment(),
type: 'card',
hasHolderName: true,
holderNameRequired: true,
......@@ -128,13 +127,12 @@ define(
onChange: function (state, component) {
if (!!state.isValid && !component.state.errors.encryptedSecurityCode) {
self.storeCc = !!state.data.storeDetails;
self.variant(state.brand);
self.creditCardNumber(state.data.encryptedCardNumber);
self.expiryMonth(state.data.encryptedExpiryMonth);
self.expiryYear(state.data.encryptedExpiryYear);
self.securityCode(state.data.encryptedSecurityCode);
self.creditCardOwner(state.data.holderName);
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 {
......@@ -248,28 +246,44 @@ define(
self.threeDS2ChallengeComponent = self.checkout
.create('threeDS2Challenge', {
challengeToken: token,
size: '05',
onComplete: function (result) {
popupModal.modal("closeModal");
self.closeModal(popupModal);
fullScreenLoader.startLoader();
threeds2.processThreeDS2(result.data).done(function (responseJSON) {
self.validateThreeDS2OrPlaceOrder(responseJSON);
}).error(function () {
popupModal.modal("closeModal");
self.isPlaceOrderActionAllowed(true);
fullScreenLoader.stopLoader();
});
},
onError: function (error) {
self.closeModal(popupModal);
console.log(JSON.stringify(error));
}
});
self.threeDS2ChallengeComponent.mount(threeDS2Node);
}
},
/**
* 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();
$('.modals-overlay').remove();
// reconstruct the threeDS2Modal container again otherwise component can not find the threeDS2Modal
$('#threeDS2Wrapper').append("<div id=\"threeDS2Modal\">" +
"<div id=\"threeDS2Container\"></div>" +
"</div>");
},
/**
* Builds the payment details part of the payment information reqeust
*
* @returns {{method: *, additional_data: {card_brand: *, cc_type: *, number: *, cvc: *, expiryMonth: *, expiryYear: *, holderName: *, store_cc: (boolean|*), number_of_installments: *, java_enabled: boolean, screen_color_depth: number, screen_width, screen_height, timezone_offset: *}}}
* @returns {{method: *, additional_data: {cc_type: *, number: *, cvc: *, expiryMonth: *, expiryYear: *, holderName: *, store_cc: (boolean|*), number_of_installments: *, java_enabled: () => boolean, screen_color_depth: number, screen_width, screen_height, timezone_offset: *, language: *}}}
*/
getCcData: function () {
const browserInfo = threeDS2Utils.getBrowserInfo();
......@@ -277,7 +291,6 @@ define(
var data = {
'method': this.item.method,
additional_data: {
'card_brand': this.variant(),
'cc_type': this.creditCardType(),
'number': this.creditCardNumber(),
'cvc': this.securityCode(),
......@@ -306,7 +319,6 @@ define(
return {
'method': this.item.method,
additional_data: {
'card_brand': this.variant(),
'cc_type': this.creditCardType(),
'store_cc': this.storeCc,
'number_of_installments': this.installment()
......@@ -373,7 +385,9 @@ define(
if (self.redirectAfterPlaceOrder) {
// use custom redirect Link for supporting 3D secure
window.location.replace(url.build(window.checkoutConfig.payment[quote.paymentMethod().method].redirectUrl));
window.location.replace(url.build(
window.checkoutConfig.payment[quote.paymentMethod().method].redirectUrl)
);
}
}
);
......@@ -454,8 +468,8 @@ define(
getOriginKey: function () {
return window.checkoutConfig.payment.adyenCc.originKey;
},
getLoadingContext: function () {
return window.checkoutConfig.payment.adyenCc.checkoutUrl;
getCheckoutEnvironment: function () {
return window.checkoutConfig.payment.adyenCc.checkoutEnvironment;
},
getLocale: function () {
return window.checkoutConfig.payment.adyenCc.locale;
......
......@@ -42,6 +42,7 @@ define(
var brandCode = ko.observable(null);
var paymentMethod = ko.observable(null);
var messageComponents;
var shippingAddressCountryCode = quote.shippingAddress().countryId;
/**
* Shareble adyen checkout component
* @type {AdyenCheckout}
......@@ -90,28 +91,9 @@ define(
// reset variable:
adyenPaymentService.setPaymentMethods();
// retrieve payment methods
var serviceUrl,
payload;
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()
});
}
payload = {
cartId: quote.getQuoteId(),
shippingAddress: quote.shippingAddress()
};
storage.post(
serviceUrl, JSON.stringify(payload)
).done(
function (response) {
adyenPaymentService.setPaymentMethods(response);
if (JSON.stringify(response).indexOf("ratepay") > -1) {
adyenPaymentService.retrieveAvailablePaymentMethods(function() {
let paymentMethods = adyenPaymentService.getAvailablePaymentMethods();
if (JSON.stringify(paymentMethods).indexOf("ratepay") > -1) {
var ratePayId = window.checkoutConfig.payment.adyenHpp.ratePayId;
var dfValueRatePay = self.getRatePayDeviceIdentToken();
......@@ -130,7 +112,7 @@ define(
// create component needs to be in initialize method
var messageComponents = {};
_.map(response, function (value) {
_.map(paymentMethods, function (value) {
var messageContainer = new Messages();
var name = 'messages-' + self.getBrandCodeFromPaymentMethod(value);
......@@ -149,14 +131,21 @@ define(
});
self.messageComponents = messageComponents;
fullScreenLoader.stopLoader();
}
).fail(function (error) {
fullScreenLoader.stopLoader();
});
},
getAdyenHppPaymentMethods: function () {
var self = this;
let currentShippingAddressCountryCode = quote.shippingAddress().countryId;
// retrieve new payment methods if country code changed
if (shippingAddressCountryCode != currentShippingAddressCountryCode) {
fullScreenLoader.startLoader();
adyenPaymentService.retrieveAvailablePaymentMethods();
shippingAddressCountryCode = currentShippingAddressCountryCode;
fullScreenLoader.stopLoader();
}
var paymentMethods = adyenPaymentService.getAvailablePaymentMethods();
var paymentList = _.map(paymentMethods, function (value) {
......@@ -289,16 +278,28 @@ define(
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 (
typeof value.details !== 'undefined' &&
typeof value.details[0].items !== 'undefined' &&
value.details[0].key == 'issuer'
) {
if (result.findIssuersProperty() !== false) {
return true;
}
......@@ -309,7 +310,7 @@ define(
* @returns {boolean}
*/
result.hasIssuersAvailable = function () {
if (result.hasIssuersProperty() && value.details[0].items.length > 0) {
if (result.hasIssuersProperty() && value.details[result.findIssuersProperty()].items.length > 0) {
return true;
}
......@@ -321,7 +322,7 @@ define(
*/
result.getIssuers = function() {
if (result.hasIssuersAvailable()) {
return value.details[0].items;
return value.details[result.findIssuersProperty()].items;
}
return [];
......@@ -372,7 +373,7 @@ define(
items: result.getIssuers(),
onChange: function (state) {
if (!!state.isValid) {
result.issuer(state.data.issuer);
result.issuer(state.data.paymentMethod.issuer);
result.isPlaceOrderAllowed(true);
} else {
......@@ -397,8 +398,8 @@ define(
countryCode: self.getLocale(),
onChange: function (state) {
if (!!state.isValid) {
result.ownerName(state.data["sepa.ownerName"]);
result.ibanNumber(state.data["sepa.ibanNumber"]);
result.ownerName(state.data.paymentMethod["sepa.ownerName"]);
result.ibanNumber(state.data.paymentMethod["sepa.ibanNumber"]);
result.isPlaceOrderAllowed(true);
} else {
result.isPlaceOrderAllowed(false);
......@@ -430,9 +431,9 @@ define(
},
onChange: function (state) {
if (!!state.isValid) {
result.dob(state.data.personalDetails.dateOfBirth);
result.telephone(state.data.personalDetails.telephoneNumber);
result.gender(state.data.personalDetails.gender);
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);
......@@ -455,9 +456,9 @@ define(
},
onChange: function (state) {
if (!!state.isValid) {
result.dob(state.data.personalDetails.dateOfBirth);
result.telephone(state.data.personalDetails.telephoneNumber);
result.gender(state.data.personalDetails.gender);
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);
......
......@@ -110,7 +110,7 @@ define(
var checkout = new AdyenCheckout({
locale: self.getLocale(),
originKey: self.getOriginKey(),
loadingContext: self.getLoadingContext(),
environment: self.getCheckoutEnvironment(),
risk: {
enabled: false
}
......@@ -256,9 +256,10 @@ define(
isValid(true);
if (typeof state.data !== 'undefined' &&
typeof state.data.encryptedSecurityCode !== 'undefined'
typeof state.data.paymentMethod !== 'undefined' &&
typeof state.data.paymentMethod.encryptedSecurityCode !== 'undefined'
) {
self.encryptedCreditCardVerificationNumber = state.data.encryptedSecurityCode;
self.encryptedCreditCardVerificationNumber = state.data.paymentMethod.encryptedSecurityCode;
}
} else {
self.encryptedCreditCardVerificationNumber = '';
......@@ -268,14 +269,6 @@ define(
isValid(false);
}
}
// When we move to the component v2.2 it should be removed
if (self.agreement_data.variant == "maestro" &&
component.state.errors.encryptedSecurityCode
) {
self.placeOrderAllowed(false);
isValid(false);
}
}
})
.mount(oneClickCardNode);
......@@ -566,8 +559,8 @@ define(
getOriginKey: function () {
return window.checkoutConfig.payment.adyenOneclick.originKey;
},
getLoadingContext: function () {
return window.checkoutConfig.payment.adyenOneclick.checkoutUrl;
getCheckoutEnvironment: function () {
return window.checkoutConfig.payment.adyenOneclick.checkoutEnvironment;
}
});
}
......
......@@ -57,12 +57,26 @@ define(
agreementsAssigner(paymentData);
serviceUrl = urlBuilder.createUrl('/adyen/initiate', {});
fullScreenLoader.startLoader();
let payload = {
"payload": JSON.stringify({terminal_id: self.terminalId()})
}
return storage.post(
serviceUrl
serviceUrl,
JSON.stringify(payload)
).always(function(){
self.placeOrderPos()});
return false;
},
initObservable: function () {
this._super()
.observe([
'terminalId'
]);
return this;
},
posComplete: function () {
this.afterPlaceOrder();
if (this.redirectAfterPlaceOrder) {
......@@ -91,6 +105,33 @@ define(
}
)
},
getConnectedTerminals: function() {
let connectedTerminals = [];
const connectedTerminalsList = window.checkoutConfig.payment.adyenPos.connectedTerminals;
for (let i = 0; i < connectedTerminalsList.length; i++) {
connectedTerminals.push(
{
key: connectedTerminalsList[i],
value: connectedTerminalsList[i]
}
);
}
return connectedTerminals;
},
/**
* Get data for place order
* @returns {{method: *}}
*/
getData: function () {
return {
'method': this.item.method,
additional_data: {
'terminal_id': this.terminalId()
}
};
},
showLogo: function () {
return window.checkoutConfig.payment.adyen.showLogo;
},
......
......@@ -77,9 +77,12 @@
<div afterRender="renderSecureFields()" data-bind="attr: { id: 'cardContainer'}"></div>
</div>
<div id="threeDS2Wrapper">
<div id="threeDS2Modal">
<div id="threeDS2Container"></div>
</div>
</div>
<!-- ko if: (hasInstallments())-->
......
......@@ -50,6 +50,25 @@
<!--/ko-->
</div>
<div class="field required"
data-bind="attr: {id: getCode() + '_connected_terminals_div'}, visible: getConnectedTerminals().length > 0">
<label data-bind="attr: {for: getCode() + '_connected_terminals'}" class="label">
<span><!-- ko text: $t('Connected terminals')--><!-- /ko --></span>
</label>
<div class="control">
<select class="select"
name="paymentMethod[connected_terminals]"
data-bind="attr: {id: getCode() + '_connected_terminals', 'data-container': getCode() + '-connected-terminals', 'data-validate': JSON.stringify({required:true})},
options: getConnectedTerminals(),
optionsValue: 'value',
optionsText: 'key',
value: terminalId"
>
</select>
</div>
</div>
<div class="checkout-agreements-block">
<!-- ko foreach: $parent.getRegion('before-place-order') -->
<!-- ko template: getTemplate() --><!-- /ko -->
......@@ -62,8 +81,7 @@
data-bind="
click: initiate,
attr: {title: $t('Place Order')},
enable: (getCode() == isChecked()),
css: {disabled: !isPlaceOrderActionAllowed()}
enable: getConnectedTerminals().length > 0
"
disabled>
<span data-bind="text: $t('Place Order')"></span>
......@@ -72,4 +90,3 @@
</div>
</div>
</div>
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