Skip to content

Commit

Permalink
Refacto/payment method token (#1181)
Browse files Browse the repository at this point in the history
* Replace client token by user id token

* Implement payment method token

* Adding payment method token events for each webhook

* Added is_favorite column to token table

* PaymentToken entity and repository

* Added new JS component for vaulted payment methods

* Added correct payment logo display

* Added custom marks for vaulted payment methods

* Moved vaulting config check to fundingSourceProvider

* CS fix

* Added forms for vaulting payments and setting as favorite

* Renamed smarty template variables

* Added token delete handler and repository functions

* Temp fix for token events

* Changed delete function argument type

* Undid database table changes

* Added event subscriber implementation

* CS fix

* Fixed OrderDispatcher

* PHPStan fixes

* Added PayPal customer saving

* Added logic to set token as favorite after receiving webhook

* CS fix

* phpStan fix

* Remove final_capture from pscheckout_order table

* Added payment token delete without confirmation

* Added modal component and confirmation for payment token delete

* Added config for token event subscriber

* Added token deletion

* Added total count function for admin ajax controller

* Added setting payment token as favorite on order create

* Added userIdToken loading

* Added payment token status to Db table, entity and repository

* Added API call to get userIdToken

* added payment token status fetching from webhook

* Added token filtering by status

* Added Payment controller for validating orders with payment token

* Added check if payment token belongs to customer

* Replaced new order payload builder with old one in create order command handler

* Changed token and oauth functions to match API specifications

* Added new handler to save paypal order and related entities to database

* CS fix

* Added template for 3DS error

* Removed OauthHttpClient and added configuration for CheckoutHttpCLient base URL

* CS fix

* Add vault to legacy OrderPayloadBuilder

* Added 3DS logic to payment controller and moved order entities

* Fix ExpressCheckout product data

* Fix create order payload address

* Fix json array for card brands

* Added cancel_url to order payload

* Changed PayPal Order update functions and added Order update on PayPalOrderUpdated event

* Fixed total token count query

* PHPStan fixes

* CS fix

* Fixes payment token saving during order capture

* Fixes for vaulting order creation and capture

* Replaced returl_url in order create payload with redirect_uri in 3DS url

* Remove deprecated PayPal Client Token

* CS fixes

---------

Co-authored-by: Bastien Tafforeau <[email protected]>
Co-authored-by: Laurynas <[email protected]>
  • Loading branch information
3 people authored Apr 3, 2024
1 parent 1a5aa3a commit 0a73564
Show file tree
Hide file tree
Showing 106 changed files with 3,788 additions and 979 deletions.
31 changes: 31 additions & 0 deletions _dev/js/front/src/api/ps-checkout.api.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,4 +227,35 @@ export class PsCheckoutApi extends BaseClass {
})
);
}

