Skip to content

Commit

Permalink
Added Iframe 3DS validation
Browse files Browse the repository at this point in the history
  • Loading branch information
L3RAZ committed Apr 2, 2024
1 parent 46139a9 commit ce981d4
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 40 deletions.
65 changes: 65 additions & 0 deletions _dev/js/front/src/components/common/iframe.component.js
Original file line number Diff line number Diff line change
@@ -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 <contact@prestashop.com>
* @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 = '';
}
}
36 changes: 34 additions & 2 deletions _dev/js/front/src/components/common/payment-token.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
import { BaseComponent } from '../../core/dependency-injection/base.component';
import {ModalComponent} from "./modal.component";
import {IframeComponent} from "./iframe.component";

/**
* @typedef PaymentTokenComponentProps
Expand Down Expand Up @@ -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() {
Expand All @@ -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(() => {
Expand Down Expand Up @@ -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();
});
}

Expand Down
40 changes: 21 additions & 19 deletions controllers/front/payment.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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') {
Expand All @@ -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);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/Repository/PaymentTokenRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down
14 changes: 8 additions & 6 deletions views/css/payment.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
9 changes: 7 additions & 2 deletions views/css/payments.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}
Expand Down
45 changes: 35 additions & 10 deletions views/templates/front/payment.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,45 @@
*}
{extends file='page.tpl'}
{block name='content'}
<div class="ps-checkout wrapper">
<div class="ps-checkout content">
<div class="alert alert-danger">
{if isset($error)}
{$error}

<div class="ps-checkout wrapper">
<div class="ps-checkout content">
{if isset($success3DS)}
<input type="hidden" name="3DS">
<div class="alert alert-success">
{l s='3DS verification successful!' mod='ps_checkout'}
</div>
{else}
{l s='3DS verification failed, please try again.' mod='ps_checkout'}
<div class="alert alert-danger">
{if isset($error)}
{$error}
{else}
{l s='3DS verification failed, please try again.' mod='ps_checkout'}
{/if}
</div>
<div class="ps-checkout order-link">
<a href="#">{l s='Back to order page' mod='ps_checkout'}</a>
</div>
{/if}
</div>
<div class="ps-checkout order-link">
<a href="{$order_url}">{l s='Back to order page' mod='ps_checkout'}</a>
</div>
</div>
</div>
{literal}
<script>
window.onload = () => {
if (document.querySelector('input[name="3DS"]')) {
window.parent.document.dispatchEvent(new Event('3DS-success'));
}

if (document.querySelector('.ps-checkout.order-link>a')) {
const orderLink = document.querySelector('.ps-checkout.order-link>a');
orderLink.addEventListener('click', (e) => {
e.preventDefault();
window.parent.document.dispatchEvent(new Event('3DS-close'));
})
}
};
</script>
{/literal}
{/block}
{block name='notifications'}
{/block}
Expand Down

0 comments on commit ce981d4

Please sign in to comment.