Skip to content
Snippets Groups Projects
Commit fc7ad0d7 authored by Volodymyr Zaets's avatar Volodymyr Zaets Committed by GitHub
Browse files

Merge pull request #507 from magento-mpi/MPI-PR-PayPal

Bug
- MAGETWO-59086 [GITHUB] Credit Card capture not associated with the Authorization since upgrade to 2.1.1 #6716
Story
- MAGETWO-58251 PayPal Best Practice to Separate Saved Tokens
- MAGETWO-59073 PayPal Team Comments After Demo: PayPal Vault Shown on Orders in Backend
parents 3926be43 3fbe2b5b
Branches
No related merge requests found
Showing
with 455 additions and 57 deletions
<?php
/**
* Copyright © 2016 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\Braintree\Model\Ui\Adminhtml\PayPal;
use Magento\Braintree\Gateway\Config\PayPal\Config;
use Magento\Braintree\Model\Ui\ConfigProvider;
use Magento\Braintree\Model\Ui\PayPal\ConfigProvider as PayPalConfigProvider;
use Magento\Framework\UrlInterface;
use Magento\Framework\View\Element\Template;
use Magento\Vault\Api\Data\PaymentTokenInterface;
use Magento\Vault\Model\Ui\TokenUiComponentInterfaceFactory;
use Magento\Vault\Model\Ui\TokenUiComponentProviderInterface;
/**
* Gets Ui component configuration for Braintree PayPal Vault
*/
class TokenUiComponentProvider implements TokenUiComponentProviderInterface
{
/**
* @var TokenUiComponentInterfaceFactory
*/
private $componentFactory;
/**
* @var UrlInterface
*/
private $urlBuilder;
/**
* @var Config
*/
private $config;
/**
* @param TokenUiComponentInterfaceFactory $componentFactory
* @param UrlInterface $urlBuilder
* @param Config $config
*/
public function __construct(
TokenUiComponentInterfaceFactory $componentFactory,
UrlInterface $urlBuilder,
Config $config
) {
$this->componentFactory = $componentFactory;
$this->urlBuilder = $urlBuilder;
$this->config = $config;
}
/**
* @inheritdoc
*/
public function getComponentForToken(PaymentTokenInterface $paymentToken)
{
$data = json_decode($paymentToken->getTokenDetails() ?: '{}', true);
$data['icon'] = $this->config->getPayPalIcon();
$component = $this->componentFactory->create(
[
'config' => [
'code' => PayPalConfigProvider::PAYPAL_VAULT_CODE,
'nonceUrl' => $this->getNonceRetrieveUrl(),
TokenUiComponentProviderInterface::COMPONENT_DETAILS => $data,
TokenUiComponentProviderInterface::COMPONENT_PUBLIC_HASH => $paymentToken->getPublicHash(),
'template' => 'Magento_Braintree::form/paypal/vault.phtml'
],
'name' => Template::class
]
);
return $component;
}
/**
* Get url to retrieve payment method nonce
* @return string
*/
private function getNonceRetrieveUrl()
{
return $this->urlBuilder->getUrl(ConfigProvider::CODE . '/payment/getnonce', ['_secure' => true]);
}
}
......@@ -49,6 +49,7 @@ class TokenUiComponentProvider implements TokenUiComponentProviderInterface
$component = $this->componentFactory->create(
[
'config' => [
'code' => ConfigProvider::CC_VAULT_CODE,
'nonceUrl' => $this->getNonceRetrieveUrl(),
TokenUiComponentProviderInterface::COMPONENT_DETAILS => $data,
TokenUiComponentProviderInterface::COMPONENT_PUBLIC_HASH => $paymentToken->getPublicHash(),
......
<?php
/**
* Copyright © 2016 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\Braintree\Test\Unit\Model\Ui\Adminhtml\PayPal;
use Magento\Braintree\Gateway\Config\PayPal\Config;
use Magento\Braintree\Model\Ui\Adminhtml\PayPal\TokenUiComponentProvider;
use Magento\Framework\UrlInterface;
use Magento\Vault\Api\Data\PaymentTokenInterface;
use Magento\Vault\Model\Ui\TokenUiComponentInterface;
use Magento\Vault\Model\Ui\TokenUiComponentInterfaceFactory;
use PHPUnit_Framework_MockObject_MockObject as MockObject;
/**
* Contains methods to test PayPal token Ui component provider
*/
class TokenUiComponentProviderTest extends \PHPUnit_Framework_TestCase
{
/**
* @var TokenUiComponentInterfaceFactory|MockObject
*/
private $componentFactory;
/**
* @var UrlInterface|MockObject
*/
private $urlBuilder;
/**
* @var Config|MockObject
*/
private $config;
/**
* @var TokenUiComponentProvider
*/
private $tokenUiComponentProvider;
protected function setUp()
{
$this->componentFactory = $this->getMockBuilder(TokenUiComponentInterfaceFactory::class)
->disableOriginalConstructor()
->setMethods(['create'])
->getMock();
$this->urlBuilder = $this->getMock(UrlInterface::class);
$this->config = $this->getMockBuilder(Config::class)
->disableOriginalConstructor()
->setMethods(['getPayPalIcon'])
->getMock();
$this->tokenUiComponentProvider = new TokenUiComponentProvider(
$this->componentFactory,
$this->urlBuilder,
$this->config
);
}
/**
* @covers \Magento\Braintree\Model\Ui\Adminhtml\PayPal\TokenUiComponentProvider::getComponentForToken
*/
public function testGetComponentForToken()
{
$nonceUrl = 'https://payment/adminhtml/nonce/url';
$payerEmail = 'john.doe@test.com';
$icon = [
'url' => 'https://payment/adminhtml/icon.png',
'width' => 48,
'height' => 32
];
$expected = [
'code' => 'vault',
'nonceUrl' => $nonceUrl,
'details' => [
'payerEmail' => $payerEmail,
'icon' => $icon
],
'template' => 'vault.phtml'
];
$this->config->expects(static::once())
->method('getPayPalIcon')
->willReturn($icon);
$paymentToken = $this->getMock(PaymentTokenInterface::class);
$paymentToken->expects(static::once())
->method('getTokenDetails')
->willReturn('{"payerEmail":" ' . $payerEmail . '"}');
$paymentToken->expects(static::once())
->method('getPublicHash')
->willReturn('cmk32dl21l');
$this->urlBuilder->expects(static::once())
->method('getUrl')
->willReturn($nonceUrl);
$tokenComponent = $this->getMock(TokenUiComponentInterface::class);
$tokenComponent->expects(static::once())
->method('getConfig')
->willReturn($expected);
$this->componentFactory->expects(static::once())
->method('create')
->willReturn($tokenComponent);
$component = $this->tokenUiComponentProvider->getComponentForToken($paymentToken);
static::assertEquals($tokenComponent, $component);
static::assertEquals($expected, $component->getConfig());
}
}
......@@ -7,10 +7,10 @@ namespace Magento\Braintree\Test\Unit\Model\Ui\Adminhtml;
use Magento\Braintree\Model\Ui\Adminhtml\TokenUiComponentProvider;
use Magento\Framework\UrlInterface;
use Magento\Framework\View\Element\Template;
use Magento\Vault\Api\Data\PaymentTokenInterface;
use Magento\Vault\Model\Ui\TokenUiComponentInterface;
use Magento\Vault\Model\Ui\TokenUiComponentInterfaceFactory;
use PHPUnit_Framework_MockObject_MockObject as MockObject;
/**
* Class TokenUiComponentProviderTest
......@@ -19,12 +19,12 @@ class TokenUiComponentProviderTest extends \PHPUnit_Framework_TestCase
{
/**
* @var TokenUiComponentInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject
* @var TokenUiComponentInterfaceFactory|MockObject
*/
private $componentFactory;
/**
* @var UrlInterface|\PHPUnit_Framework_MockObject_MockObject
* @var UrlInterface|MockObject
*/
private $urlBuilder;
......@@ -59,6 +59,7 @@ class TokenUiComponentProviderTest extends \PHPUnit_Framework_TestCase
$expirationDate = '12/2015';
$expected = [
'code' => 'vault',
'nonceUrl' => $nonceUrl,
'details' => [
'type' => $type,
......
......@@ -47,6 +47,7 @@
<arguments>
<argument name="tokenUiComponentProviders" xsi:type="array">
<item name="braintree" xsi:type="object">Magento\Braintree\Model\Ui\Adminhtml\TokenUiComponentProvider</item>
<item name="braintree_paypal" xsi:type="object">Magento\Braintree\Model\Ui\Adminhtml\PayPal\TokenUiComponentProvider</item>
</argument>
</arguments>
</type>
......
......@@ -71,7 +71,8 @@
</braintree_cc_vault>
<braintree_paypal_vault>
<model>BraintreePayPalVaultFacade</model>
<title>Vault Token (Braintree PayPal)</title>
<title>Stored Accounts (Braintree PayPal)</title>
<can_use_internal>1</can_use_internal>
</braintree_paypal_vault>
</payment>
</default>
......
......@@ -18,6 +18,10 @@
<argument name="method" xsi:type="string">braintree_cc_vault</argument>
<argument name="template" xsi:type="string">Magento_Vault::form/vault.phtml</argument>
</action>
<action method="setMethodFormTemplate">
<argument name="method" xsi:type="string">braintree_paypal_vault</argument>
<argument name="template" xsi:type="string">Magento_Vault::form/vault.phtml</argument>
</action>
</referenceBlock>
<referenceBlock name="content">
<block name="braintree_payment_script"
......
......@@ -18,6 +18,10 @@
<argument name="method" xsi:type="string">braintree_cc_vault</argument>
<argument name="template" xsi:type="string">Magento_Vault::form/vault.phtml</argument>
</action>
<action method="setMethodFormTemplate">
<argument name="method" xsi:type="string">braintree_paypal_vault</argument>
<argument name="template" xsi:type="string">Magento_Vault::form/vault.phtml</argument>
</action>
</referenceBlock>
</body>
</page>
\ No newline at end of file
<?php
use Magento\Vault\Model\Ui\TokenUiComponentProviderInterface;
/**
* Copyright © 2016 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
// @codingStandardsIgnoreFile
/** @var \Magento\Framework\View\Element\Template $block */
$details = $block->getData(TokenUiComponentProviderInterface::COMPONENT_DETAILS);
$icon = $details['icon'];
$id = $block->escapeHtml($block->getData('id'));
?>
<div data-mage-init='{
"Magento_Braintree/js/vault": {
"container": "payment_<?php /* @noEscape */ echo $id; ?>",
"publicHash": "<?php echo $block->escapeHtml($block->getData(TokenUiComponentProviderInterface::COMPONENT_PUBLIC_HASH)); ?>",
"code": "<?php echo $block->escapeHtml($block->getData('code')); ?>",
"nonceUrl": "<?php echo $block->escapeUrl($block->getData('nonceUrl')); ?>"
}
}' id="payment_<?php /* @noEscape */ echo $id;?>" class="admin__field">
<div class="admin__field-control control">
<input type="radio" id="token_switcher_<?php /* @noEscape */ echo $id; ?>" name="payment[token_switcher]"/>
<img src="<?php echo $block->escapeUrl($icon['url']); ?>"
width="<?php echo $block->escapeHtml($icon['width']); ?>"
height="<?php echo $block->escapeHtml($icon['height']); ?>"
class="payment-icon" >
<span><?php echo $block->escapeHtml($details['payerEmail']); ?></span>
</div>
</div>
......@@ -7,7 +7,7 @@ use Magento\Vault\Model\Ui\TokenUiComponentProviderInterface;
// @codingStandardsIgnoreFile
/** @var \Magento\Framework\View\Element\Template $block */
$details = $block->getData('details');
$details = $block->getData(TokenUiComponentProviderInterface::COMPONENT_DETAILS);
$icon = $block->getData('icons')[$details['type']];
$id = $block->escapeHtml($block->getData('id'));
?>
......@@ -15,6 +15,7 @@ $id = $block->escapeHtml($block->getData('id'));
"Magento_Braintree/js/vault": {
"container": "payment_<?php /* @noEscape */ echo $id; ?>",
"publicHash": "<?php echo $block->escapeHtml($block->getData(TokenUiComponentProviderInterface::COMPONENT_PUBLIC_HASH)); ?>",
"code": "<?php echo $block->escapeHtml($block->getData('code')); ?>",
"nonceUrl": "<?php echo $block->escapeUrl($block->getData('nonceUrl')); ?>"
}
}' id="payment_<?php /* @noEscape */ echo $id;?>" class="admin__field">
......
......@@ -14,7 +14,8 @@ define([
return Class.extend({
defaults: {
$selector: null,
selector: 'edit_form'
selector: 'edit_form',
$container: null
},
/**
......@@ -25,17 +26,18 @@ define([
var self = this;
self.$selector = $('#' + self.selector);
self.$container = $('#' + self.container);
self.$selector.on(
'setVaultNotActive',
function () {
self.$selector.off('submitOrder.braintree_vault');
self.$selector.off('submitOrder.' + self.getCode());
}
);
this._super();
self._super();
this.initEventHandlers();
self.initEventHandlers();
return this;
return self;
},
/**
......@@ -43,14 +45,14 @@ define([
* @returns {String}
*/
getCode: function () {
return 'braintree';
return this.code;
},
/**
* Init event handlers
*/
initEventHandlers: function () {
$('#' + this.container).find('[name="payment[token_switcher]"]')
$(this.$container).find('[name="payment[token_switcher]"]')
.on('click', this.selectPaymentMethod.bind(this));
},
......@@ -66,7 +68,7 @@ define([
* Enable form event listeners
*/
enableEventListeners: function () {
this.$selector.on('submitOrder.braintree_vault', this.submitOrder.bind(this));
this.$selector.on('submitOrder.' + this.getCode(), this.submitOrder.bind(this));
},
/**
......@@ -129,7 +131,7 @@ define([
this.createPublicHashSelector();
this.$selector.find('[name="payment[public_hash]"]').val(this.publicHash);
this.$selector.find('#braintree_nonce').val(nonce);
this.$container.find('#' + this.getNonceSelectorName()).val(nonce);
},
/**
......@@ -138,16 +140,16 @@ define([
createPublicHashSelector: function () {
var $input;
if (this.$selector.find('#braintree_nonce').size() === 0) {
if (this.$container.find('#' + this.getNonceSelectorName()).size() === 0) {
$input = $('<input>').attr(
{
type: 'hidden',
id: 'braintree_nonce',
id: this.getNonceSelectorName(),
name: 'payment[payment_method_nonce]'
}
);
$input.appendTo(this.$selector);
$input.appendTo(this.$container);
$input.prop('disabled', false);
}
},
......@@ -160,6 +162,14 @@ define([
alert({
content: message
});
},
/**
* Get selector name for nonce input
* @returns {String}
*/
getNonceSelectorName: function () {
return 'nonce_' + this.getCode();
}
});
});
/**
* Copyright © 2016 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
define([
'uiElement',
'mage/translate'
], function (Element, $t) {
'use strict';
var DEFAULT_GROUP_ALIAS = 'default';
return Element.extend({
defaults: {
alias: DEFAULT_GROUP_ALIAS,
title: $t('Payment Method'),
sortOrder: 100,
displayArea: 'payment-methods-items-${ $.alias }'
},
/**
* Checks if group instance is default
*
* @returns {Boolean}
*/
isDefault: function () {
return this.alias === DEFAULT_GROUP_ALIAS;
}
});
});
......@@ -10,14 +10,22 @@ define([
'Magento_Checkout/js/model/payment/method-list',
'Magento_Checkout/js/model/payment/renderer-list',
'uiLayout',
'Magento_Checkout/js/model/checkout-data-resolver'
], function (_, ko, utils, Component, paymentMethods, rendererList, layout, checkoutDataResolver) {
'Magento_Checkout/js/model/checkout-data-resolver',
'mage/translate',
'uiRegistry'
], function (_, ko, utils, Component, paymentMethods, rendererList, layout, checkoutDataResolver, $t, registry) {
'use strict';
return Component.extend({
defaults: {
template: 'Magento_Checkout/payment-methods/list',
visible: paymentMethods().length > 0
visible: paymentMethods().length > 0,
configDefaultGroup: {
name: 'methodGroup',
component: 'Magento_Checkout/js/model/payment/method-group'
},
paymentGroupsList: [],
defaultGroupTitle: $t('Select a new payment method')
},
/**
......@@ -26,7 +34,7 @@ define([
* @returns {Component} Chainable.
*/
initialize: function () {
this._super().initChildren();
this._super().initDefaulGroup().initChildren();
paymentMethods.subscribe(
function (changes) {
checkoutDataResolver.resolvePaymentMethod();
......@@ -47,6 +55,27 @@ define([
return this;
},
/** @inheritdoc */
initObservable: function () {
this._super().
observe(['paymentGroupsList']);
return this;
},
/**
* Creates default group
*
* @returns {Component} Chainable.
*/
initDefaulGroup: function() {
layout([
this.configDefaultGroup
]);
return this;
},
/**
* Create renders for child payment methods.
*
......@@ -77,7 +106,7 @@ define([
rendererTemplate = {
parent: '${ $.$data.parentName }',
name: '${ $.$data.name }',
displayArea: 'payment-method-items',
displayArea: payment.displayArea,
component: payment.component
};
rendererComponent = utils.template(rendererTemplate, templateData);
......@@ -95,49 +124,105 @@ define([
* @param {Object} paymentMethodData
*/
createRenderer: function (paymentMethodData) {
var isRendererForMethod = false;
var isRendererForMethod = false,
currentGroup;
registry.get(this.configDefaultGroup.name, function (defaultGroup) {
_.each(rendererList(), function (renderer) {
_.find(rendererList(), function (renderer) {
if (renderer.hasOwnProperty('typeComparatorCallback') &&
typeof renderer.typeComparatorCallback == 'function'
) {
isRendererForMethod = renderer.typeComparatorCallback(renderer.type, paymentMethodData.method);
} else {
isRendererForMethod = renderer.type === paymentMethodData.method;
}
if (renderer.hasOwnProperty('typeComparatorCallback') &&
typeof renderer.typeComparatorCallback == 'function'
) {
isRendererForMethod = renderer.typeComparatorCallback(renderer.type, paymentMethodData.method);
} else {
isRendererForMethod = renderer.type === paymentMethodData.method;
}
if (isRendererForMethod) {
currentGroup = renderer.group ? renderer.group : defaultGroup;
if (isRendererForMethod) {
layout(
[
this.collectPaymentGroups(currentGroup);
layout([
this.createComponent(
{
config: renderer.config,
component: renderer.component,
name: renderer.type,
method: paymentMethodData.method,
item: paymentMethodData
item: paymentMethodData,
displayArea: currentGroup.displayArea
}
)
]
);
}
)]);
}
}.bind(this));
}.bind(this));
},
/**
* Collects unique groups of available payment methods
*
* @param {Object} group
*/
collectPaymentGroups: function (group) {
var groupsList = this.paymentGroupsList(),
isGroupExists = _.some(groupsList, function (existsGroup) {
return existsGroup.alias === group.alias;
});
if (!isGroupExists) {
groupsList.push(group);
groupsList = _.sortBy(groupsList, function (existsGroup) {
return existsGroup.sortOrder;
});
this.paymentGroupsList(groupsList);
}
},
/**
* Returns payment group title
*
* @param {Object} group
* @returns {String}
*/
getGroupTitle: function (group) {
var title = group().title;
if (group().isDefault() && this.paymentGroupsList().length > 1) {
title = this.defaultGroupTitle;
}
return title + ':';
},
/**
* Checks if at least one payment method available
*
* @returns {String}
*/
isPaymentMethodsAvailable: function () {
return _.some(this.paymentGroupsList(), function (group) {
return this.getRegion(group.displayArea)().length;
}, this);
},
/**
* Remove view renderer.
*
* @param {String} paymentMethodCode
*/
removeRenderer: function (paymentMethodCode) {
var items = this.getRegion('payment-method-items');
var items;
_.each(this.paymentGroupsList(), function (group) {
items = this.getRegion(group.displayArea);
_.find(items(), function (value) {
if (value.item.method.indexOf(paymentMethodCode) === 0) {
value.disposeSubscriptions();
value.destroy();
}
_.find(items(), function (value) {
if (value.item.method.indexOf(paymentMethodCode) === 0) {
value.disposeSubscriptions();
value.destroy();
}
});
}, this);
}
});
......
......@@ -4,9 +4,19 @@
* See COPYING.txt for license details.
*/
-->
<div class="items payment-methods">
<!-- ko foreach: { data: getRegion('payment-method-items'), as: 'element'} -->
<!-- ko template: element.getTemplate() --><!-- /ko -->
<!-- /ko -->
<div if="isPaymentMethodsAvailable()"
class="items payment-methods">
<div repeat="foreach: paymentGroupsList, item: '$group'"
class="payment-group">
<div if="getRegion($group().displayArea)().length"
translate="getGroupTitle($group)"
class="step-title"
data-role="title">
</div>
<each args="data: getRegion($group().displayArea), as: 'method'" render=""/>
</div>
</div>
<div ifnot="isPaymentMethodsAvailable()"
class="no-payments-block"
translate="'No Payment Methods'">
</div>
<!-- ko ifnot: getRegion('payment-method-items')().length > 0 --><div class="no-payments-block"><!-- ko i18n: 'No Payment Methods'--><!-- /ko --></div><!-- /ko -->
......@@ -5,7 +5,6 @@
*/
-->
<li id="payment" role="presentation" class="checkout-payment-method" data-bind="fadeVisible: isVisible">
<div class="step-title" data-bind="i18n: title" data-role="title"></div>
<div id="checkout-step-payment"
class="step-content"
data-role="content"
......
......@@ -186,6 +186,7 @@ class Transparent extends Payflowpro implements TransparentInterface
$this->createPaymentToken($payment, $token);
$payment->unsAdditionalInformation(self::CC_DETAILS);
$payment->unsAdditionalInformation(self::PNREF);
return $this;
}
......
......@@ -428,6 +428,13 @@ class TransparentTest extends \PHPUnit_Framework_TestCase
->method('setVaultPaymentToken')
->with($paymentTokenMock);
$this->paymentMock->expects($this->at(8))
->method('unsAdditionalInformation')
->with(Transparent::CC_DETAILS);
$this->paymentMock->expects($this->at(9))
->method('unsAdditionalInformation')
->with(Transparent::PNREF);
$this->assertSame($this->object, $this->object->authorize($this->paymentMock, 33));
}
}
......@@ -268,6 +268,14 @@ define([
_.each(grouped, this.updateRegion, this);
_.each(this.regions, function (items) {
var hasObsoleteComponents = items().length && !_.intersection(_elems, items()).length;
if (hasObsoleteComponents) {
items.removeAll();
}
});
this.elems(_elems);
return this;
......
......@@ -5,17 +5,16 @@
*/
namespace Magento\Vault\Model\Method;
use Magento\Framework\DataObject;
use Magento\Framework\Event\ManagerInterface;
use Magento\Framework\ObjectManagerInterface;
use Magento\Payment\Gateway\Command;
use Magento\Sales\Api\Data\OrderPaymentExtensionInterfaceFactory;
use Magento\Payment\Gateway\Config\ValueHandlerPoolInterface;
use Magento\Payment\Gateway\ConfigFactoryInterface;
use Magento\Payment\Gateway\ConfigInterface;
use Magento\Payment\Model\InfoInterface;
use Magento\Payment\Model\MethodInterface;
use Magento\Payment\Observer\AbstractDataAssignObserver;
use Magento\Sales\Api\Data\OrderPaymentExtensionInterfaceFactory;
use Magento\Sales\Api\Data\OrderPaymentInterface;
use Magento\Sales\Model\Order\Payment;
use Magento\Vault\Api\Data\PaymentTokenInterface;
......@@ -275,7 +274,12 @@ final class Vault implements VaultPaymentInterface
*/
public function canUseInternal()
{
return $this->getVaultProvider()->canUseInternal();
$isInternalAllowed = $this->getConfiguredValue('can_use_internal');
// if config has't been specified for Vault, need to check payment provider option
if ($isInternalAllowed === null) {
return $this->getVaultProvider()->canUseInternal();
}
return (bool) $isInternalAllowed;
}
/**
......
......@@ -221,9 +221,11 @@ final class TokensConfigProvider
*/
private function getPaymentTokenEntityId()
{
return $this->getPaymentTokenManagement()
->getByPaymentId($this->getOrderPaymentEntityId())
->getEntityId();
$paymentToken = $this->getPaymentTokenManagement()->getByPaymentId($this->getOrderPaymentEntityId());
if ($paymentToken === null) {
throw new NoSuchEntityException(__('No available payment tokens for specified order payment.'));
}
return $paymentToken->getEntityId();
}
/**
......
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