From ce981d43874a4224f3db426786b9cb8264c67811 Mon Sep 17 00:00:00 2001 From: Laurynas Date: Thu, 28 Mar 2024 17:31:38 +0200 Subject: [PATCH] Added Iframe 3DS validation --- .../src/components/common/iframe.component.js | 65 +++++++++++++++++++ .../common/payment-token.component.js | 36 +++++++++- controllers/front/payment.php | 40 ++++++------ src/Repository/PaymentTokenRepository.php | 2 +- views/css/payment.css | 14 ++-- views/css/payments.css | 9 ++- views/templates/front/payment.tpl | 45 ++++++++++--- 7 files changed, 171 insertions(+), 40 deletions(-) create mode 100644 _dev/js/front/src/components/common/iframe.component.js diff --git a/_dev/js/front/src/components/common/iframe.component.js b/_dev/js/front/src/components/common/iframe.component.js new file mode 100644 index 000000000..442fb3f87 --- /dev/null +++ b/_dev/js/front/src/components/common/iframe.component.js @@ -0,0 +1,65 @@ +/** + * Copyright since 2007 PrestaShop SA and Contributors + * PrestaShop is an International Registered Trademark & Property of PrestaShop SA + * + * NOTICE OF LICENSE + * + * This source file is subject to the Academic Free License 3.0 (AFL-3.0) + * that is bundled with this package in the file LICENSE.md. + * It is also available through the world-wide-web at this URL: + * https://opensource.org/licenses/AFL-3.0 + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@prestashop.com so we can send you a copy immediately. + * + * @author PrestaShop SA + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) + */ +import { BaseComponent } from '../../core/dependency-injection/base.component'; + +export class IframeComponent extends BaseComponent { + static Inject = { + querySelectorService: 'QuerySelectorService', + config: 'PsCheckoutConfig', + $: '$' + }; + + created() { + this.data.parent = this.querySelectorService.getLoaderParent(); + this.data.src = this.props.src; + this.data.onConfirm = this.props.onConfirm || (() => {}); + this.data.onClose = this.props.onClose || (() => {}); + } + + render() { + this.overlay = document.createElement('div'); + this.overlay.classList.add('ps-checkout', 'overlay'); + + this.iframe = document.createElement('iframe'); + this.iframe.src = this.data.src; + this.iframe.classList.add('ps-checkout', 'ps-checkout-iframe'); + + this.overlay.append(this.iframe); + this.data.parent.append(this.overlay); + + return this; + } + + reload(url = null) { + if (url) { + this.iframe.src = url; + } + this.iframe.contentWindow.location.reload(); + } + + show() { + this.overlay.classList.add('visible'); + document.body.style.overflow = 'hidden'; + } + + hide() { + this.overlay.classList.remove('visible'); + document.body.style.overflow = ''; + } +} diff --git a/_dev/js/front/src/components/common/payment-token.component.js b/_dev/js/front/src/components/common/payment-token.component.js index c794c997f..5cb9c718f 100644 --- a/_dev/js/front/src/components/common/payment-token.component.js +++ b/_dev/js/front/src/components/common/payment-token.component.js @@ -18,6 +18,7 @@ */ import { BaseComponent } from '../../core/dependency-injection/base.component'; import {ModalComponent} from "./modal.component"; +import {IframeComponent} from "./iframe.component"; /** * @typedef PaymentTokenComponentProps @@ -55,6 +56,21 @@ export class PaymentTokenComponent extends BaseComponent { this.data.disabled = false; this.data.modal = null; + this.data.iframe = null; + + window.document.addEventListener('3DS-success', this.validateOrder) + window.document.addEventListener('3DS-close', (event) => {this.data.iframe.hide();}) + } + + validateOrder(e) { + console.log(e); + this.data.iframe.hide(); + // this.psCheckoutApi.postValidateOrder( + // { + // fundingSource: this.getVaultFormData().fundingSource, + // orderID: this.data.orderId + // } + // ).catch((error) => this.handleError(error)); } showModal() { @@ -80,6 +96,21 @@ export class PaymentTokenComponent extends BaseComponent { this.data.modal.show(); } + showIframe() { + const confirmationUrl = new URL(this.config.paymentUrl); + // confirmationUrl.searchParams.append('orderID', this.data.orderId); + confirmationUrl.searchParams.append('orderID', '4H913400R2970140U'); + + if (!this.data.iframe) { + this.data.iframe = new IframeComponent(this.app, { + src: confirmationUrl.toString() + }).render(); + } else { + this.data.iframe.reload(confirmationUrl.toString()); + } + this.data.iframe.show(); + } + onDeleteConfirm() { const vaultId = this.getVaultFormData().vaultId; this.psCheckoutApi.postDeleteVaultedToken({vaultId}).then(() => { @@ -171,10 +202,11 @@ export class PaymentTokenComponent extends BaseComponent { this.data.HTMLElementButton.addEventListener('click', (event) => { event.preventDefault(); - this.data.loader.show(); + // this.data.loader.show(); this.data.HTMLElementButton.setAttribute('disabled', ''); - this.createOrder(); + this.showIframe(); + // this.createOrder(); }); } diff --git a/controllers/front/payment.php b/controllers/front/payment.php index aa265bdc3..8ed79461d 100644 --- a/controllers/front/payment.php +++ b/controllers/front/payment.php @@ -18,8 +18,10 @@ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 */ +use PrestaShop\Module\PrestashopCheckout\Checkout\CheckoutChecker; use PrestaShop\Module\PrestashopCheckout\CommandBus\CommandBusInterface; use PrestaShop\Module\PrestashopCheckout\Controller\AbstractFrontController; +use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; use PrestaShop\Module\PrestashopCheckout\Order\Command\CreateOrderCommand; use PrestaShop\Module\PrestashopCheckout\PayPal\Card3DSecure; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\CapturePayPalOrderCommand; @@ -97,8 +99,10 @@ public function postProcess() throw new Exception('PayPal order does not belong to this customer'); } - $payPalOrderFromCache = $payPalOrderProvider->getById($payPalOrder->getId()->getValue()); - +// $payPalOrderFromCache = $payPalOrderProvider->getById($payPalOrder->getId()->getValue()); + $payPalOrderFromCache = [ + 'status' => 'TEST' + ]; if ($payPalOrderFromCache['status'] === 'COMPLETED') { $capture = $payPalOrderFromCache['purchase_units'][0]['payments']['captures'][0]; if ($capture['status'] === 'COMPLETED') { @@ -118,28 +122,26 @@ public function postProcess() // WHEN 3DS fails if ($payPalOrderFromCache['status'] === 'CREATED') { - $card3DSecure = new Card3DSecure(); - switch ($card3DSecure->continueWithAuthorization($payPalOrderFromCache)) { - case Card3DSecure::RETRY: - $this->redirectTo3DSVerification($payPalOrderFromCache); - break; - case Card3DSecure::PROCEED: - $commandBus->handle(new CapturePayPalOrderCommand($this->paypalOrderId->getValue(), array_keys($payPalOrderFromCache['payment_source'])[0])); - $payPalOrderFromCache = $payPalOrderCache->get($this->paypalOrderId->getValue()); - $capture = $payPalOrderFromCache['purchase_units'][0]['payments']['captures'][0]; - if ($capture['status'] === 'COMPLETED') { - $commandBus->handle(new CreateOrderCommand($payPalOrder->getId()->getValue(), $capture)); - $this->redirectToOrderConfirmationPage($payPalOrder->getIdCart(), $capture['id'], $payPalOrderFromCache['status']); - } - break; - case Card3DSecure::NO_DECISION: - default: - break; + /** @var CheckoutChecker $checkoutChecker */ + $checkoutChecker = $this->module->getService(CheckoutChecker::class); + + try { + $checkoutChecker->continueWithAuthorization($payPalOrder->getIdCart(), $payPalOrderFromCache); + $this->context->smarty->assign('success3DS', true); + } catch (PsCheckoutException $exception) { + switch ($exception->getCode()) { + case PsCheckoutException::PAYPAL_PAYMENT_CARD_SCA_UNKNOWN: + $this->redirectTo3DSVerification($payPalOrderFromCache); + break; + default: + throw $exception; + } } } } catch (Exception $exception) { $this->context->smarty->assign('error', $exception->getMessage()); } +// $this->context->smarty->assign('success3DS', true); } /** diff --git a/src/Repository/PaymentTokenRepository.php b/src/Repository/PaymentTokenRepository.php index 30c808dd8..643384418 100644 --- a/src/Repository/PaymentTokenRepository.php +++ b/src/Repository/PaymentTokenRepository.php @@ -84,7 +84,7 @@ public function findByPrestaShopCustomerId($psCustomerId, $onlyVaulted = false) $query->select('t.*') ->from(PaymentToken::TABLE, 't') ->leftJoin('pscheckout_customer', 'c', 't.`paypal_customer_id` = c.`paypal_customer_id`') - ->where('c.`id_customer` =' . (int) $psCustomerId) + ->where('c.`id_customer` = ' . (int) $psCustomerId) ->orderBy('t.`is_favorite` DESC') ->orderBy('t.`id` ASC'); diff --git a/views/css/payment.css b/views/css/payment.css index 9d49d6d2e..ab340534b 100644 --- a/views/css/payment.css +++ b/views/css/payment.css @@ -29,18 +29,20 @@ } .ps-checkout.content { - width: 500px; - height: 250px; - max-width: 90%; - border-radius: 15px; + width: 100%; + height: 100%; border: 2px solid #AAAAAA; padding: 50px; - box-shadow: 0 0 10px 2px rgba(0, 0, 0, 0.1); display: flex; flex-direction: column; - justify-content: space-between; + justify-content: center; + background-color: #FFFFFF; } .ps-checkout.order-link { text-align: center; } + +#wrapper { + background-color: transparent; +} diff --git a/views/css/payments.css b/views/css/payments.css index a25daa188..3fbc5b317 100755 --- a/views/css/payments.css +++ b/views/css/payments.css @@ -269,7 +269,7 @@ opacity: 100; } -.ps-checkout.popup, .ps-checkout.ps-checkout-modal { +.ps-checkout.popup, .ps-checkout.ps-checkout-modal, .ps-checkout.ps-checkout-iframe { position: absolute; top: 0; left: 0; @@ -285,7 +285,12 @@ border-radius: 15px; } -.ps-checkout.ps-checkout-modal { +.ps-checkout.ps-checkout-iframe { + width: 500px; + border: none; +} + +.ps-checkout.ps-checkout-modal, .ps-checkout.ps-checkout-iframe { border-radius: 0; height: fit-content; } diff --git a/views/templates/front/payment.tpl b/views/templates/front/payment.tpl index 334917301..f6315f2b8 100644 --- a/views/templates/front/payment.tpl +++ b/views/templates/front/payment.tpl @@ -18,20 +18,45 @@ *} {extends file='page.tpl'} {block name='content'} -
-
-
- {if isset($error)} - {$error} + +
+
+ {if isset($success3DS)} + +
+ {l s='3DS verification successful!' mod='ps_checkout'} +
{else} - {l s='3DS verification failed, please try again.' mod='ps_checkout'} +
+ {if isset($error)} + {$error} + {else} + {l s='3DS verification failed, please try again.' mod='ps_checkout'} + {/if} +
+ {/if}
-
-
+ {literal} + + {/literal} {/block} {block name='notifications'} {/block}