postDeleteVaultedToken(data) {
return fetch(this.config.vaultUrl, {
method: 'post',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json'
},
...(data ? { body: JSON.stringify({action: 'deleteToken', ...data}) } : {})
})
.then((response) => {
const contentType = response.headers.get('content-type');
const isJsonResponse =
contentType && contentType.indexOf('application/json') !== -1;

if (isJsonResponse) {
if (false === response.ok || response.status >= 400) {
return response.json().then((response) => {
throw response.body && response.body.error
? response.body.error
: { message: this.$('checkout.form.error.label') };
});
}

return response.json();
}

throw new Error(this.$('checkout.form.error.label'));
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,24 +43,11 @@ export class ExpressButtonProductComponent extends BaseComponent {

buttonContainer.append(this.checkoutExpressButton);

const {
id_product,
id_product_attribute,
id_customization,
quantity_wanted
} = this.prestashopService.getProductDetails();

this.children.expressCheckoutButton = new ExpressCheckoutButtonComponent(
this.app,
{
fundingSource: 'paypal',
querySelector: '#ps-checkout-express-button',
data: {
id_product,
id_product_attribute,
id_customization,
quantity_wanted
}
querySelector: '#ps-checkout-express-button'
}
).render();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,24 +47,11 @@ export class PayLaterButtonProductComponent extends BaseComponent {
buttonContainer.append(this.checkoutExpressButton);
}

const {
id_product,
id_product_attribute,
id_customization,
quantity_wanted
} = this.prestashopService.getProductDetails();

this.children.expressCheckoutButton = new ExpressCheckoutButtonComponent(
this.app,
{
fundingSource: 'paylater',
querySelector: '#ps-checkout-express-button',
data: {
id_product,
id_product_attribute,
id_customization,
quantity_wanted
}
querySelector: '#ps-checkout-express-button'
}
).render();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ export class PaymentOptionsComponent extends BaseComponent {
if (
this.config.expressCheckout.active &&
'ps_checkout-' + this.payPalService.getFundingSource() !==
HTMLListenerElements[index].button.dataset.moduleName
HTMLListenerElements[index].button.dataset.moduleName &&
this.payPalService.getOrderId()
) {
this.psCheckoutApi
.postCancelOrder({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,24 +51,11 @@ export class ExpressButtonProductComponent extends BaseComponent {
this.updateButtonContainerVisibility();
});

const {
id_product,
id_product_attribute,
id_customization,
quantity_wanted
} = this.prestashopService.getProductDetails();

this.children.expressCheckoutButton = new ExpressCheckoutButtonComponent(
this.app,
{
fundingSource: 'paypal',
querySelector: `#${BUTTON_CONTAINER_SELECTOR}`,
data: {
id_product,
id_product_attribute,
id_customization,
quantity_wanted
}
querySelector: `#${BUTTON_CONTAINER_SELECTOR}`
}
).render();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,24 +55,11 @@ export class PayLaterButtonProductComponent extends BaseComponent {
this.updateButtonContainerVisibility();
});

const {
id_product,
id_product_attribute,
id_customization,
quantity_wanted
} = this.prestashopService.getProductDetails();

this.children.expressCheckoutButton = new ExpressCheckoutButtonComponent(
this.app,
{
fundingSource: 'paylater',
querySelector: `#${BUTTON_CONTAINER_SELECTOR}`,
data: {
id_product,
id_product_attribute,
id_customization,
quantity_wanted
}
querySelector: `#${BUTTON_CONTAINER_SELECTOR}`
}
).render();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ export class PaymentOptionsComponent extends BaseComponent {
if (
this.config.expressCheckout.active &&
'ps_checkout-' + this.payPalService.getFundingSource() !==
radio.dataset.moduleName
radio.dataset.moduleName &&
this.payPalService.getOrderId()
) {
this.psCheckoutApi
.postCancelOrder({
Expand Down
16 changes: 14 additions & 2 deletions _dev/js/front/src/components/common/card-fields.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,18 @@ export class CardFieldsComponent extends BaseComponent {
this.toggleCardFieldsErrors();
}

getVaultFormData() {
if (this.data.HTMLElementCardForm) {
const formData = new FormData(this.data.HTMLElementCardForm);
return {
vault: formData.get(`ps_checkout-vault-payment-${this.data.name}`) === '1',
favorite: formData.get(`ps_checkout-favorite-payment-${this.data.name}`) === '1'
};

}
return {};
}

renderPayPalCardFields() {
this.data.HTMLElementCardForm.classList.toggle('loading', true);

Expand Down Expand Up @@ -223,10 +235,10 @@ export class CardFieldsComponent extends BaseComponent {

return this.psCheckoutApi
.postCreateOrder({
...this.getVaultFormData(),
...data,
fundingSource: this.data.name,
isHostedFields: true
// vault: storeCardInVault
isCardFields: true
})
.then((data) => {
this.data.orderId = data;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export class ExpressCheckoutButtonComponent extends BaseComponent {
static Inject = {
payPalService: 'PayPalService',
psCheckoutApi: 'PsCheckoutApi',
prestashopService: 'PrestashopService',
$: '$'
};

Expand Down Expand Up @@ -88,7 +89,22 @@ export class ExpressCheckoutButtonComponent extends BaseComponent {
}

createOrder(data) {
const extraData = this.props?.data ? this.props.data : {};
let extraData = {};

if (this.prestashopService.isProductPage()) {
let {
id_product,
id_product_attribute,
id_customization,
quantity_wanted
} = this.prestashopService.getProductDetails();
extraData = {
id_product,
id_product_attribute,
id_customization,
quantity_wanted
};
}

return this.psCheckoutApi
.postCreateOrder({
Expand Down
2 changes: 1 addition & 1 deletion _dev/js/front/src/components/common/marker.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export class MarkComponent extends BaseComponent {
const src = this.config.customMark[this.data.name];
let logoList = [];

if (this.config.cardSupportedBrands && this.config.cardLogoBrands) {
if (this.data.name === 'card' && this.config.cardSupportedBrands && this.config.cardLogoBrands) {
this.config.cardSupportedBrands.forEach(brand => {
if (this.config.cardLogoBrands[brand]) {
let customMarkImg = document.createElement('img');
Expand Down
109 changes: 109 additions & 0 deletions _dev/js/front/src/components/common/modal.component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/**
* 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 [email protected] so we can send you a copy immediately.
*
* @author PrestaShop SA <[email protected]>
* @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 ModalComponent extends BaseComponent {
static Inject = {
querySelectorService: 'QuerySelectorService',
config: 'PsCheckoutConfig',
$: '$'
};

created() {
this.data.parent = this.querySelectorService.getLoaderParent();
this.data.header = this.props.header || null;
this.data.content = this.props.content || null;
this.data.confirmText = this.props.confirmText || this.$('ok');
this.data.confirmType = this.props.confirmType || 'primary';
this.data.cancelText = this.props.cancelText || this.$('cancel');
this.data.cancelType = this.props.cancelType || 'primary';
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.overlay.addEventListener('click', (event) => {
if (event.target === this.overlay) {
this.data.onClose();
this.hide();
}
})

this.modal = document.createElement('div');
this.modal.classList.add('ps-checkout', 'ps-checkout-modal');

if (this.data.header) {
this.header = document.createElement('h1');
this.header.classList.add('ps-checkout', 'text');
this.header.innerHTML = this.data.header;

this.modal.append(this.header);
}

if (this.data.content) {
this.contentContainer = document.createElement('div');
this.contentContainer.classList.add('ps-checkout', 'content');
this.contentContainer.append(this.data.content);
this.modal.append(this.contentContainer);
}

this.footer = document.createElement('div');
this.footer.classList.add('ps-checkout', 'footer');

this.cancelButton = document.createElement('button');
this.cancelButton.innerHTML = this.data.cancelText;
this.cancelButton.classList.add('ps-checkout', 'btn', this.data.cancelType);

this.cancelButton.addEventListener('click', (event) => {
this.data.onClose();
this.hide();
})

this.confirmButton = document.createElement('button');
this.confirmButton.innerHTML = this.data.confirmText;
this.confirmButton.classList.add('ps-checkout', 'btn', this.data.confirmType);

this.confirmButton.addEventListener('click', (event) => {
this.data.onConfirm();
this.hide();
})

this.footer.append(this.cancelButton, this.confirmButton);

this.modal.append(this.footer);

this.overlay.append(this.modal);
this.data.parent.append(this.overlay);

return this;
}

show() {
this.overlay.classList.add('visible');
document.body.style.overflow = 'hidden';
}

hide() {
this.overlay.classList.remove('visible');
document.body.style.overflow = '';
}
}
15 changes: 14 additions & 1 deletion _dev/js/front/src/components/common/payment-option.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { SmartButtonComponent } from './smart-button.component';
import { PaymentFieldsComponent } from "./payment-fields.component";
import {CardFieldsComponent} from "./card-fields.component";
import {PS_VERSION_1_6} from "../../constants/ps-version.constants";
import {PaymentTokenComponent} from "./payment-token.component";

/**
* @typedef PaymentOptionComponentProps
Expand Down Expand Up @@ -74,6 +75,11 @@ export class PaymentOptionComponent extends BaseComponent {
);
}

getPaymentForm() {
const container = `pay-with-${this.data.HTMLElement.id}-form`;
return document.getElementById(container);
}

getLabel() {
const translationKey = `funding-source.name.${this.data.name}`;
const label =
Expand Down Expand Up @@ -170,7 +176,14 @@ export class PaymentOptionComponent extends BaseComponent {
this.data.HTMLElementCardFields.style.display = 'none';
}

if (this.data.HTMLElementCardFields && isCardFieldsEligible && isCardFieldsAvailable) {
if (this.props.fundingSource.name.includes('token')) {
this.children.paymentToken = new PaymentTokenComponent(this.app, {
fundingSource: this.props.fundingSource,
HTMLElementRadio: this.data.HTMLElement,
HTMLElementContainer: this.data.HTMLElementContainer,
HTMLElementForm: this.getPaymentForm()
}).render();
} else if (this.data.HTMLElementCardFields && isCardFieldsEligible && isCardFieldsAvailable) {
this.data.HTMLElementCardFields.style.display = '';
this.children.cardFields = new CardFieldsComponent(this.app, {
fundingSource: this.props.fundingSource,
Expand Down
Loading

0 comments on commit 0a73564

Please sign in to comment.