Merge trunk

This commit is contained in:
Emili Castells Guasch 2023-11-21 14:59:46 +01:00
commit 9be6601ba9
13 changed files with 769 additions and 291 deletions

View file

@ -53,6 +53,14 @@ return function ( string $root_dir ): iterable {
$modules[] = ( require "$modules_dir/ppcp-saved-payment-checker/module.php" )();
}
if ( apply_filters(
//phpcs:disable WordPress.NamingConventions.ValidHookName.UseUnderscores
'woocommerce.feature-flags.woocommerce_paypal_payments.card_fields_enabled',
getenv( 'PCP_CARD_FIELDS_ENABLED' ) === '1'
) ) {
$modules[] = ( require "$modules_dir/ppcp-card-fields/module.php" )();
}
if ( apply_filters(
//phpcs:disable WordPress.NamingConventions.ValidHookName.UseUnderscores
'woocommerce.feature-flags.woocommerce_paypal_payments.save_payment_methods_enabled',

View file

@ -5,7 +5,8 @@ import CheckoutBootstap from './modules/ContextBootstrap/CheckoutBootstap';
import PayNowBootstrap from "./modules/ContextBootstrap/PayNowBootstrap";
import Renderer from './modules/Renderer/Renderer';
import ErrorHandler from './modules/ErrorHandler';
import CreditCardRenderer from "./modules/Renderer/CreditCardRenderer";
import HostedFieldsRenderer from "./modules/Renderer/HostedFieldsRenderer";
import CardFieldsRenderer from "./modules/Renderer/CardFieldsRenderer";
import MessageRenderer from "./modules/Renderer/MessageRenderer";
import Spinner from "./modules/Helper/Spinner";
import {
@ -37,7 +38,11 @@ const bootstrap = () => {
document.querySelector(checkoutFormSelector) ?? document.querySelector('.woocommerce-notices-wrapper')
);
const spinner = new Spinner();
const creditCardRenderer = new CreditCardRenderer(PayPalCommerceGateway, errorHandler, spinner);
let creditCardRenderer = new HostedFieldsRenderer(PayPalCommerceGateway, errorHandler, spinner);
if (typeof paypal.CardFields !== 'undefined') {
creditCardRenderer = new CardFieldsRenderer(PayPalCommerceGateway, errorHandler, spinner);
}
const formSaver = new FormSaver(
PayPalCommerceGateway.ajax.save_checkout_form.endpoint,

View file

@ -0,0 +1,155 @@
import {show} from "../Helper/Hiding";
class CardFieldsRenderer {
constructor(defaultConfig, errorHandler, spinner) {
this.defaultConfig = defaultConfig;
this.errorHandler = errorHandler;
this.spinner = spinner;
this.cardValid = false;
this.formValid = false;
this.emptyFields = new Set(['number', 'cvv', 'expirationDate']);
this.currentHostedFieldsInstance = null;
}
render(wrapper, contextConfig) {
if (
(
this.defaultConfig.context !== 'checkout'
&& this.defaultConfig.context !== 'pay-now'
)
|| wrapper === null
|| document.querySelector(wrapper) === null
) {
return;
}
const buttonSelector = wrapper + ' button';
const gateWayBox = document.querySelector('.payment_box.payment_method_ppcp-credit-card-gateway');
if (!gateWayBox) {
return
}
const oldDisplayStyle = gateWayBox.style.display;
gateWayBox.style.display = 'block';
const hideDccGateway = document.querySelector('#ppcp-hide-dcc');
if (hideDccGateway) {
hideDccGateway.parentNode.removeChild(hideDccGateway);
}
const cardField = paypal.CardFields({
createOrder: contextConfig.createOrder,
onApprove: function (data) {
return contextConfig.onApprove(data);
},
onError: function (error) {
console.error(error)
this.spinner.unblock();
}
});
if (cardField.isEligible()) {
const nameField = document.getElementById('ppcp-credit-card-gateway-card-name');
if (nameField) {
let styles = this.cardFieldStyles(nameField);
cardField.NameField({style: {'input': styles}}).render(nameField.parentNode);
nameField.remove();
}
const numberField = document.getElementById('ppcp-credit-card-gateway-card-number');
if (numberField) {
let styles = this.cardFieldStyles(numberField);
cardField.NumberField({style: {'input': styles}}).render(numberField.parentNode);
numberField.remove();
}
const expiryField = document.getElementById('ppcp-credit-card-gateway-card-expiry');
if (expiryField) {
let styles = this.cardFieldStyles(expiryField);
cardField.ExpiryField({style: {'input': styles}}).render(expiryField.parentNode);
expiryField.remove();
}
const cvvField = document.getElementById('ppcp-credit-card-gateway-card-cvc');
if (cvvField) {
let styles = this.cardFieldStyles(cvvField);
cardField.CVVField({style: {'input': styles}}).render(cvvField.parentNode);
cvvField.remove();
}
document.dispatchEvent(new CustomEvent("hosted_fields_loaded"));
}
gateWayBox.style.display = oldDisplayStyle;
show(buttonSelector);
document.querySelector(buttonSelector).addEventListener("click", (event) => {
event.preventDefault();
this.spinner.block();
this.errorHandler.clear();
cardField.submit()
.catch((error) => {
this.spinner.unblock();
console.error(error)
this.errorHandler.message(this.defaultConfig.hosted_fields.labels.fields_not_valid);
})
});
}
cardFieldStyles(field) {
const allowedProperties = [
'appearance',
'color',
'direction',
'font',
'font-family',
'font-size',
'font-size-adjust',
'font-stretch',
'font-style',
'font-variant',
'font-variant-alternates',
'font-variant-caps',
'font-variant-east-asian',
'font-variant-ligatures',
'font-variant-numeric',
'font-weight',
'letter-spacing',
'line-height',
'opacity',
'outline',
'padding',
'padding-bottom',
'padding-left',
'padding-right',
'padding-top',
'text-shadow',
'transition',
'-moz-appearance',
'-moz-osx-font-smoothing',
'-moz-tap-highlight-color',
'-moz-transition',
'-webkit-appearance',
'-webkit-osx-font-smoothing',
'-webkit-tap-highlight-color',
'-webkit-transition',
];
const stylesRaw = window.getComputedStyle(field);
const styles = {};
Object.values(stylesRaw).forEach((prop) => {
if (!stylesRaw[prop] || !allowedProperties.includes(prop)) {
return;
}
styles[prop] = '' + stylesRaw[prop];
});
return styles;
}
}
export default CardFieldsRenderer;

View file

@ -1,275 +0,0 @@
import dccInputFactory from "../Helper/DccInputFactory";
import {show} from "../Helper/Hiding";
import Product from "../Entity/Product";
class CreditCardRenderer {
constructor(defaultConfig, errorHandler, spinner) {
this.defaultConfig = defaultConfig;
this.errorHandler = errorHandler;
this.spinner = spinner;
this.cardValid = false;
this.formValid = false;
this.emptyFields = new Set(['number', 'cvv', 'expirationDate']);
this.currentHostedFieldsInstance = null;
}
render(wrapper, contextConfig) {
if (
(
this.defaultConfig.context !== 'checkout'
&& this.defaultConfig.context !== 'pay-now'
)
|| wrapper === null
|| document.querySelector(wrapper) === null
) {
return;
}
if (
typeof paypal.HostedFields === 'undefined'
|| ! paypal.HostedFields.isEligible()
) {
const wrapperElement = document.querySelector(wrapper);
wrapperElement.parentNode.removeChild(wrapperElement);
return;
}
const buttonSelector = wrapper + ' button';
if (this.currentHostedFieldsInstance) {
this.currentHostedFieldsInstance.teardown()
.catch(err => console.error(`Hosted fields teardown error: ${err}`));
this.currentHostedFieldsInstance = null;
}
const gateWayBox = document.querySelector('.payment_box.payment_method_ppcp-credit-card-gateway');
if(! gateWayBox) {
return
}
const oldDisplayStyle = gateWayBox.style.display;
gateWayBox.style.display = 'block';
const hideDccGateway = document.querySelector('#ppcp-hide-dcc');
if (hideDccGateway) {
hideDccGateway.parentNode.removeChild(hideDccGateway);
}
const cardNumberField = document.querySelector('#ppcp-credit-card-gateway-card-number');
const stylesRaw = window.getComputedStyle(cardNumberField);
let styles = {};
Object.values(stylesRaw).forEach( (prop) => {
if (! stylesRaw[prop]) {
return;
}
styles[prop] = '' + stylesRaw[prop];
});
const cardNumber = dccInputFactory(cardNumberField);
cardNumberField.parentNode.replaceChild(cardNumber, cardNumberField);
const cardExpiryField = document.querySelector('#ppcp-credit-card-gateway-card-expiry');
const cardExpiry = dccInputFactory(cardExpiryField);
cardExpiryField.parentNode.replaceChild(cardExpiry, cardExpiryField);
const cardCodeField = document.querySelector('#ppcp-credit-card-gateway-card-cvc');
const cardCode = dccInputFactory(cardCodeField);
cardCodeField.parentNode.replaceChild(cardCode, cardCodeField);
gateWayBox.style.display = oldDisplayStyle;
const formWrapper = '.payment_box payment_method_ppcp-credit-card-gateway';
if (
this.defaultConfig.enforce_vault
&& document.querySelector(formWrapper + ' .ppcp-credit-card-vault')
) {
document.querySelector(formWrapper + ' .ppcp-credit-card-vault').checked = true;
document.querySelector(formWrapper + ' .ppcp-credit-card-vault').setAttribute('disabled', true);
}
paypal.HostedFields.render({
createOrder: contextConfig.createOrder,
styles: {
'input': styles
},
fields: {
number: {
selector: '#ppcp-credit-card-gateway-card-number',
placeholder: this.defaultConfig.hosted_fields.labels.credit_card_number,
},
cvv: {
selector: '#ppcp-credit-card-gateway-card-cvc',
placeholder: this.defaultConfig.hosted_fields.labels.cvv,
},
expirationDate: {
selector: '#ppcp-credit-card-gateway-card-expiry',
placeholder: this.defaultConfig.hosted_fields.labels.mm_yy,
}
}
}).then(hostedFields => {
document.dispatchEvent(new CustomEvent("hosted_fields_loaded"));
this.currentHostedFieldsInstance = hostedFields;
hostedFields.on('inputSubmitRequest', () => {
this._submit(contextConfig);
});
hostedFields.on('cardTypeChange', (event) => {
if ( ! event.cards.length ) {
this.cardValid = false;
return;
}
const validCards = this.defaultConfig.hosted_fields.valid_cards;
this.cardValid = validCards.indexOf(event.cards[0].type) !== -1;
const className = this._cardNumberFiledCLassNameByCardType(event.cards[0].type);
this._recreateElementClassAttribute(cardNumber, cardNumberField.className);
if (event.cards.length === 1) {
cardNumber.classList.add(className);
}
})
hostedFields.on('validityChange', (event) => {
this.formValid = Object.keys(event.fields).every(function (key) {
return event.fields[key].isValid;
});
});
hostedFields.on('empty', (event) => {
this._recreateElementClassAttribute(cardNumber, cardNumberField.className);
this.emptyFields.add(event.emittedBy);
});
hostedFields.on('notEmpty', (event) => {
this.emptyFields.delete(event.emittedBy);
});
show(buttonSelector);
if (document.querySelector(wrapper).getAttribute('data-ppcp-subscribed') !== true) {
document.querySelector(buttonSelector).addEventListener(
'click',
event => {
event.preventDefault();
this._submit(contextConfig);
}
);
document.querySelector(wrapper).setAttribute('data-ppcp-subscribed', true);
}
});
document.querySelector('#payment_method_ppcp-credit-card-gateway').addEventListener(
'click',
() => {
document.querySelector('label[for=ppcp-credit-card-gateway-card-number]').click();
}
)
}
disableFields() {
if (this.currentHostedFieldsInstance) {
this.currentHostedFieldsInstance.setAttribute({
field: 'number',
attribute: 'disabled'
})
this.currentHostedFieldsInstance.setAttribute({
field: 'cvv',
attribute: 'disabled'
})
this.currentHostedFieldsInstance.setAttribute({
field: 'expirationDate',
attribute: 'disabled'
})
}
}
enableFields() {
if (this.currentHostedFieldsInstance) {
this.currentHostedFieldsInstance.removeAttribute({
field: 'number',
attribute: 'disabled'
})
this.currentHostedFieldsInstance.removeAttribute({
field: 'cvv',
attribute: 'disabled'
})
this.currentHostedFieldsInstance.removeAttribute({
field: 'expirationDate',
attribute: 'disabled'
})
}
}
_submit(contextConfig) {
this.spinner.block();
this.errorHandler.clear();
if (this.formValid && this.cardValid) {
const save_card = this.defaultConfig.can_save_vault_token ? true : false;
let vault = document.getElementById('ppcp-credit-card-vault') ?
document.getElementById('ppcp-credit-card-vault').checked : save_card;
if (this.defaultConfig.enforce_vault) {
vault = true;
}
const contingency = this.defaultConfig.hosted_fields.contingency;
const hostedFieldsData = {
vault: vault
};
if (contingency !== 'NO_3D_SECURE') {
hostedFieldsData.contingencies = [contingency];
}
if (this.defaultConfig.payer) {
hostedFieldsData.cardholderName = this.defaultConfig.payer.name.given_name + ' ' + this.defaultConfig.payer.name.surname;
}
if (!hostedFieldsData.cardholderName) {
const firstName = document.getElementById('billing_first_name') ? document.getElementById('billing_first_name').value : '';
const lastName = document.getElementById('billing_last_name') ? document.getElementById('billing_last_name').value : '';
hostedFieldsData.cardholderName = firstName + ' ' + lastName;
}
this.currentHostedFieldsInstance.submit(hostedFieldsData).then((payload) => {
payload.orderID = payload.orderId;
this.spinner.unblock();
return contextConfig.onApprove(payload);
}).catch(err => {
this.spinner.unblock();
this.errorHandler.clear();
if (err.data?.details?.length) {
this.errorHandler.message(err.data.details.map(d => `${d.issue} ${d.description}`).join('<br/>'));
} else if (err.details?.length) {
this.errorHandler.message(err.details.map(d => `${d.issue} ${d.description}`).join('<br/>'));
} else if (err.data?.errors?.length > 0) {
this.errorHandler.messages(err.data.errors);
} else if (err.data?.message) {
this.errorHandler.message(err.data.message);
} else if (err.message) {
this.errorHandler.message(err.message);
} else {
this.errorHandler.genericError();
}
});
} else {
this.spinner.unblock();
let message = this.defaultConfig.labels.error.generic;
if (this.emptyFields.size > 0) {
message = this.defaultConfig.hosted_fields.labels.fields_empty;
} else if (!this.cardValid) {
message = this.defaultConfig.hosted_fields.labels.card_not_supported;
} else if (!this.formValid) {
message = this.defaultConfig.hosted_fields.labels.fields_not_valid;
}
this.errorHandler.message(message);
}
}
_cardNumberFiledCLassNameByCardType(cardType) {
return cardType === 'american-express' ? 'amex' : cardType.replace('-', '');
}
_recreateElementClassAttribute(element, newClassName) {
element.removeAttribute('class')
element.setAttribute('class', newClassName);
}
}
export default CreditCardRenderer;

View file

@ -0,0 +1,274 @@
import dccInputFactory from "../Helper/DccInputFactory";
import {show} from "../Helper/Hiding";
class HostedFieldsRenderer {
constructor(defaultConfig, errorHandler, spinner) {
this.defaultConfig = defaultConfig;
this.errorHandler = errorHandler;
this.spinner = spinner;
this.cardValid = false;
this.formValid = false;
this.emptyFields = new Set(['number', 'cvv', 'expirationDate']);
this.currentHostedFieldsInstance = null;
}
render(wrapper, contextConfig) {
if (
(
this.defaultConfig.context !== 'checkout'
&& this.defaultConfig.context !== 'pay-now'
)
|| wrapper === null
|| document.querySelector(wrapper) === null
) {
return;
}
if (typeof paypal.HostedFields !== 'undefined' && paypal.HostedFields.isEligible()) {
const buttonSelector = wrapper + ' button';
if (this.currentHostedFieldsInstance) {
this.currentHostedFieldsInstance.teardown()
.catch(err => console.error(`Hosted fields teardown error: ${err}`));
this.currentHostedFieldsInstance = null;
}
const gateWayBox = document.querySelector('.payment_box.payment_method_ppcp-credit-card-gateway');
if (!gateWayBox) {
return
}
const oldDisplayStyle = gateWayBox.style.display;
gateWayBox.style.display = 'block';
const hideDccGateway = document.querySelector('#ppcp-hide-dcc');
if (hideDccGateway) {
hideDccGateway.parentNode.removeChild(hideDccGateway);
}
const cardNumberField = document.querySelector('#ppcp-credit-card-gateway-card-number');
const stylesRaw = window.getComputedStyle(cardNumberField);
let styles = {};
Object.values(stylesRaw).forEach((prop) => {
if (!stylesRaw[prop]) {
return;
}
styles[prop] = '' + stylesRaw[prop];
});
const cardNumber = dccInputFactory(cardNumberField);
cardNumberField.parentNode.replaceChild(cardNumber, cardNumberField);
const cardExpiryField = document.querySelector('#ppcp-credit-card-gateway-card-expiry');
const cardExpiry = dccInputFactory(cardExpiryField);
cardExpiryField.parentNode.replaceChild(cardExpiry, cardExpiryField);
const cardCodeField = document.querySelector('#ppcp-credit-card-gateway-card-cvc');
const cardCode = dccInputFactory(cardCodeField);
cardCodeField.parentNode.replaceChild(cardCode, cardCodeField);
gateWayBox.style.display = oldDisplayStyle;
const formWrapper = '.payment_box payment_method_ppcp-credit-card-gateway';
if (
this.defaultConfig.enforce_vault
&& document.querySelector(formWrapper + ' .ppcp-credit-card-vault')
) {
document.querySelector(formWrapper + ' .ppcp-credit-card-vault').checked = true;
document.querySelector(formWrapper + ' .ppcp-credit-card-vault').setAttribute('disabled', true);
}
paypal.HostedFields.render({
createOrder: contextConfig.createOrder,
styles: {
'input': styles
},
fields: {
number: {
selector: '#ppcp-credit-card-gateway-card-number',
placeholder: this.defaultConfig.hosted_fields.labels.credit_card_number,
},
cvv: {
selector: '#ppcp-credit-card-gateway-card-cvc',
placeholder: this.defaultConfig.hosted_fields.labels.cvv,
},
expirationDate: {
selector: '#ppcp-credit-card-gateway-card-expiry',
placeholder: this.defaultConfig.hosted_fields.labels.mm_yy,
}
}
}).then(hostedFields => {
document.dispatchEvent(new CustomEvent("hosted_fields_loaded"));
this.currentHostedFieldsInstance = hostedFields;
hostedFields.on('inputSubmitRequest', () => {
this._submit(contextConfig);
});
hostedFields.on('cardTypeChange', (event) => {
if (!event.cards.length) {
this.cardValid = false;
return;
}
const validCards = this.defaultConfig.hosted_fields.valid_cards;
this.cardValid = validCards.indexOf(event.cards[0].type) !== -1;
const className = this._cardNumberFiledCLassNameByCardType(event.cards[0].type);
this._recreateElementClassAttribute(cardNumber, cardNumberField.className);
if (event.cards.length === 1) {
cardNumber.classList.add(className);
}
})
hostedFields.on('validityChange', (event) => {
this.formValid = Object.keys(event.fields).every(function (key) {
return event.fields[key].isValid;
});
});
hostedFields.on('empty', (event) => {
this._recreateElementClassAttribute(cardNumber, cardNumberField.className);
this.emptyFields.add(event.emittedBy);
});
hostedFields.on('notEmpty', (event) => {
this.emptyFields.delete(event.emittedBy);
});
show(buttonSelector);
if (document.querySelector(wrapper).getAttribute('data-ppcp-subscribed') !== true) {
document.querySelector(buttonSelector).addEventListener(
'click',
event => {
event.preventDefault();
this._submit(contextConfig);
}
);
document.querySelector(wrapper).setAttribute('data-ppcp-subscribed', true);
}
});
document.querySelector('#payment_method_ppcp-credit-card-gateway').addEventListener(
'click',
() => {
document.querySelector('label[for=ppcp-credit-card-gateway-card-number]').click();
}
)
return;
}
const wrapperElement = document.querySelector(wrapper);
wrapperElement.parentNode.removeChild(wrapperElement);
}
disableFields() {
if (this.currentHostedFieldsInstance) {
this.currentHostedFieldsInstance.setAttribute({
field: 'number',
attribute: 'disabled'
})
this.currentHostedFieldsInstance.setAttribute({
field: 'cvv',
attribute: 'disabled'
})
this.currentHostedFieldsInstance.setAttribute({
field: 'expirationDate',
attribute: 'disabled'
})
}
}
enableFields() {
if (this.currentHostedFieldsInstance) {
this.currentHostedFieldsInstance.removeAttribute({
field: 'number',
attribute: 'disabled'
})
this.currentHostedFieldsInstance.removeAttribute({
field: 'cvv',
attribute: 'disabled'
})
this.currentHostedFieldsInstance.removeAttribute({
field: 'expirationDate',
attribute: 'disabled'
})
}
}
_submit(contextConfig) {
this.spinner.block();
this.errorHandler.clear();
if (this.formValid && this.cardValid) {
const save_card = this.defaultConfig.can_save_vault_token ? true : false;
let vault = document.getElementById('ppcp-credit-card-vault') ?
document.getElementById('ppcp-credit-card-vault').checked : save_card;
if (this.defaultConfig.enforce_vault) {
vault = true;
}
const contingency = this.defaultConfig.hosted_fields.contingency;
const hostedFieldsData = {
vault: vault
};
if (contingency !== 'NO_3D_SECURE') {
hostedFieldsData.contingencies = [contingency];
}
if (this.defaultConfig.payer) {
hostedFieldsData.cardholderName = this.defaultConfig.payer.name.given_name + ' ' + this.defaultConfig.payer.name.surname;
}
if (!hostedFieldsData.cardholderName) {
const firstName = document.getElementById('billing_first_name') ? document.getElementById('billing_first_name').value : '';
const lastName = document.getElementById('billing_last_name') ? document.getElementById('billing_last_name').value : '';
hostedFieldsData.cardholderName = firstName + ' ' + lastName;
}
this.currentHostedFieldsInstance.submit(hostedFieldsData).then((payload) => {
payload.orderID = payload.orderId;
this.spinner.unblock();
return contextConfig.onApprove(payload);
}).catch(err => {
this.spinner.unblock();
this.errorHandler.clear();
if (err.data?.details?.length) {
this.errorHandler.message(err.data.details.map(d => `${d.issue} ${d.description}`).join('<br/>'));
} else if (err.details?.length) {
this.errorHandler.message(err.details.map(d => `${d.issue} ${d.description}`).join('<br/>'));
} else if (err.data?.errors?.length > 0) {
this.errorHandler.messages(err.data.errors);
} else if (err.data?.message) {
this.errorHandler.message(err.data.message);
} else if (err.message) {
this.errorHandler.message(err.message);
} else {
this.errorHandler.genericError();
}
});
} else {
this.spinner.unblock();
let message = this.defaultConfig.labels.error.generic;
if (this.emptyFields.size > 0) {
message = this.defaultConfig.hosted_fields.labels.fields_empty;
} else if (!this.cardValid) {
message = this.defaultConfig.hosted_fields.labels.card_not_supported;
} else if (!this.formValid) {
message = this.defaultConfig.hosted_fields.labels.fields_not_valid;
}
this.errorHandler.message(message);
}
}
_cardNumberFiledCLassNameByCardType(cardType) {
return cardType === 'american-express' ? 'amex' : cardType.replace('-', '');
}
_recreateElementClassAttribute(element, newClassName) {
element.removeAttribute('class')
element.setAttribute('class', newClassName);
}
}
export default HostedFieldsRenderer;

View file

@ -0,0 +1,17 @@
{
"name": "woocommerce/ppcp-card-fields",
"type": "dhii-mod",
"description": "Advanced Checkout Card Fields module",
"license": "GPL-2.0",
"require": {
"php": "^7.2 | ^8.0",
"dhii/module-interface": "^0.3.0-alpha1"
},
"autoload": {
"psr-4": {
"WooCommerce\\PayPalCommerce\\CardFields\\": "src"
}
},
"minimum-stability": "dev",
"prefer-stable": true
}

View file

@ -0,0 +1,12 @@
<?php
/**
* The Card Fields module extensions.
*
* @package WooCommerce\PayPalCommerce\CardFields
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\CardFields;
return array();

View file

@ -0,0 +1,16 @@
<?php
/**
* The Card Fields module.
*
* @package WooCommerce\PayPalCommerce\CardFields
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\CardFields;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
return static function (): ModuleInterface {
return new CardFieldsModule();
};

View file

@ -0,0 +1,44 @@
<?php
/**
* The Card Fields module services.
*
* @package WooCommerce\PayPalCommerce\CardFields
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\CardFields;
use WooCommerce\PayPalCommerce\CardFields\Helper\CardFieldsApplies;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
return array(
'card-fields.eligible' => static function ( ContainerInterface $container ): bool {
$save_payment_methods_applies = $container->get( 'card-fields.helpers.save-payment-methods-applies' );
assert( $save_payment_methods_applies instanceof CardFieldsApplies );
return $save_payment_methods_applies->for_country_currency();
},
'card-fields.helpers.save-payment-methods-applies' => static function ( ContainerInterface $container ) : CardFieldsApplies {
return new CardFieldsApplies(
$container->get( 'card-fields.supported-country-currency-matrix' ),
$container->get( 'api.shop.currency' ),
$container->get( 'api.shop.country' )
);
},
'card-fields.supported-country-currency-matrix' => static function ( ContainerInterface $container ) : array {
return apply_filters(
'woocommerce_paypal_payments_card_fields_supported_country_currency_matrix',
array(
'US' => array(
'AUD',
'CAD',
'EUR',
'GBP',
'JPY',
'USD',
),
)
);
},
);

View file

@ -0,0 +1,115 @@
<?php
/**
* The Card Fields module.
*
* @package WooCommerce\PayPalCommerce\CardFields
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\CardFields;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
/**
* Class CardFieldsModule
*/
class CardFieldsModule implements ModuleInterface {
/**
* {@inheritDoc}
*/
public function setup(): ServiceProviderInterface {
return new ServiceProvider(
require __DIR__ . '/../services.php',
require __DIR__ . '/../extensions.php'
);
}
/**
* {@inheritDoc}
*/
public function run( ContainerInterface $c ): void {
if ( ! $c->get( 'card-fields.eligible' ) ) {
return;
}
/**
* Param types removed to avoid third-party issues.
*
* @psalm-suppress MissingClosureParamType
*/
add_filter(
'woocommerce_paypal_payments_sdk_components_hook',
function( $components ) {
if ( in_array( 'hosted-fields', $components, true ) ) {
$key = array_search( 'hosted-fields', $components, true );
if ( $key !== false ) {
unset( $components[ $key ] );
}
}
$components[] = 'card-fields';
return $components;
}
);
add_filter(
'woocommerce_credit_card_form_fields',
/**
* Return/Param types removed to avoid third-party issues.
*
* @psalm-suppress MissingClosureReturnType
* @psalm-suppress MissingClosureParamType
*/
function( $default_fields, $id ) {
if ( CreditCardGateway::ID === $id && apply_filters( 'woocommerce_paypal_payments_enable_cardholder_name_field', false ) ) {
$default_fields['card-name-field'] = '<p class="form-row form-row-wide">
<label for="ppcp-credit-card-gateway-card-name">' . esc_attr__( 'Cardholder Name', 'woocommerce-paypal-payments' ) . '</label>
<input id="ppcp-credit-card-gateway-card-name" class="input-text wc-credit-card-form-card-expiry" type="text" placeholder="' . esc_attr__( 'Cardholder Name (optional)', 'woocommerce-paypal-payments' ) . '" name="ppcp-credit-card-gateway-card-name">
</p>';
// Moves new item to first position.
$new_field = $default_fields['card-name-field'];
unset( $default_fields['card-name-field'] );
array_unshift( $default_fields, $new_field );
}
return $default_fields;
},
10,
2
);
add_filter(
'ppcp_create_order_request_body_data',
function( array $data ) use ( $c ): array {
$settings = $c->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
if (
$settings->has( '3d_secure_contingency' )
&& (
$settings->get( '3d_secure_contingency' ) === 'SCA_ALWAYS'
|| $settings->get( '3d_secure_contingency' ) === 'SCA_WHEN_REQUIRED'
)
) {
$data['payment_source']['card'] = array(
'attributes' => array(
'verification' => array(
'method' => $settings->get( '3d_secure_contingency' ),
),
),
);
}
return $data;
}
);
}
}

View file

@ -0,0 +1,66 @@
<?php
/**
* Service for checking whether Card Fields can be used in the current country and the current currency.
*
* @package WooCommerce\PayPalCommerce\CardFields\Helper
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\CardFields\Helper;
/**
* Class CardFieldsApplies
*/
class CardFieldsApplies {
/**
* The matrix which countries and currency combinations can be used.
*
* @var array
*/
private $allowed_country_currency_matrix;
/**
* 3-letter currency code of the shop.
*
* @var string
*/
private $currency;
/**
* 2-letter country code of the shop.
*
* @var string
*/
private $country;
/**
* CardFieldsApplies constructor.
*
* @param array $allowed_country_currency_matrix The matrix which countries and currency combinations can be used.
* @param string $currency 3-letter currency code of the shop.
* @param string $country 2-letter country code of the shop.
*/
public function __construct(
array $allowed_country_currency_matrix,
string $currency,
string $country
) {
$this->allowed_country_currency_matrix = $allowed_country_currency_matrix;
$this->currency = $currency;
$this->country = $country;
}
/**
* Returns whether Card Fields can be used in the current country and the current currency.
*
* @return bool
*/
public function for_country_currency(): bool {
if ( ! in_array( $this->country, array_keys( $this->allowed_country_currency_matrix ), true ) ) {
return false;
}
return in_array( $this->currency, $this->allowed_country_currency_matrix[ $this->country ], true );
}
}

View file

@ -273,7 +273,12 @@ class CompatModule implements ModuleInterface {
add_action(
'init',
function() {
if ( $this->is_elementor_pro_active() || $this->is_divi_theme_active() ) {
if (
$this->is_block_theme_active()
|| $this->is_elementor_pro_active()
|| $this->is_divi_theme_active()
|| $this->is_divi_child_theme_active()
) {
add_filter(
'woocommerce_paypal_payments_single_product_renderer_hook',
function(): string {
@ -286,6 +291,15 @@ class CompatModule implements ModuleInterface {
);
}
/**
* Checks whether the current theme is a blocks theme.
*
* @return bool
*/
protected function is_block_theme_active(): bool {
return function_exists( 'wp_is_block_theme' ) && wp_is_block_theme();
}
/**
* Checks whether the Elementor Pro plugins (allowing integrations with WC) is active.
*
@ -304,4 +318,15 @@ class CompatModule implements ModuleInterface {
$theme = wp_get_theme();
return $theme->get( 'Name' ) === 'Divi';
}
/**
* Checks whether a Divi child theme is currently used.
*
* @return bool
*/
protected function is_divi_child_theme_active(): bool {
$theme = wp_get_theme();
$parent = $theme->parent();
return ( $parent && $parent->get( 'Name' ) === 'Divi' );
}
}

View file

@ -21,16 +21,16 @@ use WooCommerce\PayPalCommerce\OrderTracking\Assets\OrderEditPageAssets;
use WooCommerce\PayPalCommerce\OrderTracking\Endpoint\OrderTrackingEndpoint;
return array(
'order-tracking.assets' => function( ContainerInterface $container ) : OrderEditPageAssets {
'order-tracking.assets' => function( ContainerInterface $container ) : OrderEditPageAssets {
return new OrderEditPageAssets(
$container->get( 'order-tracking.module.url' ),
$container->get( 'ppcp.asset-version' )
);
},
'order-tracking.shipment.factory' => static function ( ContainerInterface $container ) : ShipmentFactoryInterface {
'order-tracking.shipment.factory' => static function ( ContainerInterface $container ) : ShipmentFactoryInterface {
return new ShipmentFactory();
},
'order-tracking.endpoint.controller' => static function ( ContainerInterface $container ) : OrderTrackingEndpoint {
'order-tracking.endpoint.controller' => static function ( ContainerInterface $container ) : OrderTrackingEndpoint {
return new OrderTrackingEndpoint(
$container->get( 'api.host' ),
$container->get( 'api.bearer' ),
@ -38,10 +38,10 @@ return array(
$container->get( 'button.request-data' ),
$container->get( 'order-tracking.shipment.factory' ),
$container->get( 'order-tracking.allowed-shipping-statuses' ),
$container->get( 'order-tracking.is-merchant-country-us' )
$container->get( 'order-tracking.should-use-second-version-of-api' )
);
},
'order-tracking.module.url' => static function ( ContainerInterface $container ): string {
'order-tracking.module.url' => static function ( ContainerInterface $container ): string {
/**
* The path cannot be false.
*
@ -52,15 +52,15 @@ return array(
dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php'
);
},
'order-tracking.meta-box.renderer' => static function ( ContainerInterface $container ): MetaBoxRenderer {
'order-tracking.meta-box.renderer' => static function ( ContainerInterface $container ): MetaBoxRenderer {
return new MetaBoxRenderer(
$container->get( 'order-tracking.allowed-shipping-statuses' ),
$container->get( 'order-tracking.available-carriers' ),
$container->get( 'order-tracking.endpoint.controller' ),
$container->get( 'order-tracking.is-merchant-country-us' )
$container->get( 'order-tracking.should-use-second-version-of-api' )
);
},
'order-tracking.allowed-shipping-statuses' => static function ( ContainerInterface $container ): array {
'order-tracking.allowed-shipping-statuses' => static function ( ContainerInterface $container ): array {
return (array) apply_filters(
'woocommerce_paypal_payments_tracking_statuses',
array(
@ -71,10 +71,10 @@ return array(
)
);
},
'order-tracking.allowed-carriers' => static function ( ContainerInterface $container ): array {
'order-tracking.allowed-carriers' => static function ( ContainerInterface $container ): array {
return require __DIR__ . '/carriers.php';
},
'order-tracking.available-carriers' => static function ( ContainerInterface $container ): array {
'order-tracking.available-carriers' => static function ( ContainerInterface $container ): array {
$api_shop_country = $container->get( 'api.shop.country' );
$allowed_carriers = $container->get( 'order-tracking.allowed-carriers' );
$selected_country_carriers = $allowed_carriers[ $api_shop_country ] ?? array();
@ -90,10 +90,26 @@ return array(
),
);
},
'order-tracking.is-merchant-country-us' => static function ( ContainerInterface $container ): bool {
return $container->get( 'api.shop.country' ) === 'US';
/**
* The list of country codes, for which the 2nd version of PayPal tracking API is supported.
*/
'order-tracking.second-version-api-supported-countries' => static function (): array {
/**
* Returns codes of countries, for which the 2nd version of PayPal tracking API is supported.
*/
return apply_filters(
'woocommerce_paypal_payments_supported_country_codes_for_second_version_of_tracking_api',
array( 'US', 'AU', 'CA', 'FR', 'DE', 'IT', 'ES' )
);
},
'order-tracking.integrations' => static function ( ContainerInterface $container ): array {
'order-tracking.should-use-second-version-of-api' => static function ( ContainerInterface $container ): bool {
$supported_county_codes = $container->get( 'order-tracking.second-version-api-supported-countries' );
$selected_country_code = $container->get( 'api.shop.country' );
return in_array( $selected_country_code, $supported_county_codes, true );
},
'order-tracking.integrations' => static function ( ContainerInterface $container ): array {
$shipment_factory = $container->get( 'order-tracking.shipment.factory' );
$logger = $container->get( 'woocommerce.logger.woocommerce' );
$endpoint = $container->get( 'order-tracking.endpoint.controller' );