Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ version: 2
jobs:
deploy:
machine:
enabled: true
image: ubuntu-2004:202201-02
steps:
- run:
name: Deploy Over SSH
Expand All @@ -26,7 +26,7 @@ jobs:

tag-release:
machine:
enabled: true
image: ubuntu-2004:202201-02
working_directory: ~/tnw_extension/
steps:
- checkout
Expand Down
92 changes: 92 additions & 0 deletions Model/Checks/HasActiveSavedCreditCards.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php
/**
* TNW_Stripe extension
* NOTICE OF LICENSE
*
* This source file is subject to the OSL 3.0 License
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/osl-3.0.php
*
* @category TNW
* @package TNW_Stripe
* @copyright Copyright (c) 2017-2022
* @license Open Software License (OSL 3.0)
*/
namespace TNW\Stripe\Model\Checks;

use DateTimeZone;
use Magento\Framework\Intl\DateTimeFactory;
use Magento\Payment\Model\Checks\SpecificationInterface;
use Magento\Payment\Model\MethodInterface;
use Magento\Quote\Model\Quote;
use Magento\Vault\Api\Data\PaymentTokenInterface;
use Magento\Vault\Model\ResourceModel\PaymentToken\Collection;
use TNW\Stripe\Model\Ui\ConfigProvider;

/**
* Model class for customer's saved credit cards presence check.
*/
class HasActiveSavedCreditCards implements SpecificationInterface
{
/**
* @var Collection
*/
private $paymentTokenCollection;

/**
* @var DateTimeFactory
*/
private $dateTimeFactory;

/**
* @param Collection $paymentTokenCollection
* @param DateTimeFactory $dateTimeFactory
*/
public function __construct(
Collection $paymentTokenCollection,
DateTimeFactory $dateTimeFactory
) {
$this->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;
}
}
60 changes: 51 additions & 9 deletions Model/CreatePaymentIntent.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -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';
}
Expand Down Expand Up @@ -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;
}
}
}
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "tnw/module-stripe",
"description": "Stripe Payments for Magento 2",
"type": "magento2-module",
"version": "2.3.11",
"version": "2.3.13",
"license": "OSL-3.0",
"require": {
"magento/framework": ">100",
Expand Down
15 changes: 15 additions & 0 deletions etc/csp_whitelist.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0"?>
<csp_whitelist xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Csp:etc/csp_whitelist.xsd">
<policies>
<policy id="script-src">
<values>
<value id="stripe" type="host">https://*.stripe.com</value>
</values>
</policy>
<policy id="frame-src">
<values>
<value id="stripe" type="host">https://*.stripe.com</value>
</values>
</policy>
</policies>
</csp_whitelist>
16 changes: 16 additions & 0 deletions etc/di.xml
Original file line number Diff line number Diff line change
Expand Up @@ -393,4 +393,20 @@
type="TNW\Stripe\Plugin\Quote\Api\CartManagement"
sortOrder="1"/>
</type>

<type name="Magento\Payment\Model\Checks\SpecificationFactory">
<arguments>
<argument name="mapping" xsi:type="array">
<item name="has_saved_credit_cards" xsi:type="object">TNW\Stripe\Model\Checks\HasActiveSavedCreditCards</item>
</argument>
</arguments>
</type>

<type name="Magento\Payment\Block\Form\Container">
<arguments>
<argument name="additionalChecks" xsi:type="array">
<item name="0" xsi:type="string">has_saved_credit_cards</item>
</argument>
</arguments>
</type>
</config>
29 changes: 29 additions & 0 deletions view/adminhtml/web/js/stripe.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));
},

/**
Expand All @@ -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');
Expand Down Expand Up @@ -250,6 +265,9 @@ define([
self.error('Something went wrong')
$('body').trigger('processStop');
})
.always(function () {
self.isSubmitting = false;
});
},

/**
Expand Down Expand Up @@ -279,6 +297,7 @@ define([
* @returns {jQuery.Deferred}
*/
createPaymentIntent: function () {
this.isCreatingPaymentIntent = true;
var self = this,
dfd = $.Deferred();
if ($("#tnw_stripe_vault").length) {
Expand All @@ -294,6 +313,8 @@ define([
} else {
dfd.resolve(response);
}
}).always(function () {
self.isCreatingPaymentIntent = false;
});
return dfd;
},
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions view/adminhtml/web/js/vault.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ define([
}).done(function (response) {
if (response.skip_3ds) {
$('body').trigger('processStop');
self.setPaymentDetails(response.paymentIntent.id);
self.placeOrder();
return;
}
Expand Down