diff --git a/Model/Checks/HasActiveSavedCreditCards.php b/Model/Checks/HasActiveSavedCreditCards.php
new file mode 100644
index 0000000..b1723d6
--- /dev/null
+++ b/Model/Checks/HasActiveSavedCreditCards.php
@@ -0,0 +1,92 @@
+paymentTokenCollection = $paymentTokenCollection;
+ $this->dateTimeFactory = $dateTimeFactory;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function isApplicable(MethodInterface $paymentMethod, Quote $quote)
+ {
+ if ($paymentMethod->getCode() !== ConfigProvider::CC_VAULT_CODE) {
+ return true;
+ }
+
+ $customerId = $quote->getCustomerId();
+
+ return $customerId !== null && $this->hasActiveSavedCards($customerId);
+ }
+
+ /**
+ * Checks if customer has active saved credit cards.
+ *
+ * @param int $customerId
+ * @return bool
+ */
+ private function hasActiveSavedCards($customerId)
+ {
+ $this->paymentTokenCollection->addFilter(PaymentTokenInterface::CUSTOMER_ID, $customerId);
+ $this->paymentTokenCollection
+ ->addFilter(PaymentTokenInterface::PAYMENT_METHOD_CODE, ConfigProvider::CODE);
+ $this->paymentTokenCollection->addFilter(PaymentTokenInterface::IS_ACTIVE, 1);
+ $this->paymentTokenCollection->addFilter(PaymentTokenInterface::IS_VISIBLE, 1);
+ $this->paymentTokenCollection->addFieldToFilter(
+ PaymentTokenInterface::EXPIRES_AT,
+ [
+ 'gt' => $this->dateTimeFactory->create(
+ 'now',
+ new DateTimeZone('UTC')
+ )->format('Y-m-d 00:00:00')
+ ]
+ );
+ return $this->paymentTokenCollection->count() > 0;
+ }
+}
diff --git a/Model/CreatePaymentIntent.php b/Model/CreatePaymentIntent.php
index a7d2650..70c7203 100644
--- a/Model/CreatePaymentIntent.php
+++ b/Model/CreatePaymentIntent.php
@@ -15,7 +15,15 @@
*/
namespace TNW\Stripe\Model;
+use Magento\Customer\Api\CustomerRepositoryInterface;
+use Magento\Framework\Exception\InputException;
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\Exception\NoSuchEntityException;
+use Magento\Framework\Exception\State\InputMismatchException;
+use Magento\Quote\Model\Quote;
use Magento\Vault\Api\PaymentTokenManagementInterface;
+use Stripe\Exception\ApiErrorException;
+use Stripe\PaymentIntent;
use TNW\Stripe\Helper\Payment\Formatter;
use TNW\Stripe\Model\Adapter\StripeAdapterFactory;
use TNW\Stripe\Helper\Customer as CustomerHelper;
@@ -64,40 +72,56 @@ class CreatePaymentIntent
*/
private $vaultTokenProcessor;
+ /**
+ * @var CustomerRepositoryInterface
+ */
+ private $customerRepository;
+
/**
* @param StripeAdapterFactory $adapterFactory
* @param CustomerHelper $customerHelper
* @param UrlInterface $url
* @param PaymentTokenManagementInterface $tokenManagement
* @param VaultTokenProcessor $vaultTokenProcessor
+ * @param CustomerRepositoryInterface $customerRepository
*/
public function __construct(
StripeAdapterFactory $adapterFactory,
CustomerHelper $customerHelper,
UrlInterface $url,
PaymentTokenManagementInterface $tokenManagement,
- VaultTokenProcessor $vaultTokenProcessor
+ VaultTokenProcessor $vaultTokenProcessor,
+ CustomerRepositoryInterface $customerRepository
) {
$this->vaultTokenProcessor = $vaultTokenProcessor;
$this->url = $url;
$this->adapterFactory = $adapterFactory;
$this->customerHelper = $customerHelper;
$this->tokenManagement = $tokenManagement;
+ $this->customerRepository = $customerRepository;
}
/**
* @param $data
- * @param \Magento\Quote\Model\Quote $quote
+ * @param Quote $quote
* @param bool $isLoggedIn
- * @return \Stripe\PaymentIntent
- * @throws \Magento\Framework\Exception\InputException
- * @throws \Magento\Framework\Exception\LocalizedException
- * @throws \Magento\Framework\Exception\NoSuchEntityException
- * @throws \Magento\Framework\Exception\State\InputMismatchException
- * @throws \Stripe\Exception\ApiErrorException
+ * @return PaymentIntent
+ * @throws LocalizedException
+ * @throws InputException
+ * @throws NoSuchEntityException
+ * @throws InputMismatchException
+ * @throws ApiErrorException
*/
public function getPaymentIntent($data, $quote, $isLoggedIn = false)
{
+ $customer = $quote->getCustomer();
+ if (!$customer->getId() && $quote->getCustomerEmail()
+ && $this->isExistingCustomerEmail($quote->getCustomerEmail(), $customer->getWebsiteId())
+ ) {
+ throw new LocalizedException(
+ __('A customer with the same email address already exists in an associated website.')
+ );
+ }
$stripeAdapter = $this->adapterFactory->create();
if (property_exists($data, 'public_hash')) {
$customerId = $quote->getCustomer()->getId();
@@ -116,7 +140,7 @@ public function getPaymentIntent($data, $quote, $isLoggedIn = false)
'payment_method' => $payment,
'metadata' => ['site' => $this->url->getBaseUrl()]
];
- $email = $quote->getBillingAddress()->getEmail();
+ $email = $quote->getCustomerEmail();
if (!$isLoggedIn) {
$attributes['description'] = 'guest';
}
@@ -149,4 +173,22 @@ public function getPaymentIntent($data, $quote, $isLoggedIn = false)
}
return $stripeAdapter->createPaymentIntent($params);
}
+
+ /**
+ * Checks if customer with given email exists
+ *
+ * @param string $email
+ * @param int|null $websiteId
+ * @return bool
+ * @throws LocalizedException
+ */
+ public function isExistingCustomerEmail($email, $websiteId = null)
+ {
+ try {
+ $this->customerRepository->get($email, $websiteId);
+ return true;
+ } catch (NoSuchEntityException $e) {
+ return false;
+ }
+ }
}
diff --git a/composer.json b/composer.json
index 7bbe60a..5ed1b0d 100755
--- a/composer.json
+++ b/composer.json
@@ -2,7 +2,7 @@
"name": "tnw/module-stripe",
"description": "Stripe Payments for Magento 2",
"type": "magento2-module",
- "version": "2.3.11",
+ "version": "2.3.12",
"license": "OSL-3.0",
"require": {
"magento/framework": ">100",
diff --git a/etc/csp_whitelist.xml b/etc/csp_whitelist.xml
new file mode 100644
index 0000000..3c8e67f
--- /dev/null
+++ b/etc/csp_whitelist.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+ https://*.stripe.com
+
+
+
+
+ https://*.stripe.com
+
+
+
+
diff --git a/etc/di.xml b/etc/di.xml
index 21a24f0..2264ad0 100755
--- a/etc/di.xml
+++ b/etc/di.xml
@@ -393,4 +393,20 @@
type="TNW\Stripe\Plugin\Quote\Api\CartManagement"
sortOrder="1"/>
+
+
+
+
+ - TNW\Stripe\Model\Checks\HasActiveSavedCreditCards
+
+
+
+
+
+
+
+ - has_saved_credit_cards
+
+
+
diff --git a/view/adminhtml/web/js/stripe.js b/view/adminhtml/web/js/stripe.js
index 897aa64..7a983e4 100644
--- a/view/adminhtml/web/js/stripe.js
+++ b/view/adminhtml/web/js/stripe.js
@@ -176,6 +176,7 @@ define([
}.bind(this), 1000)
}
this.$selector.on('submitOrder.tnw_stripe', this.submitOrder.bind(this));
+ this.$selector.on('beforeSubmitOrder', this.beforeSubmitOrder.bind(this));
},
/**
@@ -184,12 +185,26 @@ define([
disableEventListeners: function () {
this.$selector.off('submitOrder');
this.$selector.off('submit');
+ this.$selector.off('beforeSubmitOrder');
+ },
+
+ /**
+ * Event handler for 'beforeSubmitOrder' event
+ * @param event
+ */
+ beforeSubmitOrder: function (event) {
+ if (this.active() && (this.getPaymentMethodToken() || this.isCreatingPaymentIntent || this.isSubmitting)) {
+ // Cancel order submission if payment intent is already created
+ event.result = false;
+ this.$selector.trigger('processStop');
+ }
},
/**
* Trigger order submit
*/
submitOrder: function () {
+ this.isSubmitting = true;
var self = this;
this.$selector.validate().form();
this.$selector.trigger('afterValidate.beforeSubmit');
@@ -250,6 +265,9 @@ define([
self.error('Something went wrong')
$('body').trigger('processStop');
})
+ .always(function () {
+ self.isSubmitting = false;
+ });
},
/**
@@ -279,6 +297,7 @@ define([
* @returns {jQuery.Deferred}
*/
createPaymentIntent: function () {
+ this.isCreatingPaymentIntent = true;
var self = this,
dfd = $.Deferred();
if ($("#tnw_stripe_vault").length) {
@@ -294,6 +313,8 @@ define([
} else {
dfd.resolve(response);
}
+ }).always(function () {
+ self.isCreatingPaymentIntent = false;
});
return dfd;
},
@@ -335,6 +356,14 @@ define([
$('#' + this.container).find('#' + this.code + '_cc_token').val(token);
},
+ /**
+ * Get payment method token
+ * @return {string}
+ */
+ getPaymentMethodToken: function () {
+ return $('#' + this.container).find('#' + this.code + '_cc_token').val();
+ },
+
/**
* Set card 3DS flag
* @param threedsactive
diff --git a/view/adminhtml/web/js/vault.js b/view/adminhtml/web/js/vault.js
index 2fd05e5..beeab71 100644
--- a/view/adminhtml/web/js/vault.js
+++ b/view/adminhtml/web/js/vault.js
@@ -168,6 +168,7 @@ define([
}).done(function (response) {
if (response.skip_3ds) {
$('body').trigger('processStop');
+ self.setPaymentDetails(response.paymentIntent.id);
self.placeOrder();
return;
}