Merge branch 'trunk' into PCP-3171-several-issues-on-backwpup-com

This commit is contained in:
Emili Castells Guasch 2024-06-14 12:43:15 +02:00
commit 39b6f8a1aa
67 changed files with 1716 additions and 490 deletions

View file

@ -1,6 +1,20 @@
*** Changelog ***
= 2.7.1 - xxxx-xx-xx =
= 2.8.0 - 2024-06-11 =
* Fix - Calculate totals after adding shipping to include taxes #2296
* Fix - Package tracking integration throws error in 2.7.1 #2289
* Fix - Make PayPal Subscription products unique in cart #2265
* Fix - PayPal declares subscription support when merchant not enabled for Reference Transactions #2282
* Fix - Google Pay and Apple Pay Settings button from Connection tab have wrong links #2273
* Fix - Smart Buttons in Block Checkout not respecting the location setting (2830) #2278
* Fix - Disable Pay Upon Invoice if billing/shipping country not set #2281
* Fix - Critical error on pay for order page when we try to pay with ACDC gateway #2321
* Enhancement - Enable shipping callback for WC subscriptions #2259
* Enhancement - Disable the shipping callback for "venmo" when vaulting is active #2269
* Enhancement - Improve "Could not retrieve order" error message #2271
* Enhancement - Add block Checkout compatibility to Advanced Card Processing #2246
= 2.7.1 - 2024-05-28 =
* Fix - Ensure package tracking data is sent to original PayPal transaction #2180
* Fix - Set the 'Woo_PPCP' as a default value for data-partner-attribution-id #2188
* Fix - Allow PUI Gateway for refund processor #2192

View file

@ -18,8 +18,8 @@ return function ( string $root_dir ): iterable {
( require "$modules_dir/woocommerce-logging/module.php" )(),
( require "$modules_dir/ppcp-admin-notices/module.php" )(),
( require "$modules_dir/ppcp-api-client/module.php" )(),
( require "$modules_dir/ppcp-button/module.php" )(),
( require "$modules_dir/ppcp-compat/module.php" )(),
( require "$modules_dir/ppcp-button/module.php" )(),
( require "$modules_dir/ppcp-onboarding/module.php" )(),
( require "$modules_dir/ppcp-session/module.php" )(),
( require "$modules_dir/ppcp-status-report/module.php" )(),
@ -73,12 +73,12 @@ return function ( string $root_dir ): iterable {
$modules[] = ( require "$modules_dir/ppcp-paylater-block/module.php" )();
}
if ( PayLaterWCBlocksModule::is_module_loading_required() ) {
$modules[] = ( require "$modules_dir/ppcp-paylater-wc-blocks/module.php" )();
}
if ( PayLaterConfiguratorModule::is_enabled() ) {
$modules[] = ( require "$modules_dir/ppcp-paylater-configurator/module.php" )();
if ( PayLaterWCBlocksModule::is_module_loading_required() ) {
$modules[] = ( require "$modules_dir/ppcp-paylater-wc-blocks/module.php" )();
}
}
if ( apply_filters(

View file

@ -73,6 +73,7 @@ class SdkClientToken {
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$domain = wp_unslash( $_SERVER['HTTP_HOST'] ?? '' );
$domain = preg_replace( '/^www\./', '', $domain );
$url = trailingslashit( $this->host ) . 'v1/oauth2/token?grant_type=client_credentials&response_type=client_token&intent=sdk_init&domains[]=' . $domain;

View file

@ -50,9 +50,7 @@ class ShippingOptionFactory {
$cart->calculate_shipping();
$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods', array() );
if ( ! is_array( $chosen_shipping_methods ) ) {
$chosen_shipping_methods = array();
}
$chosen_shipping_method = $chosen_shipping_methods[0] ?? false;
$packages = WC()->shipping()->get_packages();
$options = array();
@ -62,11 +60,10 @@ class ShippingOptionFactory {
if ( ! $rate instanceof \WC_Shipping_Rate ) {
continue;
}
$options[] = new ShippingOption(
$rate->get_id(),
$rate->get_label(),
in_array( $rate->get_id(), $chosen_shipping_methods, true ),
$rate->get_id() === $chosen_shipping_method,
new Money(
(float) $rate->get_cost(),
get_woocommerce_currency()

View file

@ -964,7 +964,7 @@ return array(
: $container->get( 'applepay.enable-url-sandbox' );
$button_url = $enabled
? admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway#field-alternative_payment_methods' )
? admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway&ppcp-tab=ppcp-credit-card-gateway#ppcp-applepay_button_enabled' )
: $enable_url;
return sprintf(

View file

@ -148,14 +148,8 @@ return array(
'axo_privacy' => array(
'title' => __( 'Privacy', 'woocommerce-paypal-payments' ),
'type' => 'select',
'label' => __(
'This setting will control whether Fastlane branding is shown by email field.
<p class="description">PayPal powers this accelerated checkout solution from Fastlane. Since you\'ll share consumers\' email addresses with PayPal, please consult your legal advisors on the apropriate privacy setting for your business.</p>',
'woocommerce-paypal-payments'
),
'desc_tip' => true,
'description' => __(
'This setting will control whether Fastlane branding is shown by email field.',
'PayPal powers this accelerated checkout solution from Fastlane. Since you\'ll share consumers\' email address with PayPal, please consult your legal advisors on the appropriate privacy setting for your business.',
'woocommerce-paypal-payments'
),
'classes' => array( 'ppcp-field-indent' ),
@ -168,12 +162,14 @@ return array(
'requirements' => array( 'axo' ),
),
'axo_name_on_card' => array(
'title' => __( 'Display Name on Card', 'woocommerce-paypal-payments' ),
'type' => 'checkbox',
'title' => __( 'Cardholder Name', 'woocommerce-paypal-payments' ),
'type' => 'select',
'default' => 'yes',
'options' => PropertiesDictionary::cardholder_name_options(),
'classes' => array( 'ppcp-field-indent' ),
'class' => array(),
'label' => __( 'Enable this to display the "Name on Card" field for new Fastlane buyers.', 'woocommerce-paypal-payments' ),
'input_class' => array( 'wc-enhanced-select' ),
'description' => __( 'This setting will control whether or not the cardholder name is displayed in the card field\'s UI.', 'woocommerce-paypal-payments' ),
'screens' => array( State::STATE_ONBOARDED ),
'gateway' => array( 'dcc', 'axo' ),
'requirements' => array( 'axo' ),
@ -196,7 +192,7 @@ return array(
sprintf(
// translators: %1$s and %2$s is a link tag.
__(
'Leave the default styling, or customize how Fastlane looks on your website. %1$sSee PayPal\'s developer docs%2$s for info',
'Leave the default styling, or customize how Fastlane looks on your website. Styles that don\'t meet accessibility guidelines will revert to the defaults. See %1$sPayPal\'s developer docs%2$s for info.',
'woocommerce-paypal-payments'
),
'<a href="https://www.paypal.com/us/fastlane" target="_blank">',
@ -236,18 +232,6 @@ return array(
'requirements' => array( 'axo' ),
'gateway' => array( 'dcc', 'axo' ),
),
'axo_style_root_primary_color' => array(
'title' => __( 'Primary Color', 'woocommerce-paypal-payments' ),
'type' => 'text',
'placeholder' => '#0057F',
'classes' => array( 'ppcp-field-indent' ),
'default' => '',
'screens' => array(
State::STATE_ONBOARDED,
),
'requirements' => array( 'axo' ),
'gateway' => array( 'dcc', 'axo' ),
),
'axo_style_root_error_color' => array(
'title' => __( 'Error Color', 'woocommerce-paypal-payments' ),
'type' => 'text',
@ -308,6 +292,18 @@ return array(
'requirements' => array( 'axo' ),
'gateway' => array( 'dcc', 'axo' ),
),
'axo_style_root_primary_color' => array(
'title' => __( 'Primary Color', 'woocommerce-paypal-payments' ),
'type' => 'text',
'placeholder' => '#0057FF',
'classes' => array( 'ppcp-field-indent' ),
'default' => '',
'screens' => array(
State::STATE_ONBOARDED,
),
'requirements' => array( 'axo' ),
'gateway' => array( 'dcc', 'axo' ),
),
'axo_style_input_heading' => array(
'heading' => __( 'Input Settings', 'woocommerce-paypal-payments' ),
'type' => 'ppcp-heading',

View file

@ -1,6 +1,48 @@
.ppcp-axo-watermark-container {
max-width: 200px;
margin-top: 10px;
position: relative;
&.loader:before {
height: 12px;
width: 12px;
margin-left: -6px;
margin-top: -6px;
left: 12px;
}
}
#ppcp-axo-billing-email-field-wrapper {
display: flex;
gap: 0.5rem;
}
#ppcp-axo-billing-email-submit-button {
margin-top: 0;
position: relative;
transition: opacity 0.3s ease;
flex: 0 1 auto;
align-self: flex-start;
.loader:before {
display: inline;
height: 12px;
width: 12px;
margin-left: -6px;
margin-top: -6px;
left: auto;
right: auto;
}
}
.ppcp-axo-billing-email-submit-button {
&-hidden {
opacity: 0;
}
&-loaded:not([disabled]) {
opacity: 1;
}
}
.ppcp-axo-payment-container {
@ -28,6 +70,7 @@
.ppcp-axo-customer-details {
margin-bottom: 40px;
position: relative;
}
.axo-checkout-header-section {
@ -44,6 +87,31 @@
padding: 0.6em 1em;
}
.ppcp-axo-watermark-loading {
min-height: 12px;
}
.ppcp-axo-overlay,
.ppcp-axo-watermark-loading:after {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 999;
content: '';
}
.ppcp-axo-loading .col-1 {
position: relative;
opacity: 0.9;
transition: opacity 0.5s ease;
}
#payment .payment_methods li label[for="payment_method_ppcp-axo-gateway"] {
img {
float: none;
@ -54,3 +122,22 @@
max-height: 25px;
}
}
.ppcp-axo-customer-details #billing_email_field .woocommerce-input-wrapper {
flex: 1 1 auto;
}
@media screen and (max-width: 719px) {
#ppcp-axo-billing-email {
&-field-wrapper {
flex-direction: column;
}
&-submit-button {
align-self: auto;
}
}
}

View file

@ -24,7 +24,8 @@ class AxoManager {
active: false,
validEmail: false,
hasProfile: false,
useEmailWidget: this.useEmailWidget()
useEmailWidget: this.useEmailWidget(),
hasCard: false,
};
this.data = {
@ -59,7 +60,6 @@ class AxoManager {
}
document.axoDebugObject = () => {
console.log(this);
return this;
}
@ -156,6 +156,7 @@ class AxoManager {
this.el.showGatewaySelectionLink.on('click', async () => {
this.hideGatewaySelection = false;
this.$('.wc_payment_methods label').show();
this.$('.wc_payment_methods input').show();
this.cardView.refresh();
});
@ -164,18 +165,20 @@ class AxoManager {
this.$('form.woocommerce-checkout input').on('keydown', async (ev) => {
if(ev.key === 'Enter' && getCurrentPaymentMethod() === 'ppcp-axo-gateway' ) {
ev.preventDefault();
log('Enter key attempt');
log('emailInput', this.emailInput.value);
log('this.lastEmailCheckedIdentity', this.lastEmailCheckedIdentity);
log(`Enter key attempt - emailInput: ${this.emailInput.value}`);
log(`this.lastEmailCheckedIdentity: ${this.lastEmailCheckedIdentity}`);
this.validateEmail(this.el.fieldBillingEmail.selector);
if (this.emailInput && this.lastEmailCheckedIdentity !== this.emailInput.value) {
await this.onChangeEmail();
}
}
});
this.reEnableEmailInput();
// Clear last email checked identity when email field is focused.
this.$('#billing_email_field input').on('focus', (ev) => {
log('Clear the last email checked:', this.lastEmailCheckedIdentity);
log(`Clear the last email checked: ${this.lastEmailCheckedIdentity}`);
this.lastEmailCheckedIdentity = '';
});
@ -212,15 +215,17 @@ class AxoManager {
this.status.hasProfile
);
log('Scenario', scenario);
log(`Scenario: ${JSON.stringify(scenario)}`);
// Reset some elements to a default status.
this.el.watermarkContainer.hide();
if (scenario.defaultSubmitButton) {
this.el.defaultSubmitButton.show();
this.el.billingEmailSubmitButton.hide();
} else {
this.el.defaultSubmitButton.hide();
this.el.billingEmailSubmitButton.show();
}
if (scenario.defaultEmailField) {
@ -231,6 +236,7 @@ class AxoManager {
if (scenario.defaultFormFields) {
this.el.customerDetails.show();
this.toggleLoaderAndOverlay(this.el.customerDetails, 'loader', 'ppcp-axo-overlay');
} else {
this.el.customerDetails.hide();
}
@ -245,10 +251,9 @@ class AxoManager {
this.el.watermarkContainer.show();
// Move watermark to after email.
this.$(this.el.fieldBillingEmail.selector).append(
this.$(this.el.watermarkContainer.selector)
document.querySelector('#billing_email_field .woocommerce-input-wrapper').append(
document.querySelector(this.el.watermarkContainer.selector)
);
} else {
this.el.emailWidgetContainer.hide();
if (!scenario.defaultEmailField) {
@ -257,12 +262,14 @@ class AxoManager {
}
if (scenario.axoProfileViews) {
this.el.billingAddressContainer.hide();
this.shippingView.activate();
this.billingView.activate();
this.cardView.activate();
if (this.status.hasCard) {
this.billingView.activate();
}
// Move watermark to after shipping.
this.$(this.el.shippingAddressContainer.selector).after(
this.$(this.el.watermarkContainer.selector)
@ -278,6 +285,8 @@ class AxoManager {
if (scenario.axoPaymentContainer) {
this.el.paymentContainer.show();
this.el.gatewayDescription.hide();
document.querySelector(this.el.billingEmailSubmitButton.selector).setAttribute('disabled', 'disabled');
} else {
this.el.paymentContainer.hide();
}
@ -372,7 +381,7 @@ class AxoManager {
setStatus(key, value) {
this.status[key] = value;
log('Status updated', JSON.parse(JSON.stringify(this.status)));
log(`Status updated: ${JSON.stringify(this.status)}`);
document.dispatchEvent(new CustomEvent("axo_status_updated", {detail: this.status}));
@ -384,9 +393,8 @@ class AxoManager {
this.initFastlane();
this.setStatus('active', true);
log('Attempt on activation');
log('emailInput', this.emailInput.value);
log('this.lastEmailCheckedIdentity', this.lastEmailCheckedIdentity);
log(`Attempt on activation - emailInput: ${this.emailInput.value}`);
log(`this.lastEmailCheckedIdentity: ${this.lastEmailCheckedIdentity}`);
if (this.emailInput && this.lastEmailCheckedIdentity !== this.emailInput.value) {
this.onChangeEmail();
}
@ -424,10 +432,18 @@ class AxoManager {
`);
}
// billingEmailFieldWrapper
const befw = this.el.billingEmailFieldWrapper;
if (!document.querySelector(befw.selector)) {
document.querySelector('#billing_email_field .woocommerce-input-wrapper').insertAdjacentHTML('afterend', `
<div id="${befw.id}"></div>
`);
}
// Watermark container
const wc = this.el.watermarkContainer;
if (!document.querySelector(wc.selector)) {
this.emailInput.insertAdjacentHTML('afterend', `
document.querySelector(befw.selector).insertAdjacentHTML('beforeend', `
<div class="${wc.className}" id="${wc.id}"></div>
`);
}
@ -457,10 +473,10 @@ class AxoManager {
}
} else {
// Move email to the AXO container.
let emailRow = document.querySelector(this.el.fieldBillingEmail.selector);
wrapperElement.prepend(emailRow);
document.querySelector(this.el.billingEmailFieldWrapper.selector).prepend(document.querySelector('#billing_email_field .woocommerce-input-wrapper'));
}
}
@ -471,7 +487,8 @@ class AxoManager {
this.initialized = true;
await this.connect();
this.renderWatermark();
await this.renderWatermark();
this.renderEmailSubmitButton();
this.watchEmail();
}
@ -496,6 +513,26 @@ class AxoManager {
(await this.fastlane.FastlaneWatermarkComponent({
includeAdditionalInfo
})).render(this.el.watermarkContainer.selector);
this.toggleWatermarkLoading(this.el.watermarkContainer, 'ppcp-axo-watermark-loading', 'loader');
}
renderEmailSubmitButton() {
const billingEmailSubmitButton = this.el.billingEmailSubmitButton;
const billingEmailSubmitButtonSpinner = this.el.billingEmailSubmitButtonSpinner;
if (!document.querySelector(billingEmailSubmitButton.selector)) {
document.querySelector(this.el.billingEmailFieldWrapper.selector).insertAdjacentHTML('beforeend', `
<button type="button" id="${billingEmailSubmitButton.id}" class="${billingEmailSubmitButton.className}">
${this.axoConfig.billing_email_button_text}
<span id="${billingEmailSubmitButtonSpinner.id}"></span>
</button>
`);
document.querySelector(this.el.billingEmailSubmitButton.selector).offsetHeight;
document.querySelector(this.el.billingEmailSubmitButton.selector).classList.remove('ppcp-axo-billing-email-submit-button-hidden');
document.querySelector(this.el.billingEmailSubmitButton.selector).classList.add('ppcp-axo-billing-email-submit-button-loaded');
}
}
watchEmail() {
@ -506,17 +543,16 @@ class AxoManager {
} else {
this.emailInput.addEventListener('change', async ()=> {
log('Change event attempt');
log('emailInput', this.emailInput.value);
log('this.lastEmailCheckedIdentity', this.lastEmailCheckedIdentity);
log(`Change event attempt - emailInput: ${this.emailInput.value}`);
log(`this.lastEmailCheckedIdentity: ${this.lastEmailCheckedIdentity}`);
if (this.emailInput && this.lastEmailCheckedIdentity !== this.emailInput.value) {
this.validateEmail(this.el.fieldBillingEmail.selector);
this.onChangeEmail();
}
});
log('Last, this.emailInput.value attempt');
log('emailInput', this.emailInput.value);
log('this.lastEmailCheckedIdentity', this.lastEmailCheckedIdentity);
log(`Last, this.emailInput.value attempt - emailInput: ${this.emailInput.value}`);
log(`this.lastEmailCheckedIdentity: ${this.lastEmailCheckedIdentity}`);
if (this.emailInput.value) {
this.onChangeEmail();
}
@ -536,7 +572,7 @@ class AxoManager {
return;
}
log('Email changed: ' + (this.emailInput ? this.emailInput.value : '<empty>'));
log(`Email changed: ${this.emailInput ? this.emailInput.value : '<empty>'}`);
this.$(this.el.paymentContainer.selector + '-detail').html('');
this.$(this.el.paymentContainer.selector + '-form').html('');
@ -548,7 +584,7 @@ class AxoManager {
this.lastEmailCheckedIdentity = this.emailInput.value;
if (!this.emailInput.value || !this.emailInput.checkValidity()) {
if (!this.emailInput.value || !this.emailInput.checkValidity() || !this.validateEmailFormat(this.emailInput.value)) {
log('The email address is not valid.');
return;
}
@ -565,12 +601,19 @@ class AxoManager {
page_type: 'checkout'
});
this.disableGatewaySelection();
this.spinnerToggleLoaderAndOverlay(this.el.billingEmailSubmitButtonSpinner, 'loader', 'ppcp-axo-overlay');
await this.lookupCustomerByEmail();
this.spinnerToggleLoaderAndOverlay(this.el.billingEmailSubmitButtonSpinner, 'loader', 'ppcp-axo-overlay');
this.enableGatewaySelection();
}
async lookupCustomerByEmail() {
const lookupResponse = await this.fastlane.identity.lookupCustomerByEmail(this.emailInput.value);
log(`lookupCustomerByEmail: ${JSON.stringify(lookupResponse)}`);
if (lookupResponse.customerContextId) {
// Email is associated with a Connect profile or a PayPal member.
// Authenticate the customer to get access to their profile.
@ -578,18 +621,24 @@ class AxoManager {
const authResponse = await this.fastlane.identity.triggerAuthenticationFlow(lookupResponse.customerContextId);
log('AuthResponse', authResponse);
log(`AuthResponse - triggerAuthenticationFlow: ${JSON.stringify(authResponse)}`);
if (authResponse.authenticationState === 'succeeded') {
log(JSON.stringify(authResponse));
const shippingData = authResponse.profileData.shippingAddress;
if(shippingData) {
if (shippingData) {
this.setShipping(shippingData);
}
if (authResponse.profileData.card) {
this.setStatus('hasCard', true);
} else {
this.cardComponent = (await this.fastlane.FastlaneCardComponent(
this.cardComponentData()
)).render(this.el.paymentContainer.selector + '-form');
}
const cardBillingAddress = authResponse.profileData?.card?.paymentSource?.card?.billingAddress;
if(cardBillingAddress) {
if (cardBillingAddress) {
this.setCard(authResponse.profileData.card);
const billingData = {
@ -608,6 +657,7 @@ class AxoManager {
this.hideGatewaySelection = true;
this.$('.wc_payment_methods label').hide();
this.$('.wc_payment_methods input').hide();
await this.renderWatermark(false);
@ -644,6 +694,14 @@ class AxoManager {
}
}
disableGatewaySelection() {
this.$('.wc_payment_methods input').prop('disabled', true);
}
enableGatewaySelection() {
this.$('.wc_payment_methods input').prop('disabled', false);
}
clearData() {
this.data = {
email: null,
@ -672,7 +730,7 @@ class AxoManager {
// TODO: validate data.
if (this.data.card) { // Ryan flow
log('Ryan flow.');
log('Starting Ryan flow.');
this.$('#ship-to-different-address-checkbox').prop('checked', 'checked');
@ -683,20 +741,23 @@ class AxoManager {
this.ensureBillingPhoneNumber(data);
log(`Ryan flow - submitted nonce: ${this.data.card.id}` )
this.submit(this.data.card.id, data);
} else { // Gary flow
log('Gary flow.');
log('Starting Gary flow.');
try {
this.cardComponent.getPaymentToken(
this.tokenizeData()
).then((response) => {
log(`Gary flow - submitted nonce: ${response.id}` )
this.submit(response.id);
});
} catch (e) {
log('Error tokenizing.');
alert('Error tokenizing data.');
log(`Error tokenizing data. ${e.message}`, 'error');
}
}
}
@ -714,7 +775,7 @@ class AxoManager {
tokenizeData() {
return {
name: {
cardholderName: {
fullName: this.billingView.fullName()
},
billingAddress: {
@ -776,7 +837,9 @@ class AxoManager {
scrollTop: $notices.offset().top
}, 500);
}
console.error('Failure:', responseData);
log(`Error sending checkout form. ${responseData}`, 'error');
this.hideLoading();
return;
}
@ -785,7 +848,8 @@ class AxoManager {
}
})
.catch(error => {
console.error('Error:', error);
log(`Error sending checkout form. ${error.message}`, 'error');
this.hideLoading();
});
@ -840,6 +904,69 @@ class AxoManager {
data.billing_phone = phone;
}
}
toggleLoaderAndOverlay(element, loaderClass, overlayClass) {
const loader = document.querySelector(`${element.selector} .${loaderClass}`);
const overlay = document.querySelector(`${element.selector} .${overlayClass}`);
if (loader) {
loader.classList.toggle(loaderClass);
}
if (overlay) {
overlay.classList.toggle(overlayClass);
}
}
spinnerToggleLoaderAndOverlay(element, loaderClass, overlayClass) {
const spinner = document.querySelector(`${element.selector}`);
if (spinner) {
spinner.classList.toggle(loaderClass);
spinner.classList.toggle(overlayClass);
}
}
toggleWatermarkLoading(container, loadingClass, loaderClass) {
const watermarkLoading = document.querySelector(`${container.selector}.${loadingClass}`);
const watermarkLoader = document.querySelector(`${container.selector}.${loaderClass}`);
if (watermarkLoading) {
watermarkLoading.classList.toggle(loadingClass);
}
if (watermarkLoader) {
watermarkLoader.classList.toggle(loaderClass);
}
}
validateEmailFormat(value) {
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailPattern.test(value);
}
validateEmail(billingEmail) {
const billingEmailSelector = document.querySelector(billingEmail);
const value = document.querySelector(billingEmail + ' input').value;
if (this.validateEmailFormat(value)) {
billingEmailSelector.classList.remove('woocommerce-invalid');
billingEmailSelector.classList.add('woocommerce-validated');
this.setStatus('validEmail', true);
} else {
billingEmailSelector.classList.remove('woocommerce-validated');
billingEmailSelector.classList.add('woocommerce-invalid');
this.setStatus('validEmail', false);
}
}
reEnableEmailInput() {
const reEnableInput = (ev) => {
const submitButton = document.querySelector(this.el.billingEmailSubmitButton.selector);
if (submitButton.hasAttribute('disabled')) {
submitButton.removeAttribute('disabled');
}
};
this.$('#billing_email_field input').on('focus', reEnableInput);
this.$('#billing_email_field input').on('input', reEnableInput);
this.$('#billing_email_field input').on('click', reEnableInput);
}
}
export default AxoManager;

View file

@ -7,6 +7,10 @@ class DomElementCollection {
selector: '#payment_method_ppcp-axo-gateway',
});
this.gatewayDescription = new DomElement({
selector: '.payment_box.payment_method_ppcp-axo-gateway',
});
this.defaultSubmitButton = new DomElement({
selector: '#place_order',
});
@ -20,7 +24,7 @@ class DomElementCollection {
this.watermarkContainer = new DomElement({
id: 'ppcp-axo-watermark-container',
selector: '#ppcp-axo-watermark-container',
className: 'ppcp-axo-watermark-container'
className: 'ppcp-axo-watermark-container ppcp-axo-watermark-loading loader'
});
this.customerDetails = new DomElement({
@ -56,6 +60,23 @@ class DomElementCollection {
selector: '#billing_email_field'
});
this.billingEmailFieldWrapper = new DomElement({
id: 'ppcp-axo-billing-email-field-wrapper',
selector: '#ppcp-axo-billing-email-field-wrapper',
});
this.billingEmailSubmitButton = new DomElement({
id: 'ppcp-axo-billing-email-submit-button',
selector: '#ppcp-axo-billing-email-submit-button',
className: 'ppcp-axo-billing-email-submit-button-hidden button alt wp-element-button wc-block-components-button'
});
this.billingEmailSubmitButtonSpinner = new DomElement({
id: 'ppcp-axo-billing-email-submit-button-spinner',
selector: '#ppcp-axo-billing-email-submit-button-spinner',
className: 'loader ppcp-axo-overlay'
});
this.submitButtonContainer = new DomElement({
selector: '#ppcp-axo-submit-button-container',
});

View file

@ -1,4 +1,26 @@
export function log(message, level = 'info') {
const endpoint = window.wc_ppcp_axo?.ajax?.frontend_logger?.endpoint;
if(!endpoint) {
return;
}
export function log(...args) {
//console.log('[AXO] ', ...args);
fetch(endpoint, {
method: 'POST',
credentials: 'same-origin',
body: JSON.stringify({
nonce: window.wc_ppcp_axo.ajax.frontend_logger.nonce,
log: {
message,
level,
}
})
}).then(() => {
switch (level) {
case 'error':
console.error(`[AXO] ${message}`);
break;
default:
console.log(`[AXO] ${message}`);
}
});
}

View file

@ -34,22 +34,7 @@ class BillingView {
</div>
`;
}
return `
<div style="margin-bottom: 20px;">
<div class="axo-checkout-header-section">
<h3>Billing</h3>
<a href="javascript:void(0)" ${this.el.changeBillingAddressLink.attributes}>Edit</a>
</div>
<div>${data.value('email')}</div>
<div>${data.value('company')}</div>
<div>${data.value('firstName')} ${data.value('lastName')}</div>
<div>${data.value('street1')}</div>
<div>${data.value('street2')}</div>
<div>${data.value('postCode')} ${data.value('city')}</div>
<div>${valueOfSelect('#billing_state', data.value('stateCode'))}</div>
<div>${valueOfSelect('#billing_country', data.value('countryCode'))}</div>
</div>
`;
return '';
},
fields: {
email: {

View file

@ -20,10 +20,6 @@ class CardView {
if (data.isEmpty()) {
return `
<div style="margin-bottom: 20px; text-align: center;">
<div style="border:2px solid #cccccc; border-radius: 10px; padding: 26px 20px; margin-bottom: 20px; background-color:#f6f6f6">
<div>Please fill in your card details.</div>
</div>
<h4><a href="javascript:void(0)" ${this.el.changeCardLink.attributes}>Add card details</a></h4>
${selectOtherPaymentMethod()}
</div>
`;
@ -34,8 +30,8 @@ class CardView {
const cardIcons = {
'VISA': 'visa-light.svg',
'MASTER_CARD': 'mastercard-light.svg',
'AMEX': 'amex.svg',
'DISCOVER': 'discover.svg',
'AMEX': 'amex-light.svg',
'DISCOVER': 'discover-light.svg',
'DINERS': 'dinersclub-light.svg',
'JCB': 'jcb-light.svg',
'UNIONPAY': 'unionpay-light.svg',
@ -52,7 +48,7 @@ class CardView {
<img
class="ppcp-card-icon"
title="${data.value('brand')}"
src="${window.wc_ppcp_axo.module_url}/assets/images/axo/${cardIcons[data.value('brand')]}"
src="${window.wc_ppcp_axo.icons_directory}${cardIcons[data.value('brand')]}"
alt="${data.value('brand')}"
>
</div>

View file

@ -13,7 +13,10 @@ use WooCommerce\PayPalCommerce\Axo\Assets\AxoManager;
use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway;
use WooCommerce\PayPalCommerce\Axo\Helper\ApmApplies;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CartCheckoutDetector;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
return array(
@ -58,7 +61,8 @@ return array(
$container->get( 'onboarding.environment' ),
$container->get( 'wcgateway.settings.status' ),
$container->get( 'api.shop.currency' ),
$container->get( 'woocommerce.logger.woocommerce' )
$container->get( 'woocommerce.logger.woocommerce' ),
$container->get( 'wcgateway.url' )
);
},
@ -144,6 +148,11 @@ return array(
'woocommerce_paypal_payments_axo_supported_country_currency_matrix',
array(
'US' => array(
'AUD',
'CAD',
'EUR',
'GBP',
'JPY',
'USD',
),
)
@ -191,6 +200,40 @@ return array(
return '';
}
return '<div class="ppcp-notice ppcp-notice-error"><p>' . $notice_content . '</p></div>';
},
'axo.smart-button-location-notice' => static function ( ContainerInterface $container ) : string {
$settings = $container->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
if ( $settings->has( 'axo_enabled' ) && $settings->get( 'axo_enabled' ) ) {
$fastlane_settings_url = admin_url(
sprintf(
'admin.php?page=wc-settings&tab=checkout&section=%1$s&ppcp-tab=%2$s#field-axo_heading',
PayPalGateway::ID,
CreditCardGateway::ID
)
);
$notice_content = sprintf(
/* translators: %1$s: URL to the Checkout edit page. */
__(
'<span class="highlight">Important:</span> The <code>Cart</code> & <code>Classic Cart</code> <strong>Smart Button Locations</strong> cannot be disabled while <a href="%1$s">Fastlane</a> is active.',
'woocommerce-paypal-payments'
),
esc_url( $fastlane_settings_url )
);
} else {
return '';
}
return '<div class="ppcp-notice ppcp-notice-warning"><p>' . $notice_content . '</p></div>';
},
'axo.endpoint.frontend-logger' => static function ( ContainerInterface $container ): FrontendLoggerEndpoint {
return new FrontendLoggerEndpoint(
$container->get( 'button.request-data' ),
$container->get( 'woocommerce.logger.woocommerce' )
);
},
);

View file

@ -10,6 +10,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Axo\Assets;
use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\Axo\FrontendLoggerEndpoint;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
@ -78,6 +79,13 @@ class AxoManager {
*/
private $session_handler;
/**
* The WcGateway module URL.
*
* @var string
*/
private $wcgateway_module_url;
/**
* AxoManager constructor.
*
@ -89,6 +97,7 @@ class AxoManager {
* @param SettingsStatus $settings_status The Settings status helper.
* @param string $currency 3-letter currency code of the shop.
* @param LoggerInterface $logger The logger.
* @param string $wcgateway_module_url The WcGateway module URL.
*/
public function __construct(
string $module_url,
@ -98,17 +107,19 @@ class AxoManager {
Environment $environment,
SettingsStatus $settings_status,
string $currency,
LoggerInterface $logger
LoggerInterface $logger,
string $wcgateway_module_url
) {
$this->module_url = $module_url;
$this->version = $version;
$this->session_handler = $session_handler;
$this->settings = $settings;
$this->environment = $environment;
$this->settings_status = $settings_status;
$this->currency = $currency;
$this->logger = $logger;
$this->module_url = $module_url;
$this->version = $version;
$this->session_handler = $session_handler;
$this->settings = $settings;
$this->environment = $environment;
$this->settings_status = $settings_status;
$this->currency = $currency;
$this->logger = $logger;
$this->wcgateway_module_url = $wcgateway_module_url;
}
/**
@ -151,13 +162,13 @@ class AxoManager {
*/
private function script_data() {
return array(
'environment' => array(
'environment' => array(
'is_sandbox' => $this->environment->current_environment() === 'sandbox',
),
'widgets' => array(
'widgets' => array(
'email' => 'render',
),
'insights' => array(
'insights' => array(
'enabled' => true,
'client_id' => ( $this->settings->has( 'client_id' ) ? $this->settings->get( 'client_id' ) : null ),
'session_id' =>
@ -171,7 +182,7 @@ class AxoManager {
'value' => WC()->cart->get_total( 'numeric' ),
),
),
'style_options' => array(
'style_options' => array(
'root' => array(
'backgroundColor' => $this->settings->has( 'axo_style_root_bg_color' ) ? $this->settings->get( 'axo_style_root_bg_color' ) : '',
'errorColor' => $this->settings->has( 'axo_style_root_error_color' ) ? $this->settings->get( 'axo_style_root_error_color' ) : '',
@ -190,14 +201,22 @@ class AxoManager {
'focusBorderColor' => $this->settings->has( 'axo_style_input_focus_border_color' ) ? $this->settings->get( 'axo_style_input_focus_border_color' ) : '',
),
),
'name_on_card' => $this->settings->has( 'axo_name_on_card' ) ? $this->settings->get( 'axo_name_on_card' ) : '',
'woocommerce' => array(
'name_on_card' => $this->settings->has( 'axo_name_on_card' ) ? $this->settings->get( 'axo_name_on_card' ) : '',
'woocommerce' => array(
'states' => array(
'US' => WC()->countries->get_states( 'US' ),
'CA' => WC()->countries->get_states( 'CA' ),
),
),
'module_url' => untrailingslashit( $this->module_url ),
'icons_directory' => esc_url( $this->wcgateway_module_url ) . 'assets/images/axo/',
'module_url' => untrailingslashit( $this->module_url ),
'ajax' => array(
'frontend_logger' => array(
'endpoint' => \WC_AJAX::get_endpoint( FrontendLoggerEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( FrontendLoggerEndpoint::nonce() ),
),
),
'billing_email_button_text' => __( 'Continue', 'woocommerce-paypal-payments' ),
);
}

View file

@ -24,6 +24,7 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CartCheckoutDetector;
use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsListener;
/**
* Class AxoModule
@ -43,7 +44,6 @@ class AxoModule implements ModuleInterface {
* {@inheritDoc}
*/
public function run( ContainerInterface $c ): void {
$module = $this;
add_filter(
'woocommerce_payment_gateways',
@ -74,6 +74,10 @@ class AxoModule implements ModuleInterface {
return $methods;
}
if ( $this->is_excluded_endpoint() ) {
return $methods;
}
$methods[] = $gateway;
return $methods;
},
@ -108,9 +112,31 @@ class AxoModule implements ModuleInterface {
}
);
// Force 'cart-block' and 'cart' Smart Button locations in the settings.
add_action(
'admin_init',
static function () use ( $c ) {
$listener = $c->get( 'wcgateway.settings.listener' );
assert( $listener instanceof SettingsListener );
$settings = $c->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
$listener->filter_settings(
$settings->has( 'axo_enabled' ) && $settings->get( 'axo_enabled' ),
'smart_button_locations',
function( array $existing_setting_value ) {
$axo_forced_locations = array( 'cart-block', 'cart' );
return array_unique( array_merge( $existing_setting_value, $axo_forced_locations ) );
}
);
}
);
add_action(
'init',
function () use ( $c, $module ) {
function () use ( $c ) {
$module = $this;
// Check if the module is applicable, correct country, currency, ... etc.
if ( ! $c->get( 'axo.eligible' ) ) {
@ -123,11 +149,15 @@ class AxoModule implements ModuleInterface {
// Enqueue frontend scripts.
add_action(
'wp_enqueue_scripts',
static function () use ( $c, $manager ) {
static function () use ( $c, $manager, $module ) {
$settings = $c->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
$smart_button = $c->get( 'button.smart-button' );
assert( $smart_button instanceof SmartButtonInterface );
if ( $smart_button->should_load_ppcp_script() ) {
if ( $module->should_render_fastlane( $settings ) && $smart_button->should_load_ppcp_script() ) {
$manager->enqueue();
}
}
@ -217,6 +247,18 @@ class AxoModule implements ModuleInterface {
1
);
add_action(
'wc_ajax_' . FrontendLoggerEndpoint::ENDPOINT,
static function () use ( $c ) {
$endpoint = $c->get( 'axo.endpoint.frontend-logger' );
assert( $endpoint instanceof FrontendLoggerEndpoint );
$endpoint->handle_request();
}
);
// Add the markup necessary for displaying overlays and loaders for Axo on the checkout page.
$this->add_checkout_loader_markup( $c );
}
/**
@ -290,6 +332,60 @@ class AxoModule implements ModuleInterface {
return ! is_user_logged_in()
&& CartCheckoutDetector::has_classic_checkout()
&& $is_axo_enabled;
&& $is_axo_enabled
&& ! $this->is_excluded_endpoint();
}
/**
* Adds the markup necessary for displaying overlays and loaders for Axo on the checkout page.
*
* @param ContainerInterface $c The container.
* @return void
*/
private function add_checkout_loader_markup( ContainerInterface $c ): void {
$settings = $c->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
if ( $this->should_render_fastlane( $settings ) ) {
add_action(
'woocommerce_checkout_before_customer_details',
function () {
echo '<div class="ppcp-axo-loading">';
}
);
add_action(
'woocommerce_checkout_after_customer_details',
function () {
echo '</div>';
}
);
add_action(
'woocommerce_checkout_billing',
function () {
echo '<div class="loader"><div class="ppcp-axo-overlay"></div>';
},
8
);
add_action(
'woocommerce_checkout_billing',
function () {
echo '</div>';
},
12
);
}
}
/**
* Condition to evaluate if the current endpoint is excluded.
*
* @return bool
*/
private function is_excluded_endpoint(): bool {
// Exclude the Order Pay endpoint.
return is_wc_endpoint_url( 'order-pay' );
}
}

View file

@ -0,0 +1,80 @@
<?php
/**
* The endpoint to log entries from frontend.
*
* @package WooCommerce\PayPalCommerce\Button\Endpoint
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Axo;
use Exception;
use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\Button\Endpoint\EndpointInterface;
use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData;
/**
* Class FrontendLoggerEndpoint
*/
class FrontendLoggerEndpoint implements EndpointInterface {
const ENDPOINT = 'ppc-frontend-logger';
/**
* The request data helper.
*
* @var RequestData
*/
private $request_data;
/**
* The logger.
*
* @var LoggerInterface
*/
private $logger;
/**
* FrontendLoggerEndpoint constructor.
*
* @param RequestData $request_data The request data helper.
* @param LoggerInterface $logger The logger.
*/
public function __construct( RequestData $request_data, LoggerInterface $logger ) {
$this->request_data = $request_data;
$this->logger = $logger;
}
/**
* Returns the nonce.
*
* @return string
*/
public static function nonce(): string {
return self::ENDPOINT;
}
/**
* Handles the request.
*
* @return bool
* @throws Exception On Error.
*/
public function handle_request(): bool {
$data = $this->request_data->read_request( $this->nonce() );
$level = $data['log']['level'] ?? 'info';
switch ( $level ) {
case 'error':
$this->logger->error( '[AXO] ' . $data['log']['message'] );
break;
default:
$this->logger->info( '[AXO] ' . $data['log']['message'] );
break;
}
wp_send_json_success();
return true;
}
}

View file

@ -168,7 +168,7 @@ class AxoGateway extends WC_Payment_Gateway {
? $this->ppcp_settings->get( 'axo_gateway_title' )
: $this->get_option( 'title', $this->method_title );
$this->description = $this->get_option( 'description', '' );
$this->description = __( 'Enter your email address to continue.', 'woocommerce-paypal-payments' );
$this->init_form_fields();
$this->init_settings();

View file

@ -26,4 +26,16 @@ class PropertiesDictionary {
);
}
/**
* Returns the list of possible cardholder name options.
*
* @return array
*/
public static function cardholder_name_options(): array {
return array(
'yes' => __( 'Yes', 'woocommerce-paypal-payments' ),
'no' => __( 'No', 'woocommerce-paypal-payments' ),
);
}
}

View file

@ -51,16 +51,7 @@ return array(
);
}
$subscription_helper = $container->get( 'wc-subscriptions.helper' );
if ( $subscription_helper->plugin_is_active() ) {
$label .= __(
'<div class="ppcp-notice ppcp-notice-warning"><p><span class="highlight">Important:</span> Cannot be deactivated while the WooCommerce Subscriptions plugin is active.</p></div>',
'woocommerce-paypal-payments'
);
}
$should_disable_checkbox = $subscription_helper->plugin_is_active() || apply_filters( 'woocommerce_paypal_payments_toggle_final_review_checkbox', false );
$should_disable_checkbox = apply_filters( 'woocommerce_paypal_payments_toggle_final_review_checkbox', false );
return $insert_after(
$fields,

View file

@ -10,7 +10,7 @@
"Edge >= 14"
],
"dependencies": {
"@paypal/react-paypal-js": "^8.2.0",
"@paypal/react-paypal-js": "^8.3.0",
"core-js": "^3.25.0",
"react": "^17.0.0",
"react-dom": "^17.0.0"

View file

@ -0,0 +1,87 @@
import {useEffect, useState} from '@wordpress/element';
import {
PayPalScriptProvider,
PayPalCardFieldsProvider,
PayPalCardFieldsForm,
} from "@paypal/react-paypal-js";
import {CheckoutHandler} from "./checkout-handler";
import {createOrder, onApprove} from "../card-fields-config";
import {cartHasSubscriptionProducts} from "../Helper/Subscription";
export function CardFields({config, eventRegistration, emitResponse, components}) {
const {onPaymentSetup} = eventRegistration;
const {responseTypes} = emitResponse;
const { PaymentMethodIcons } = components;
const [cardFieldsForm, setCardFieldsForm] = useState();
const getCardFieldsForm = (cardFieldsForm) => {
setCardFieldsForm(cardFieldsForm)
}
const getSavePayment = (savePayment) => {
localStorage.setItem('ppcp-save-card-payment', savePayment);
}
const hasSubscriptionProducts = cartHasSubscriptionProducts(config.scriptData);
useEffect(() => {
localStorage.removeItem('ppcp-save-card-payment');
if(hasSubscriptionProducts) {
localStorage.setItem('ppcp-save-card-payment', 'true');
}
}, [hasSubscriptionProducts])
useEffect(
() =>
onPaymentSetup(() => {
async function handlePaymentProcessing() {
await cardFieldsForm.submit()
.catch((error) => {
return {
type: responseTypes.ERROR,
}
});
return {
type: responseTypes.SUCCESS,
}
}
return handlePaymentProcessing();
}),
[onPaymentSetup, cardFieldsForm]
);
return (
<>
<PayPalScriptProvider
options={{
clientId: config.scriptData.client_id,
components: "card-fields",
dataNamespace: 'ppcp-block-card-fields',
}}
>
<PayPalCardFieldsProvider
createOrder={createOrder}
onApprove={onApprove}
onError={(err) => {
console.error(err);
}}
>
<PayPalCardFieldsForm/>
<PaymentMethodIcons icons={config.card_icons} align="left" />
<CheckoutHandler
getCardFieldsForm={getCardFieldsForm}
getSavePayment={getSavePayment}
hasSubscriptionProducts={hasSubscriptionProducts}
saveCardText={config.save_card_text}
is_vaulting_enabled={config.is_vaulting_enabled}
/>
</PayPalCardFieldsProvider>
</PayPalScriptProvider>
</>
)
}

View file

@ -0,0 +1,28 @@
import {useEffect} from '@wordpress/element';
import {usePayPalCardFields} from "@paypal/react-paypal-js";
export const CheckoutHandler = ({getCardFieldsForm, getSavePayment, hasSubscriptionProducts, saveCardText, is_vaulting_enabled}) => {
const {cardFieldsForm} = usePayPalCardFields();
useEffect(() => {
getCardFieldsForm(cardFieldsForm)
}, []);
if (!is_vaulting_enabled) {
return null;
}
return (
<>
<input
type="checkbox"
id="save"
name="save"
onChange={(e) => getSavePayment(e.target.checked)}
defaultChecked={hasSubscriptionProducts}
disabled={hasSubscriptionProducts}
/>
<label htmlFor="save">{saveCardText}</label>
</>
)
}

View file

@ -0,0 +1,17 @@
import { registerPaymentMethod } from '@woocommerce/blocks-registry';
import {CardFields} from "./Components/card-fields";
const config = wc.wcSettings.getSetting('ppcp-credit-card-gateway_data');
registerPaymentMethod({
name: config.id,
label: <div dangerouslySetInnerHTML={{__html: config.title}}/>,
content: <CardFields config={config}/>,
edit: <div></div>,
ariaLabel: config.title,
canMakePayment: () => {return true},
supports: {
showSavedCards: true,
features: config.supports
}
})

View file

@ -0,0 +1,43 @@
const config = wc.wcSettings.getSetting('ppcp-credit-card-gateway_data');
export async function createOrder() {
return fetch(config.scriptData.ajax.create_order.endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
nonce: config.scriptData.ajax.create_order.nonce,
context: config.scriptData.context,
payment_method: 'ppcp-credit-card-gateway',
save_payment_method: localStorage.getItem('ppcp-save-card-payment') === 'true',
}),
})
.then((response) => response.json())
.then((order) => {
return order.data.id;
})
.catch((err) => {
console.error(err);
});
}
export async function onApprove(data) {
return fetch(config.scriptData.ajax.approve_order.endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
order_id: data.orderID,
nonce: config.scriptData.ajax.approve_order.nonce,
}),
})
.then((response) => response.json())
.then((data) => {
localStorage.removeItem('ppcp-save-card-payment');
})
.catch((err) => {
console.error(err);
});
}

View file

@ -23,6 +23,9 @@ import {
import buttonModuleWatcher from "../../../ppcp-button/resources/js/modules/ButtonModuleWatcher";
import BlockCheckoutMessagesBootstrap from "./Bootstrap/BlockCheckoutMessagesBootstrap";
import {keysToCamelCase} from "../../../ppcp-button/resources/js/modules/Helper/Utils";
import {
handleShippingOptionsChange
} from "../../../ppcp-button/resources/js/modules/Helper/ShippingHandler";
const config = wc.wcSettings.getSetting('ppcp-gateway_data');
window.ppcpFundingSource = config.fundingSource;
@ -146,7 +149,7 @@ const PayPalComponent = ({
shipping_address: addresses.shippingAddress,
}),
];
if (!config.finalReviewEnabled) {
if (shouldHandleShippingInPayPal()) {
// set address in UI
promises.push(wp.data.dispatch('wc/store/cart').setBillingAddress(addresses.billingAddress));
if (shippingData.needsShipping) {
@ -181,7 +184,7 @@ const PayPalComponent = ({
throw new Error(config.scriptData.labels.error.generic)
}
if (config.finalReviewEnabled) {
if (!shouldHandleShippingInPayPal()) {
location.href = getCheckoutRedirectUrl();
} else {
setGotoContinuationOnError(true);
@ -220,7 +223,7 @@ const PayPalComponent = ({
shipping_address: addresses.shippingAddress,
}),
];
if (!config.finalReviewEnabled) {
if (shouldHandleShippingInPayPal()) {
// set address in UI
promises.push(wp.data.dispatch('wc/store/cart').setBillingAddress(addresses.billingAddress));
if (shippingData.needsShipping) {
@ -255,7 +258,7 @@ const PayPalComponent = ({
throw new Error(config.scriptData.labels.error.generic)
}
if (config.finalReviewEnabled) {
if (!shouldHandleShippingInPayPal()) {
location.href = getCheckoutRedirectUrl();
} else {
setGotoContinuationOnError(true);
@ -297,8 +300,12 @@ const PayPalComponent = ({
onClick();
};
const isVenmoAndVaultingEnabled = () => {
return window.ppcpFundingSource === 'venmo' && config.scriptData.vaultingEnabled;
const shouldHandleShippingInPayPal = () => {
if (config.finalReviewEnabled) {
return false;
}
return window.ppcpFundingSource !== 'venmo' || !config.scriptData.vaultingEnabled;
}
let handleShippingOptionsChange = null;
@ -306,7 +313,7 @@ const PayPalComponent = ({
let handleSubscriptionShippingOptionsChange = null;
let handleSubscriptionShippingAddressChange = null;
if (shippingData.needsShipping && !config.finalReviewEnabled) {
if (shippingData.needsShipping && shouldHandleShippingInPayPal()) {
handleShippingOptionsChange = async (data, actions) => {
try {
const shippingOptionId = data.selectedShippingOption?.id;
@ -391,6 +398,21 @@ const PayPalComponent = ({
await shippingData.setShippingAddress(address);
const res = await fetch(config.ajax.update_shipping.endpoint, {
method: 'POST',
credentials: 'same-origin',
body: JSON.stringify({
nonce: config.ajax.update_shipping.nonce,
order_id: data.orderID,
})
});
const json = await res.json();
if (!json.success) {
throw new Error(json.data.message);
}
} catch (e) {
console.error(e);
@ -447,7 +469,7 @@ const PayPalComponent = ({
if (config.scriptData.continuation) {
return true;
}
if (!config.finalReviewEnabled) {
if (shouldHandleShippingInPayPal()) {
location.href = getCheckoutRedirectUrl();
}
return true;
@ -493,8 +515,16 @@ const PayPalComponent = ({
onError={onClose}
createSubscription={createSubscription}
onApprove={handleApproveSubscription}
onShippingOptionsChange={handleSubscriptionShippingOptionsChange}
onShippingAddressChange={handleSubscriptionShippingAddressChange}
onShippingOptionsChange={(data, actions) => {
shouldHandleShippingInPayPal()
? handleSubscriptionShippingOptionsChange(data, actions)
: null;
}}
onShippingAddressChange={(data, actions) => {
shouldHandleShippingInPayPal()
? handleSubscriptionShippingAddressChange(data, actions)
: null;
}}
/>
);
}
@ -508,8 +538,16 @@ const PayPalComponent = ({
onError={onClose}
createOrder={createOrder}
onApprove={handleApprove}
onShippingOptionsChange={handleShippingOptionsChange}
onShippingAddressChange={handleShippingAddressChange}
onShippingOptionsChange={(data, actions) => {
shouldHandleShippingInPayPal()
? handleShippingOptionsChange(data, actions)
: null;
}}
onShippingAddressChange={(data, actions) => {
shouldHandleShippingInPayPal()
? handleShippingAddressChange(data, actions)
: null;
}}
/>
);
}
@ -568,7 +606,7 @@ if(cartHasSubscriptionProducts(config.scriptData)) {
features.push('subscriptions');
}
if (block_enabled) {
if (block_enabled && config.enabled) {
if ((config.addPlaceOrderMethod || config.usePlaceOrder) && !config.scriptData.continuation) {
let descriptionElement = <div dangerouslySetInnerHTML={{__html: config.description}}></div>;
if (config.placeOrderButtonDescription) {

View file

@ -9,6 +9,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Blocks;
use WooCommerce\PayPalCommerce\Blocks\Endpoint\GetPayPalOrderFromSession;
use WooCommerce\PayPalCommerce\Blocks\Endpoint\UpdateShippingEndpoint;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface;
@ -45,6 +46,17 @@ return array(
$container->get( 'wcgateway.all-funding-sources' )
);
},
'blocks.advanced-card-method' => static function( ContainerInterface $container ): AdvancedCardPaymentMethod {
return new AdvancedCardPaymentMethod(
$container->get( 'blocks.url' ),
$container->get( 'ppcp.asset-version' ),
$container->get( 'wcgateway.credit-card-gateway' ),
function () use ( $container ): SmartButtonInterface {
return $container->get( 'button.smart-button' );
},
$container->get( 'wcgateway.settings' )
);
},
'blocks.settings.final_review_enabled' => static function ( ContainerInterface $container ): bool {
$settings = $container->get( 'wcgateway.settings' );
assert( $settings instanceof ContainerInterface );

View file

@ -0,0 +1,142 @@
<?php
/**
* Advanced card payment method.
*
* @package WooCommerce\PayPalCommerce\Blocks
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Blocks;
use Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType;
use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
/**
* Class AdvancedCardPaymentMethod
*/
class AdvancedCardPaymentMethod extends AbstractPaymentMethodType {
/**
* The URL of this module.
*
* @var string
*/
private $module_url;
/**
* The assets version.
*
* @var string
*/
private $version;
/**
* Credit card gateway.
*
* @var CreditCardGateway
*/
private $gateway;
/**
* The smart button script loading handler.
*
* @var SmartButtonInterface|callable
*/
private $smart_button;
/**
* The settings.
*
* @var Settings
*/
protected $settings;
/**
* AdvancedCardPaymentMethod constructor.
*
* @param string $module_url The URL of this module.
* @param string $version The assets version.
* @param CreditCardGateway $gateway Credit card gateway.
* @param SmartButtonInterface|callable $smart_button The smart button script loading handler.
* @param Settings $settings The settings.
*/
public function __construct(
string $module_url,
string $version,
CreditCardGateway $gateway,
$smart_button,
Settings $settings
) {
$this->name = CreditCardGateway::ID;
$this->module_url = $module_url;
$this->version = $version;
$this->gateway = $gateway;
$this->smart_button = $smart_button;
$this->settings = $settings;
}
/**
* {@inheritDoc}
*/
public function initialize() {}
/**
* {@inheritDoc}
*/
public function is_active() {
return true;
}
/**
* {@inheritDoc}
*/
public function get_payment_method_script_handles() {
wp_register_script(
'ppcp-advanced-card-checkout-block',
trailingslashit( $this->module_url ) . 'assets/js/advanced-card-checkout-block.js',
array(),
$this->version,
true
);
return array( 'ppcp-advanced-card-checkout-block' );
}
/**
* {@inheritDoc}
*/
public function get_payment_method_data() {
$script_data = $this->smart_button_instance()->script_data();
return array(
'id' => $this->name,
'title' => $this->gateway->title,
'description' => $this->gateway->description,
'scriptData' => $script_data,
'supports' => $this->gateway->supports,
'save_card_text' => esc_html__( 'Save your card', 'woocommerce-paypal-payments' ),
'is_vaulting_enabled' => $this->settings->has( 'vault_enabled_dcc' ) && $this->settings->get( 'vault_enabled_dcc' ),
'card_icons' => $this->settings->has( 'card_icons' ) ? (array) $this->settings->get( 'card_icons' ) : array(),
);
}
/**
* The smart button.
*
* @return SmartButtonInterface
*/
private function smart_button_instance(): SmartButtonInterface {
if ( $this->smart_button instanceof SmartButtonInterface ) {
return $this->smart_button;
}
if ( is_callable( $this->smart_button ) ) {
$this->smart_button = ( $this->smart_button )();
}
return $this->smart_button;
}
}

View file

@ -61,6 +61,7 @@ class BlocksModule implements ModuleInterface {
'woocommerce_blocks_payment_method_type_registration',
function( PaymentMethodRegistry $payment_method_registry ) use ( $c ): void {
$payment_method_registry->register( $c->get( 'blocks.method' ) );
$payment_method_registry->register( $c->get( 'blocks.advanced-card-method' ) );
}
);

View file

@ -10,6 +10,7 @@ module.exports = {
plugins: [ new DependencyExtractionWebpackPlugin() ],
entry: {
'checkout-block': path.resolve('./resources/js/checkout-block.js'),
'advanced-card-checkout-block': path.resolve('./resources/js/advanced-card-checkout-block.js'),
"gateway": path.resolve('./resources/css/gateway.scss')
},
output: {

View file

@ -1005,19 +1005,19 @@
"@jridgewell/resolve-uri" "3.1.0"
"@jridgewell/sourcemap-codec" "1.4.14"
"@paypal/paypal-js@^8.0.4":
version "8.0.4"
resolved "https://registry.yarnpkg.com/@paypal/paypal-js/-/paypal-js-8.0.4.tgz#abe9f40f519b1d2c306adddfbe733be03eb26ce5"
integrity sha512-91g5fhRBHGEBoikDzQT6uBn3PzlJQ75g0c3MvqVJqN0XRm5kHa9wz+6+Uaq8QQuxRzz5C2x55Zg057CW6EuwpQ==
"@paypal/paypal-js@^8.0.5":
version "8.0.5"
resolved "https://registry.yarnpkg.com/@paypal/paypal-js/-/paypal-js-8.0.5.tgz#77bc461b4d1e5a2c6f081269e3ef0b2e3331a68c"
integrity sha512-yQNV7rOILeaVCNU4aVDRPqEnbIlzfxgQfFsxzsBuZW1ouqRD/4kYBWJDzczCiscSr2xOeA/Pkm7e3a9fRfnuMQ==
dependencies:
promise-polyfill "^8.3.0"
"@paypal/react-paypal-js@^8.2.0":
version "8.2.0"
resolved "https://registry.yarnpkg.com/@paypal/react-paypal-js/-/react-paypal-js-8.2.0.tgz#4b1a142bbb68e62dca4a92da4a6b5568f54901f0"
integrity sha512-SworUfu0BNNcqoh0O53Ke4MFpx2m3qJRu3hayXvlluEEXJpKqGSV5aaSGFhbsZqi8hnbsx/hZR7BQbmqsggiGQ==
"@paypal/react-paypal-js@^8.3.0":
version "8.3.0"
resolved "https://registry.yarnpkg.com/@paypal/react-paypal-js/-/react-paypal-js-8.3.0.tgz#a103080b752766b8ff59b8620887abf802e1a01b"
integrity sha512-SX17d2h1CMNFGI+wtjb329AEDaBR8Ziy2LCV076eDcY1Q0MFKRkfQ/v0HOAvZtk3sJoydRmYez2pq47BRblwqQ==
dependencies:
"@paypal/paypal-js" "^8.0.4"
"@paypal/paypal-js" "^8.0.5"
"@paypal/sdk-constants" "^1.0.122"
"@paypal/sdk-constants@^1.0.122":

View file

@ -23,7 +23,8 @@ class CartActionHandler {
body: JSON.stringify({
nonce: this.config.ajax.approve_subscription.nonce,
order_id: data.orderID,
subscription_id: data.subscriptionID
subscription_id: data.subscriptionID,
should_create_wc_order: !context.config.vaultingEnabled || data.paymentSource !== 'venmo'
})
}).then((res)=>{
return res.json();
@ -33,7 +34,9 @@ class CartActionHandler {
throw Error(data.data.message);
}
location.href = this.config.redirect;
let orderReceivedUrl = data.data?.order_received_url
location.href = orderReceivedUrl ? orderReceivedUrl : context.config.redirect;
});
},
onError: (err) => {
@ -60,8 +63,7 @@ class CartActionHandler {
funding_source: window.ppcpFundingSource,
bn_code:bnCode,
payer,
context:this.config.context,
payment_source: data.paymentSource
context:this.config.context
}),
}).then(function(res) {
return res.json();

View file

@ -4,6 +4,7 @@ import widgetBuilder from "../Renderer/WidgetBuilder";
import merge from "deepmerge";
import {keysToCamelCase} from "./Utils";
import {getCurrentPaymentMethod} from "./CheckoutMethodState";
import { v4 as uuidv4 } from 'uuid';
// This component may be used by multiple modules. This assures that options are shared between all instances.
let options = window.ppcpWidgetBuilder = window.ppcpWidgetBuilder || {
@ -63,9 +64,10 @@ export const loadPaypalScript = (config, onLoaded, onError = null) => {
// Axo SDK options
const sdkClientToken = config?.axo?.sdk_client_token;
const uuid = uuidv4().replace(/-/g, '');
if(sdkClientToken) {
scriptOptions['data-sdk-client-token'] = sdkClientToken;
scriptOptions['data-client-metadata-id'] = 'ppcp-cm-id';
scriptOptions['data-client-metadata-id'] = uuid;
}
// Load PayPal script for special case with data-client-token

View file

@ -39,19 +39,21 @@ export const handleShippingOptionsChange = async (data, actions, config) => {
})
}
const res = await fetch(config.ajax.update_shipping.endpoint, {
method: 'POST',
credentials: 'same-origin',
body: JSON.stringify({
nonce: config.ajax.update_shipping.nonce,
order_id: data.orderID,
})
});
if (!config.data_client_id.has_subscriptions) {
const res = await fetch(config.ajax.update_shipping.endpoint, {
method: 'POST',
credentials: 'same-origin',
body: JSON.stringify({
nonce: config.ajax.update_shipping.nonce,
order_id: data.orderID,
})
});
const json = await res.json();
const json = await res.json();
if (!json.success) {
throw new Error(json.data.message);
if (!json.success) {
throw new Error(json.data.message);
}
}
} catch (e) {
console.error(e);
@ -104,20 +106,20 @@ export const handleShippingAddressChange = async (data, actions, config) => {
})
})
const res = await fetch(config.ajax.update_shipping.endpoint, {
method: 'POST',
credentials: 'same-origin',
body: JSON.stringify({
nonce: config.ajax.update_shipping.nonce,
order_id: data.orderID,
})
});
const res = await fetch(config.ajax.update_shipping.endpoint, {
method: 'POST',
credentials: 'same-origin',
body: JSON.stringify({
nonce: config.ajax.update_shipping.nonce,
order_id: data.orderID,
})
});
const json = await res.json();
const json = await res.json();
if (!json.success) {
throw new Error(json.data.message);
}
if (!json.success) {
throw new Error(json.data.message);
}
} catch (e) {
console.error(e);

View file

@ -68,14 +68,6 @@ class Renderer {
}
}
shouldHandleShippingInPaypal = (venmoButtonClicked) => {
if (!this.defaultSettings.should_handle_shipping_in_paypal) {
return false;
}
return !venmoButtonClicked || !this.defaultSettings.vaultingEnabled;
}
renderButtons(wrapper, style, contextConfig, hasEnabledSeparateGateways, fundingSource = null) {
if (! document.querySelector(wrapper) || this.isAlreadyRendered(wrapper, fundingSource, hasEnabledSeparateGateways) ) {
// Try to render registered buttons again in case they were removed from the DOM by an external source.
@ -93,7 +85,16 @@ class Renderer {
const options = {
style,
...contextConfig,
onClick: this.onSmartButtonClick,
onClick: (data, actions) => {
if (this.onSmartButtonClick) {
this.onSmartButtonClick(data, actions);
}
venmoButtonClicked = false;
if (data.fundingSource === 'venmo') {
venmoButtonClicked = true;
}
},
onInit: (data, actions) => {
if (this.onSmartButtonsInit) {
this.onSmartButtonsInit(data, actions);
@ -103,9 +104,17 @@ class Renderer {
};
// Check the condition and add the handler if needed
if (this.shouldHandleShippingInPaypal(venmoButtonClicked)) {
options.onShippingOptionsChange = (data, actions) => handleShippingOptionsChange(data, actions, this.defaultSettings);
options.onShippingAddressChange = (data, actions) => handleShippingAddressChange(data, actions, this.defaultSettings);
if (this.defaultSettings.should_handle_shipping_in_paypal) {
options.onShippingOptionsChange = (data, actions) => {
!this.isVenmoButtonClickedWhenVaultingIsEnabled(venmoButtonClicked)
? handleShippingOptionsChange(data, actions, this.defaultSettings)
: null;
}
options.onShippingAddressChange = (data, actions) => {
!this.isVenmoButtonClickedWhenVaultingIsEnabled(venmoButtonClicked)
? handleShippingAddressChange(data, actions, this.defaultSettings)
: null;
}
}
return options;
@ -139,6 +148,10 @@ class Renderer {
}
}
isVenmoButtonClickedWhenVaultingIsEnabled = (venmoButtonClicked) => {
return venmoButtonClicked && this.defaultSettings.vaultingEnabled;
}
isAlreadyRendered(wrapper, fundingSource) {
return this.renderedSources.has(wrapper + (fundingSource ?? ''));
}

View file

@ -239,7 +239,6 @@ return array(
$final_review_enabled = $container->get( 'blocks.settings.final_review_enabled' );
$wc_order_creator = $container->get( 'button.helper.wc-order-creator' );
$gateway = $container->get( 'wcgateway.paypal-gateway' );
$subscription_helper = $container->get( 'wc-subscriptions.helper' );
$logger = $container->get( 'woocommerce.logger.woocommerce' );
return new ApproveOrderEndpoint(
$request_data,
@ -252,7 +251,6 @@ return array(
$final_review_enabled,
$gateway,
$wc_order_creator,
$subscription_helper,
$logger
);
},
@ -260,7 +258,10 @@ return array(
return new ApproveSubscriptionEndpoint(
$container->get( 'button.request-data' ),
$container->get( 'api.endpoint.order' ),
$container->get( 'session.handler' )
$container->get( 'session.handler' ),
$container->get( 'blocks.settings.final_review_enabled' ),
$container->get( 'button.helper.wc-order-creator' ),
$container->get( 'wcgateway.paypal-gateway' )
);
},
'button.checkout-form-saver' => static function ( ContainerInterface $container ): CheckoutFormSaver {
@ -362,6 +363,10 @@ return array(
},
'button.helper.wc-order-creator' => static function ( ContainerInterface $container ): WooCommerceOrderCreator {
return new WooCommerceOrderCreator( $container->get( 'wcgateway.funding-source.renderer' ), $container->get( 'session.handler' ) );
return new WooCommerceOrderCreator(
$container->get( 'wcgateway.funding-source.renderer' ),
$container->get( 'session.handler' ),
$container->get( 'wc-subscriptions.helper' )
);
},
);

View file

@ -1421,7 +1421,7 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages
$disable_funding,
array_diff(
array_keys( $this->all_funding_sources ),
array( 'venmo', 'paylater', 'paypal' )
array( 'venmo', 'paylater', 'paypal', 'card' )
)
);
}

View file

@ -105,13 +105,6 @@ class ApproveOrderEndpoint implements EndpointInterface {
*/
protected $wc_order_creator;
/**
* The Subscription Helper.
*
* @var SubscriptionHelper
*/
protected $subscription_helper;
/**
* The logger.
*
@ -132,7 +125,6 @@ class ApproveOrderEndpoint implements EndpointInterface {
* @param bool $final_review_enabled Whether the final review is enabled.
* @param PayPalGateway $gateway The WC gateway.
* @param WooCommerceOrderCreator $wc_order_creator The WooCommerce order creator.
* @param SubscriptionHelper $subscription_helper The subscription helper.
* @param LoggerInterface $logger The logger.
*/
public function __construct(
@ -146,7 +138,6 @@ class ApproveOrderEndpoint implements EndpointInterface {
bool $final_review_enabled,
PayPalGateway $gateway,
WooCommerceOrderCreator $wc_order_creator,
SubscriptionHelper $subscription_helper,
LoggerInterface $logger
) {
@ -160,7 +151,6 @@ class ApproveOrderEndpoint implements EndpointInterface {
$this->final_review_enabled = $final_review_enabled;
$this->gateway = $gateway;
$this->wc_order_creator = $wc_order_creator;
$this->subscription_helper = $subscription_helper;
$this->logger = $logger;
}
@ -247,7 +237,7 @@ class ApproveOrderEndpoint implements EndpointInterface {
$this->session_handler->replace_order( $order );
if ( ! $this->subscription_helper->plugin_is_active() && apply_filters( 'woocommerce_paypal_payments_toggle_final_review_checkbox', false ) ) {
if ( apply_filters( 'woocommerce_paypal_payments_toggle_final_review_checkbox', false ) ) {
$this->toggle_final_review_enabled_setting();
}

View file

@ -11,13 +11,18 @@ namespace WooCommerce\PayPalCommerce\Button\Endpoint;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait;
use WooCommerce\PayPalCommerce\Button\Helper\WooCommerceOrderCreator;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
/**
* Class ApproveSubscriptionEndpoint
*/
class ApproveSubscriptionEndpoint implements EndpointInterface {
use ContextTrait;
const ENDPOINT = 'ppc-approve-subscription';
/**
@ -41,21 +46,51 @@ class ApproveSubscriptionEndpoint implements EndpointInterface {
*/
private $session_handler;
/**
* Whether the final review is enabled.
*
* @var bool
*/
protected $final_review_enabled;
/**
* The WooCommerce order creator.
*
* @var WooCommerceOrderCreator
*/
protected $wc_order_creator;
/**
* The WC gateway.
*
* @var PayPalGateway
*/
protected $gateway;
/**
* ApproveSubscriptionEndpoint constructor.
*
* @param RequestData $request_data The request data helper.
* @param OrderEndpoint $order_endpoint The order endpoint.
* @param SessionHandler $session_handler The session handler.
* @param RequestData $request_data The request data helper.
* @param OrderEndpoint $order_endpoint The order endpoint.
* @param SessionHandler $session_handler The session handler.
* @param bool $final_review_enabled Whether the final review is enabled.
* @param WooCommerceOrderCreator $wc_order_creator The WooCommerce order creator.
* @param PayPalGateway $gateway The WC gateway.
*/
public function __construct(
RequestData $request_data,
OrderEndpoint $order_endpoint,
SessionHandler $session_handler
SessionHandler $session_handler,
bool $final_review_enabled,
WooCommerceOrderCreator $wc_order_creator,
PayPalGateway $gateway
) {
$this->request_data = $request_data;
$this->order_endpoint = $order_endpoint;
$this->session_handler = $session_handler;
$this->request_data = $request_data;
$this->order_endpoint = $order_endpoint;
$this->session_handler = $session_handler;
$this->final_review_enabled = $final_review_enabled;
$this->wc_order_creator = $wc_order_creator;
$this->gateway = $gateway;
}
/**
@ -88,6 +123,15 @@ class ApproveSubscriptionEndpoint implements EndpointInterface {
WC()->session->set( 'ppcp_subscription_id', $data['subscription_id'] );
}
$should_create_wc_order = $data['should_create_wc_order'] ?? false;
if ( ! $this->final_review_enabled && ! $this->is_checkout() && $should_create_wc_order ) {
$wc_order = $this->wc_order_creator->create_from_paypal_order( $order, WC()->cart );
$this->gateway->process_payment( $wc_order->get_id() );
$order_received_url = $wc_order->get_checkout_order_received_url();
wp_send_json_success( array( 'order_received_url' => $order_received_url ) );
}
wp_send_json_success();
return true;
}

View file

@ -246,7 +246,6 @@ class CreateOrderEndpoint implements EndpointInterface {
$this->parsed_request_data = $data;
$payment_method = $data['payment_method'] ?? '';
$funding_source = $data['funding_source'] ?? '';
$payment_source = $data['payment_source'] ?? '';
$wc_order = null;
if ( 'pay-now' === $data['context'] ) {
$wc_order = wc_get_order( (int) $data['order_id'] );
@ -262,7 +261,7 @@ class CreateOrderEndpoint implements EndpointInterface {
}
$this->purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order );
} else {
$this->purchase_unit = $this->purchase_unit_factory->from_wc_cart( null, $this->should_handle_shipping_in_paypal( $payment_source ) );
$this->purchase_unit = $this->purchase_unit_factory->from_wc_cart( null, $this->should_handle_shipping_in_paypal( $funding_source ) );
// Do not allow completion by webhooks when started via non-checkout buttons,
// it is needed only for some APMs in checkout.
@ -615,16 +614,16 @@ class CreateOrderEndpoint implements EndpointInterface {
/**
* Checks if the shipping should be handled in PayPal popup.
*
* @param string $payment_source The payment source.
* @param string $funding_source The funding source.
* @return bool true if the shipping should be handled in PayPal popup, otherwise false.
*/
protected function should_handle_shipping_in_paypal( string $payment_source ): bool {
protected function should_handle_shipping_in_paypal( string $funding_source ): bool {
$is_vaulting_enabled = $this->settings->has( 'vault_enabled' ) && $this->settings->get( 'vault_enabled' );
if ( ! $this->handle_shipping_in_paypal ) {
return false;
}
return ! $is_vaulting_enabled || $payment_source !== 'venmo';
return ! $is_vaulting_enabled || $funding_source !== 'venmo';
}
}

View file

@ -95,47 +95,51 @@ trait ContextTrait {
* @return string
*/
protected function context(): string {
if ( is_product() || wc_post_content_has_shortcode( 'product_page' ) ) {
// Default context.
$context = 'mini-cart';
// Do this check here instead of reordering outside conditions.
// In order to have more control over the context.
if ( $this->is_checkout() && ! $this->is_paypal_continuation() ) {
return 'checkout';
}
switch ( true ) {
case is_product() || wc_post_content_has_shortcode( 'product_page' ):
// Do this check here instead of reordering outside conditions.
// In order to have more control over the context.
if ( $this->is_checkout() && ! $this->is_paypal_continuation() ) {
$context = 'checkout';
} else {
$context = 'product';
}
break;
return 'product';
// has_block may not work if called too early, such as during the block registration.
case has_block( 'woocommerce/cart' ):
$context = 'cart-block';
break;
case $this->is_cart():
$context = 'cart';
break;
case is_checkout_pay_page():
$context = 'pay-now';
break;
case has_block( 'woocommerce/checkout' ):
$context = 'checkout-block';
break;
case $this->is_checkout() && ! $this->is_paypal_continuation():
$context = 'checkout';
break;
case $this->is_add_payment_method_page():
$context = 'add-payment-method';
break;
case $this->is_block_editor():
$context = 'block-editor';
break;
}
// has_block may not work if called too early, such as during the block registration.
if ( has_block( 'woocommerce/cart' ) ) {
return 'cart-block';
}
if ( $this->is_cart() ) {
return 'cart';
}
if ( is_checkout_pay_page() ) {
return 'pay-now';
}
if ( has_block( 'woocommerce/checkout' ) ) {
return 'checkout-block';
}
if ( $this->is_checkout() && ! $this->is_paypal_continuation() ) {
return 'checkout';
}
if ( $this->is_add_payment_method_page() ) {
return 'add-payment-method';
}
if ( $this->is_block_editor() ) {
return 'block-editor';
}
return 'mini-cart';
return apply_filters( 'woocommerce_paypal_payments_context', $context );
}
/**

View file

@ -14,12 +14,16 @@ use WC_Cart;
use WC_Order;
use WC_Order_Item_Product;
use WC_Order_Item_Shipping;
use WC_Subscription;
use WC_Subscriptions_Product;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payer;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Shipping;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcGateway\FundingSource\FundingSourceRenderer;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
use WP_Error;
/**
* Class WooCommerceOrderCreator
@ -40,18 +44,28 @@ class WooCommerceOrderCreator {
*/
protected $session_handler;
/**
* The subscription helper
*
* @var SubscriptionHelper
*/
protected $subscription_helper;
/**
* WooCommerceOrderCreator constructor.
*
* @param FundingSourceRenderer $funding_source_renderer The funding source renderer.
* @param SessionHandler $session_handler The session handler.
* @param SubscriptionHelper $subscription_helper The subscription helper.
*/
public function __construct(
FundingSourceRenderer $funding_source_renderer,
SessionHandler $session_handler
SessionHandler $session_handler,
SubscriptionHelper $subscription_helper
) {
$this->funding_source_renderer = $funding_source_renderer;
$this->session_handler = $session_handler;
$this->subscription_helper = $subscription_helper;
}
/**
@ -69,10 +83,13 @@ class WooCommerceOrderCreator {
throw new RuntimeException( 'Problem creating WC order.' );
}
$this->configure_line_items( $wc_order, $wc_cart );
$this->configure_shipping( $wc_order, $order->payer(), $order->purchase_units()[0]->shipping() );
$payer = $order->payer();
$shipping = $order->purchase_units()[0]->shipping();
$this->configure_payment_source( $wc_order );
$this->configure_customer( $wc_order );
$this->configure_line_items( $wc_order, $wc_cart, $payer, $shipping );
$this->configure_shipping( $wc_order, $payer, $shipping );
$this->configure_coupons( $wc_order, $wc_cart->get_applied_coupons() );
$wc_order->calculate_totals();
@ -84,11 +101,13 @@ class WooCommerceOrderCreator {
/**
* Configures the line items.
*
* @param WC_Order $wc_order The WC order.
* @param WC_Cart $wc_cart The Cart.
* @param WC_Order $wc_order The WC order.
* @param WC_Cart $wc_cart The Cart.
* @param Payer|null $payer The payer.
* @param Shipping|null $shipping The shipping.
* @return void
*/
protected function configure_line_items( WC_Order $wc_order, WC_Cart $wc_cart ): void {
protected function configure_line_items( WC_Order $wc_order, WC_Cart $wc_cart, ?Payer $payer, ?Shipping $shipping ): void {
$cart_contents = $wc_cart->get_cart();
foreach ( $cart_contents as $cart_item ) {
@ -111,9 +130,37 @@ class WooCommerceOrderCreator {
return;
}
$total = $product->get_price() * $quantity;
$item->set_name( $product->get_name() );
$item->set_subtotal( $product->get_price() * $quantity );
$item->set_total( $product->get_price() * $quantity );
$item->set_subtotal( $total );
$item->set_total( $total );
$product_id = $product->get_id();
if ( $this->is_subscription( $product_id ) ) {
$subscription = $this->create_subscription( $wc_order, $product_id );
$sign_up_fee = WC_Subscriptions_Product::get_sign_up_fee( $product );
$subscription_total = $total + $sign_up_fee;
$item->set_subtotal( $subscription_total );
$item->set_total( $subscription_total );
$subscription->add_product( $product );
$this->configure_shipping( $subscription, $payer, $shipping );
$this->configure_payment_source( $subscription );
$this->configure_coupons( $subscription, $wc_cart->get_applied_coupons() );
$dates = array(
'trial_end' => WC_Subscriptions_Product::get_trial_expiration_date( $product_id ),
'next_payment' => WC_Subscriptions_Product::get_first_renewal_payment_date( $product_id ),
'end' => WC_Subscriptions_Product::get_expiration_date( $product_id ),
);
$subscription->update_dates( $dates );
$subscription->calculate_totals();
$subscription->payment_complete_for_order( $wc_order );
}
$wc_order->add_item( $item );
}
@ -179,8 +226,18 @@ class WooCommerceOrderCreator {
$shipping->set_method_id( $shipping_options->id() );
$shipping->set_total( $shipping_options->amount()->value_str() );
$items = $wc_order->get_items();
$items_in_package = array();
foreach ( $items as $item ) {
$items_in_package[] = $item->get_name() . ' &times; ' . (string) $item->get_quantity();
}
$shipping->add_meta_data( __( 'Items', 'woocommerce-paypal-payments' ), implode( ', ', $items_in_package ) );
$wc_order->add_item( $shipping );
}
$wc_order->calculate_totals();
}
/**
@ -225,4 +282,43 @@ class WooCommerceOrderCreator {
}
}
/**
* Checks if the product with given ID is WC subscription.
*
* @param int $product_id The product ID.
* @return bool true if the product is subscription, otherwise false.
*/
protected function is_subscription( int $product_id ): bool {
if ( ! $this->subscription_helper->plugin_is_active() ) {
return false;
}
return WC_Subscriptions_Product::is_subscription( $product_id );
}
/**
* Creates WC subscription from given order and product ID.
*
* @param WC_Order $wc_order The WC order.
* @param int $product_id The product ID.
* @return WC_Subscription The subscription order
* @throws RuntimeException If problem creating.
*/
protected function create_subscription( WC_Order $wc_order, int $product_id ): WC_Subscription {
$subscription = wcs_create_subscription(
array(
'order_id' => $wc_order->get_id(),
'status' => 'pending',
'billing_period' => WC_Subscriptions_Product::get_period( $product_id ),
'billing_interval' => WC_Subscriptions_Product::get_interval( $product_id ),
'customer_id' => $wc_order->get_customer_id(),
)
);
if ( $subscription instanceof WP_Error ) {
throw new RuntimeException( $subscription->get_error_message() );
}
return $subscription;
}
}

View file

@ -105,9 +105,8 @@ class CardFieldsModule implements ModuleInterface {
add_filter(
'ppcp_create_order_request_body_data',
function( array $data ) use ( $c ): array {
function( array $data, string $payment_method ) use ( $c ): array {
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$payment_method = wc_clean( wp_unslash( $_POST['payment_method'] ?? '' ) );
if ( $payment_method !== CreditCardGateway::ID ) {
return $data;
}
@ -134,7 +133,9 @@ class CardFieldsModule implements ModuleInterface {
}
return $data;
}
},
10,
2
);
}
}

View file

@ -54,6 +54,18 @@ return array(
);
},
'compat.plugin-script-file-names' => static function( ContainerInterface $container ) : array {
return array(
'button.js',
'gateway-settings.js',
'status-page.js',
'order-edit-page.js',
'fraudnet.js',
'tracking-compat.js',
'ppcp-clear-db.js',
);
},
'compat.gzd.is_supported_plugin_version_active' => function (): bool {
return function_exists( 'wc_gzd_get_shipments_by_order' ); // 3.0+
},

View file

@ -15,6 +15,7 @@ use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\Compat\Assets\CompatAssets;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CartCheckoutDetector;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
/**
@ -41,7 +42,6 @@ class CompatModule implements ModuleInterface {
public function run( ContainerInterface $c ): void {
$this->initialize_ppec_compat_layer( $c );
$this->fix_site_ground_optimizer_compatibility( $c );
$this->initialize_tracking_compat_layer( $c );
$asset_loader = $c->get( 'compat.assets' );
@ -54,6 +54,8 @@ class CompatModule implements ModuleInterface {
$this->migrate_smart_button_settings( $c );
$this->fix_page_builders();
$this->exclude_cache_plugins_js_minification( $c );
$this->set_elementor_checkout_context();
}
/**
@ -88,24 +90,6 @@ class CompatModule implements ModuleInterface {
}
}
);
}
/**
* Fixes the compatibility issue for <a href="https://wordpress.org/plugins/sg-cachepress/">SiteGround Optimizer plugin</a>.
*
* @link https://wordpress.org/plugins/sg-cachepress/
*
* @param ContainerInterface $c The Container.
*/
protected function fix_site_ground_optimizer_compatibility( ContainerInterface $c ): void {
$ppcp_script_names = $c->get( 'compat.plugin-script-names' );
add_filter(
'sgo_js_minify_exclude',
function ( array $scripts ) use ( $ppcp_script_names ) {
return array_merge( $scripts, $ppcp_script_names );
}
);
}
/**
@ -329,4 +313,69 @@ class CompatModule implements ModuleInterface {
$parent = $theme->parent();
return ( $parent && $parent->get( 'Name' ) === 'Divi' );
}
/**
* Sets the context for the Elementor checkout page.
*
* @return void
*/
protected function set_elementor_checkout_context(): void {
add_action(
'wp',
function() {
$page_id = get_the_ID();
if ( ! $page_id || ! CartCheckoutDetector::has_elementor_checkout( $page_id ) ) {
return;
}
add_filter(
'woocommerce_paypal_payments_context',
function ( string $context ): string {
// Default context.
return ( 'mini-cart' === $context ) ? 'checkout' : $context;
}
);
}
);
}
/**
* Excludes PayPal scripts from being minified by cache plugins.
*
* @param ContainerInterface $c The Container.
* @return void
*/
protected function exclude_cache_plugins_js_minification( ContainerInterface $c ): void {
$ppcp_script_names = $c->get( 'compat.plugin-script-names' );
$ppcp_script_file_names = $c->get( 'compat.plugin-script-file-names' );
// Siteground SG Optimize.
add_filter(
'sgo_js_minify_exclude',
function( array $scripts ) use ( $ppcp_script_names ) {
return array_merge( $scripts, $ppcp_script_names );
}
);
// LiteSpeed Cache.
add_filter(
'litespeed_optimize_js_excludes',
function( array $excluded_js ) use ( $ppcp_script_file_names ) {
return array_merge( $excluded_js, $ppcp_script_file_names );
}
);
// W3 Total Cache.
add_filter(
'w3tc_minify_js_do_tag_minification',
function( bool $do_tag_minification, string $script_tag, string $file ) {
if ( $file && strpos( $file, 'ppcp' ) !== false ) {
return false;
}
return $do_tag_minification;
},
10,
3
);
}
}

View file

@ -926,7 +926,7 @@ return array(
: $container->get( 'googlepay.enable-url-sandbox' );
$button_url = $enabled
? admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway#field-alternative_payment_methods' )
? admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway&ppcp-tab=ppcp-credit-card-gateway#ppcp-googlepay_button_enabled' )
: $enable_url;
return sprintf(

View file

@ -73,34 +73,34 @@ class GermanizedShipmentIntegration implements Integration {
add_action(
'woocommerce_gzd_shipment_status_shipped',
function( int $shipment_id, Shipment $shipment ) {
if ( ! apply_filters( 'woocommerce_paypal_payments_sync_gzd_tracking', true ) ) {
return;
}
$wc_order = $shipment->get_order();
if ( ! is_a( $wc_order, WC_Order::class ) ) {
return;
}
$paypal_order = ppcp_get_paypal_order( $wc_order );
$capture_id = $this->get_paypal_order_transaction_id( $paypal_order );
$wc_order_id = $wc_order->get_id();
$tracking_number = $shipment->get_tracking_id();
$carrier = $shipment->get_shipping_provider();
$items = array_map(
function ( ShipmentItem $item ): int {
return $item->get_order_item_id();
},
$shipment->get_items()
);
if ( ! $tracking_number || ! $carrier || ! $capture_id ) {
return;
}
try {
if ( ! apply_filters( 'woocommerce_paypal_payments_sync_gzd_tracking', true ) ) {
return;
}
$wc_order = $shipment->get_order();
if ( ! is_a( $wc_order, WC_Order::class ) ) {
return;
}
$paypal_order = ppcp_get_paypal_order( $wc_order );
$capture_id = $this->get_paypal_order_transaction_id( $paypal_order );
$wc_order_id = $wc_order->get_id();
$tracking_number = $shipment->get_tracking_id();
$carrier = $shipment->get_shipping_provider();
$items = array_map(
function ( ShipmentItem $item ): int {
return $item->get_order_item_id();
},
$shipment->get_items()
);
if ( ! $tracking_number || ! $carrier || ! $capture_id ) {
return;
}
$ppcp_shipment = $this->shipment_factory->create_shipment(
$wc_order_id,
$capture_id,
@ -118,7 +118,7 @@ class GermanizedShipmentIntegration implements Integration {
: $this->endpoint->add_tracking_information( $ppcp_shipment, $wc_order_id );
} catch ( Exception $exception ) {
$this->logger->error( "Couldn't sync tracking information: " . $exception->getMessage() );
return;
}
},
500,

View file

@ -76,25 +76,25 @@ class ShipStationIntegration implements Integration {
* @psalm-suppress MissingClosureParamType
*/
function( $wc_order, array $data ) {
if ( ! apply_filters( 'woocommerce_paypal_payments_sync_ship_station_tracking', true ) ) {
return;
}
if ( ! is_a( $wc_order, WC_Order::class ) ) {
return;
}
$paypal_order = ppcp_get_paypal_order( $wc_order );
$capture_id = $this->get_paypal_order_transaction_id( $paypal_order );
$order_id = $wc_order->get_id();
$tracking_number = $data['tracking_number'] ?? '';
$carrier = $data['carrier'] ?? '';
if ( ! $tracking_number || ! is_string( $tracking_number ) || ! $carrier || ! is_string( $carrier ) || ! $capture_id ) {
return;
}
try {
if ( ! apply_filters( 'woocommerce_paypal_payments_sync_ship_station_tracking', true ) ) {
return;
}
if ( ! is_a( $wc_order, WC_Order::class ) ) {
return;
}
$paypal_order = ppcp_get_paypal_order( $wc_order );
$capture_id = $this->get_paypal_order_transaction_id( $paypal_order );
$order_id = $wc_order->get_id();
$tracking_number = $data['tracking_number'] ?? '';
$carrier = $data['carrier'] ?? '';
if ( ! $tracking_number || ! is_string( $tracking_number ) || ! $carrier || ! is_string( $carrier ) || ! $capture_id ) {
return;
}
$ppcp_shipment = $this->shipment_factory->create_shipment(
$order_id,
$capture_id,
@ -112,7 +112,7 @@ class ShipStationIntegration implements Integration {
: $this->endpoint->add_tracking_information( $ppcp_shipment, $order_id );
} catch ( Exception $exception ) {
$this->logger->error( "Couldn't sync tracking information: " . $exception->getMessage() );
return;
}
},
500,

View file

@ -73,30 +73,35 @@ class ShipmentTrackingIntegration implements Integration {
add_action(
'wp_ajax_wc_shipment_tracking_save_form',
function() {
check_ajax_referer( 'create-tracking-item', 'security', true );
try {
check_ajax_referer( 'create-tracking-item', 'security', true );
if ( ! apply_filters( 'woocommerce_paypal_payments_sync_wc_shipment_tracking', true ) ) {
if ( ! apply_filters( 'woocommerce_paypal_payments_sync_wc_shipment_tracking', true ) ) {
return;
}
$order_id = (int) wc_clean( wp_unslash( $_POST['order_id'] ?? '' ) );
$wc_order = wc_get_order( $order_id );
if ( ! is_a( $wc_order, WC_Order::class ) ) {
return;
}
$paypal_order = ppcp_get_paypal_order( $wc_order );
$capture_id = $this->get_paypal_order_transaction_id( $paypal_order );
$tracking_number = wc_clean( wp_unslash( $_POST['tracking_number'] ?? '' ) );
$carrier = wc_clean( wp_unslash( $_POST['tracking_provider'] ?? '' ) );
$carrier_other = wc_clean( wp_unslash( $_POST['custom_tracking_provider'] ?? '' ) );
$carrier = $carrier ?: $carrier_other ?: '';
if ( ! $tracking_number || ! is_string( $tracking_number ) || ! $carrier || ! is_string( $carrier ) || ! $capture_id ) {
return;
}
$this->sync_tracking( $order_id, $capture_id, $tracking_number, $carrier );
} catch ( Exception $exception ) {
return;
}
$order_id = (int) wc_clean( wp_unslash( $_POST['order_id'] ?? '' ) );
$wc_order = wc_get_order( $order_id );
if ( ! is_a( $wc_order, WC_Order::class ) ) {
return;
}
$paypal_order = ppcp_get_paypal_order( $wc_order );
$capture_id = $this->get_paypal_order_transaction_id( $paypal_order );
$tracking_number = wc_clean( wp_unslash( $_POST['tracking_number'] ?? '' ) );
$carrier = wc_clean( wp_unslash( $_POST['tracking_provider'] ?? '' ) );
$carrier_other = wc_clean( wp_unslash( $_POST['custom_tracking_provider'] ?? '' ) );
$carrier = $carrier ?: $carrier_other ?: '';
if ( ! $tracking_number || ! is_string( $tracking_number ) || ! $carrier || ! is_string( $carrier ) || ! $capture_id ) {
return;
}
$this->sync_tracking( $order_id, $capture_id, $tracking_number, $carrier );
}
);
@ -106,34 +111,39 @@ class ShipmentTrackingIntegration implements Integration {
add_filter(
'woocommerce_rest_prepare_order_shipment_tracking',
function( WP_REST_Response $response, array $tracking_item, WP_REST_Request $request ): WP_REST_Response {
if ( ! apply_filters( 'woocommerce_paypal_payments_sync_wc_shipment_tracking', true ) ) {
try {
if ( ! apply_filters( 'woocommerce_paypal_payments_sync_wc_shipment_tracking', true ) ) {
return $response;
}
$callback = $request->get_attributes()['callback']['1'] ?? '';
if ( $callback !== 'create_item' ) {
return $response;
}
$order_id = $tracking_item['order_id'] ?? 0;
$wc_order = wc_get_order( $order_id );
if ( ! is_a( $wc_order, WC_Order::class ) ) {
return $response;
}
$paypal_order = ppcp_get_paypal_order( $wc_order );
$capture_id = $this->get_paypal_order_transaction_id( $paypal_order );
$tracking_number = $tracking_item['tracking_number'] ?? '';
$carrier = $tracking_item['tracking_provider'] ?? '';
$carrier_other = $tracking_item['custom_tracking_provider'] ?? '';
$carrier = $carrier ?: $carrier_other ?: '';
if ( ! $tracking_number || ! $carrier || ! $capture_id ) {
return $response;
}
$this->sync_tracking( $order_id, $capture_id, $tracking_number, $carrier );
} catch ( Exception $exception ) {
return $response;
}
$callback = $request->get_attributes()['callback']['1'] ?? '';
if ( $callback !== 'create_item' ) {
return $response;
}
$order_id = $tracking_item['order_id'] ?? 0;
$wc_order = wc_get_order( $order_id );
if ( ! is_a( $wc_order, WC_Order::class ) ) {
return $response;
}
$paypal_order = ppcp_get_paypal_order( $wc_order );
$capture_id = $this->get_paypal_order_transaction_id( $paypal_order );
$tracking_number = $tracking_item['tracking_number'] ?? '';
$carrier = $tracking_item['tracking_provider'] ?? '';
$carrier_other = $tracking_item['custom_tracking_provider'] ?? '';
$carrier = $carrier ?: $carrier_other ?: '';
if ( ! $tracking_number || ! $carrier || ! $capture_id ) {
return $response;
}
$this->sync_tracking( $order_id, $capture_id, $tracking_number, $carrier );
return $response;
},
10,

View file

@ -75,44 +75,48 @@ class WcShippingTaxIntegration implements Integration {
add_filter(
'rest_post_dispatch',
function( WP_HTTP_Response $response, WP_REST_Server $server, WP_REST_Request $request ): WP_HTTP_Response {
if ( ! apply_filters( 'woocommerce_paypal_payments_sync_wc_shipping_tax', true ) ) {
try {
if ( ! apply_filters( 'woocommerce_paypal_payments_sync_wc_shipping_tax', true ) ) {
return $response;
}
$params = $request->get_params();
$order_id = (int) ( $params['order_id'] ?? 0 );
$label_id = (int) ( $params['label_ids'] ?? 0 );
if ( ! $order_id || "/wc/v1/connect/label/{$order_id}/{$label_id}" !== $request->get_route() ) {
return $response;
}
$data = $response->get_data() ?? array();
$labels = $data['labels'] ?? array();
foreach ( $labels as $label ) {
$tracking_number = $label['tracking'] ?? '';
if ( ! $tracking_number ) {
continue;
}
$wc_order = wc_get_order( $order_id );
if ( ! is_a( $wc_order, WC_Order::class ) ) {
continue;
}
$paypal_order = ppcp_get_paypal_order( $wc_order );
$capture_id = $this->get_paypal_order_transaction_id( $paypal_order );
$carrier = $label['carrier_id'] ?? $label['service_name'] ?? '';
$items = array_map( 'intval', $label['product_ids'] ?? array() );
if ( ! $carrier || ! $capture_id ) {
continue;
}
$this->sync_tracking( $order_id, $capture_id, $tracking_number, $carrier, $items );
}
} catch ( Exception $exception ) {
return $response;
}
$params = $request->get_params();
$order_id = (int) ( $params['order_id'] ?? 0 );
$label_id = (int) ( $params['label_ids'] ?? 0 );
if ( ! $order_id || "/wc/v1/connect/label/{$order_id}/{$label_id}" !== $request->get_route() ) {
return $response;
}
$data = $response->get_data() ?? array();
$labels = $data['labels'] ?? array();
foreach ( $labels as $label ) {
$tracking_number = $label['tracking'] ?? '';
if ( ! $tracking_number ) {
continue;
}
$wc_order = wc_get_order( $order_id );
if ( ! is_a( $wc_order, WC_Order::class ) ) {
continue;
}
$paypal_order = ppcp_get_paypal_order( $wc_order );
$capture_id = $this->get_paypal_order_transaction_id( $paypal_order );
$carrier = $label['carrier_id'] ?? $label['service_name'] ?? '';
$items = array_map( 'intval', $label['product_ids'] ?? array() );
if ( ! $carrier || ! $capture_id ) {
continue;
}
$this->sync_tracking( $order_id, $capture_id, $tracking_number, $carrier, $items );
}
return $response;
},
10,

View file

@ -71,27 +71,27 @@ class YithShipmentIntegration implements Integration {
add_action(
'woocommerce_process_shop_order_meta',
function( int $order_id ) {
if ( ! apply_filters( 'woocommerce_paypal_payments_sync_ywot_tracking', true ) ) {
return;
}
$wc_order = wc_get_order( $order_id );
if ( ! is_a( $wc_order, WC_Order::class ) ) {
return;
}
$paypal_order = ppcp_get_paypal_order( $wc_order );
$capture_id = $this->get_paypal_order_transaction_id( $paypal_order );
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$tracking_number = wc_clean( wp_unslash( $_POST['ywot_tracking_code'] ?? '' ) );
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$carrier = wc_clean( wp_unslash( $_POST['ywot_carrier_name'] ?? '' ) );
if ( ! $tracking_number || ! is_string( $tracking_number ) || ! $carrier || ! is_string( $carrier ) || ! $capture_id ) {
return;
}
try {
if ( ! apply_filters( 'woocommerce_paypal_payments_sync_ywot_tracking', true ) ) {
return;
}
$wc_order = wc_get_order( $order_id );
if ( ! is_a( $wc_order, WC_Order::class ) ) {
return;
}
$paypal_order = ppcp_get_paypal_order( $wc_order );
$capture_id = $this->get_paypal_order_transaction_id( $paypal_order );
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$tracking_number = wc_clean( wp_unslash( $_POST['ywot_tracking_code'] ?? '' ) );
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$carrier = wc_clean( wp_unslash( $_POST['ywot_carrier_name'] ?? '' ) );
if ( ! $tracking_number || ! is_string( $tracking_number ) || ! $carrier || ! is_string( $carrier ) || ! $capture_id ) {
return;
}
$ppcp_shipment = $this->shipment_factory->create_shipment(
$order_id,
$capture_id,
@ -109,7 +109,7 @@ class YithShipmentIntegration implements Integration {
: $this->endpoint->add_tracking_information( $ppcp_shipment, $order_id );
} catch ( Exception $exception ) {
$this->logger->error( "Couldn't sync tracking information: " . $exception->getMessage() );
return;
}
},
500,

View file

@ -17,12 +17,14 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
return array(
'paylater-configurator.url' => static function ( ContainerInterface $container ): string {
/**
* The return value must not contain a trailing slash.
*
* Cannot return false for this path.
*
* @psalm-suppress PossiblyFalseArgument
*/
return plugins_url(
'/modules/ppcp-paylater-configurator/',
'/modules/ppcp-paylater-configurator',
dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php'
);
},

View file

@ -38,6 +38,9 @@ document.addEventListener(
const subscriptionTrial = document.querySelector('._subscription_trial_length_field');
subscriptionTrial.style.display = 'none';
const soldIndividually = document.querySelector( '#_sold_individually' );
soldIndividually.setAttribute( 'disabled', 'disabled' );
}
const setupProducts = () => {

View file

@ -87,6 +87,44 @@ class PayPalSubscriptionsModule implements ModuleInterface {
12
);
add_filter(
'woocommerce_add_to_cart_validation',
/**
* Param types removed to avoid third-party issues.
*
* @psalm-suppress MissingClosureParamType
*/
static function ( $passed_validation, $product_id ) {
if ( WC()->cart->is_empty() ) {
return $passed_validation;
}
$product = wc_get_product( $product_id );
if ( $product && $product->get_meta( '_ppcp_enable_subscription_product', true ) === 'yes' ) {
if ( ! $product->get_sold_individually() ) {
$product->set_sold_individually( true );
$product->save();
}
wc_add_notice( __( 'You cannot add a subscription product to a cart with other items.', 'woocommerce-paypal-payments' ), 'error' );
return false;
}
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
$cart_product = wc_get_product( $cart_item['product_id'] );
if ( $cart_product && $cart_product->get_meta( '_ppcp_enable_subscription_product', true ) === 'yes' ) {
wc_add_notice( __( 'You can only have one subscription product in your cart.', 'woocommerce-paypal-payments' ), 'error' );
return false;
}
}
return $passed_validation;
},
10,
2
);
add_action(
'woocommerce_save_product_variation',
/**
@ -654,12 +692,18 @@ class PayPalSubscriptionsModule implements ModuleInterface {
// phpcs:ignore WordPress.Security.NonceVerification
$enable_subscription_product = wc_clean( wp_unslash( $_POST['_ppcp_enable_subscription_product'] ?? '' ) );
$product->update_meta_data( '_ppcp_enable_subscription_product', $enable_subscription_product );
if ( ! $product->get_sold_individually() ) {
$product->set_sold_individually( true );
}
$product->save();
if ( ( $product->get_type() === 'subscription' || $product->get_type() === 'subscription_variation' ) && $enable_subscription_product === 'yes' ) {
if ( $product->meta_exists( 'ppcp_subscription_product' ) && $product->meta_exists( 'ppcp_subscription_plan' ) ) {
$subscriptions_api_handler->update_product( $product );
$subscriptions_api_handler->update_plan( $product );
return;
}

View file

@ -61,17 +61,33 @@ $background-ident-color: #fbfbfb;
border: 1px solid #c3c4c7;
border-left-width: 4px;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
margin: 5px 0px 2px;
padding: 0px 12px 4px 12px;
margin: 5px 15px 2px 0;
padding: 1px 12px;
p, .form-table td & p {
margin-top: 4px;
margin-bottom: 4px;
}
.highlight {
background: transparent;
font-weight: 600;
}
}
.ppcp-notice-warning {
border-left-color: #dba617;
.highlight {
background: transparent;
color: #dba617;
font-weight: 600;
}
}
.ppcp-notice-error {
border-left-color: #d63638;
.highlight {
color: #d63638;
}
}

View file

@ -347,7 +347,6 @@ return array(
$container->get( 'api.partner_merchant_id-production' ),
$container->get( 'api.partner_merchant_id-sandbox' ),
$container->get( 'api.endpoint.billing-agreements' ),
$container->get( 'wc-subscriptions.helper' ),
$logger
);
},

View file

@ -12,6 +12,7 @@ namespace WooCommerce\PayPalCommerce\WcGateway\Endpoint;
use Psr\Log\LoggerInterface;
use RuntimeException;
use stdClass;
use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\RequestTrait;
@ -131,16 +132,25 @@ class CaptureCardPayment {
/**
* Creates PayPal order from the given card vault id.
*
* @param string $vault_id Vault id.
* @param string $custom_id Custom id.
* @param string $invoice_id Invoice id.
* @param string $vault_id Vault id.
* @param string $custom_id Custom id.
* @param string $invoice_id Invoice id.
* @param WC_Order $wc_order The WC order.
* @return stdClass
* @throws RuntimeException When request fails.
*/
public function create_order( string $vault_id, string $custom_id, string $invoice_id ): stdClass {
public function create_order( string $vault_id, string $custom_id, string $invoice_id, WC_Order $wc_order ): stdClass {
$intent = $this->settings->has( 'intent' ) && strtoupper( (string) $this->settings->get( 'intent' ) ) === 'AUTHORIZE' ? 'AUTHORIZE' : 'CAPTURE';
$items = array( $this->purchase_unit_factory->from_wc_cart() );
// phpcs:disable WordPress.Security.NonceVerification
$pay_for_order = wc_clean( wp_unslash( $_GET['pay_for_order'] ?? '' ) );
$order_key = wc_clean( wp_unslash( $_GET['key'] ?? '' ) );
// phpcs:enable
if ( $pay_for_order && $order_key === $wc_order->get_order_key() ) {
$items = array( $this->purchase_unit_factory->from_wc_order( $wc_order ) );
}
$data = array(
'intent' => $intent,
'purchase_units' => array_map(

View file

@ -490,7 +490,7 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
$custom_id = $wc_order->get_order_number();
$invoice_id = $this->prefix . $wc_order->get_order_number();
$create_order = $this->capture_card_payment->create_order( $token->get_token(), $custom_id, $invoice_id );
$create_order = $this->capture_card_payment->create_order( $token->get_token(), $custom_id, $invoice_id, $wc_order );
$order = $this->order_endpoint->order( $create_order->id );
$wc_order->update_meta_data( PayPalGateway::INTENT_META_KEY, $order->intent() );

View file

@ -57,12 +57,19 @@ class CartCheckoutDetector {
/**
* Check if the Checkout page is using Elementor.
*
* @param int $page_id The ID of the page.
*
* @return bool
*/
public static function has_elementor_checkout(): bool {
public static function has_elementor_checkout( int $page_id = 0 ): bool {
// Check if Elementor is installed and activated.
if ( did_action( 'elementor/loaded' ) ) {
$elementor_widgets = self::get_elementor_widgets( wc_get_page_id( 'checkout' ) );
if ( $page_id ) {
$elementor_widgets = self::get_elementor_widgets( $page_id );
} else {
// Check the WooCommerce checkout page.
$elementor_widgets = self::get_elementor_widgets( wc_get_page_id( 'checkout' ) );
}
if ( $elementor_widgets ) {
return in_array( 'woocommerce-checkout-page', $elementor_widgets, true );
@ -114,7 +121,7 @@ class CartCheckoutDetector {
*/
public static function has_classic_checkout(): bool {
$checkout_page_id = wc_get_page_id( 'checkout' );
return $checkout_page_id && has_block( 'woocommerce/classic-shortcode', $checkout_page_id );
return $checkout_page_id && ( has_block( 'woocommerce/classic-shortcode', $checkout_page_id ) || self::has_classic_shortcode( $checkout_page_id, 'woocommerce_checkout' ) );
}
/**
@ -124,6 +131,25 @@ class CartCheckoutDetector {
*/
public static function has_classic_cart(): bool {
$cart_page_id = wc_get_page_id( 'cart' );
return $cart_page_id && has_block( 'woocommerce/classic-shortcode', $cart_page_id );
return $cart_page_id && ( has_block( 'woocommerce/classic-shortcode', $cart_page_id ) || self::has_classic_shortcode( $cart_page_id, 'woocommerce_cart' ) );
}
/**
* Check if a page has a specific shortcode.
*
* @param int $page_id The ID of the page.
* @param string $shortcode The shortcode to check for.
*
* @return bool
*/
private static function has_classic_shortcode( int $page_id, string $shortcode ): bool {
if ( ! $page_id ) {
return false;
}
$page = get_post( $page_id );
$page_content = is_object( $page ) ? $page->post_content : '';
return str_contains( $page_content, $shortcode );
}
}

View file

@ -56,14 +56,14 @@ class PayUponInvoiceHelper {
}
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$billing_country = wc_clean( wp_unslash( $_POST['country'] ?? '' ) );
if ( $billing_country && 'DE' !== $billing_country ) {
$billing_country = WC()->customer->get_billing_country();
if ( empty( $billing_country ) || 'DE' !== $billing_country ) {
return false;
}
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$shipping_country = wc_clean( wp_unslash( $_POST['s_country'] ?? '' ) );
if ( $shipping_country && 'DE' !== $shipping_country ) {
$shipping_country = WC()->customer->get_shipping_country();
if ( empty( $shipping_country ) || 'DE' !== $shipping_country ) {
return false;
}

View file

@ -220,8 +220,8 @@ class OrderProcessor {
);
throw new PayPalOrderMissingException(
__(
'Could not retrieve order. Maybe it was already completed or this browser is not supported. Please check your email or try again with a different browser.',
esc_attr__(
'There was an error processing your order. Please check for any charges in your payment method and review your order history before placing the order again.',
'woocommerce-paypal-payments'
)
);

View file

@ -36,6 +36,8 @@ return function ( ContainerInterface $container, array $fields ): array {
</div>';
};
$axo_smart_button_location_notice = $container->has( 'axo.smart-button-location-notice' ) ? $container->get( 'axo.smart-button-location-notice' ) : '';
$smart_button_fields = array(
'button_style_heading' => array(
'heading' => __( 'PayPal Smart Buttons', 'woocommerce-paypal-payments' ),
@ -65,7 +67,7 @@ return function ( ContainerInterface $container, array $fields ): array {
'type' => 'ppcp-multiselect',
'input_class' => array( 'wc-enhanced-select' ),
'default' => $container->get( 'wcgateway.button.default-locations' ),
'description' => __( 'Select where the PayPal smart buttons should be displayed.', 'woocommerce-paypal-payments' ),
'description' => __( 'Select where the PayPal smart buttons should be displayed.', 'woocommerce-paypal-payments' ) . $axo_smart_button_location_notice,
'options' => $container->get( 'wcgateway.button.locations' ),
'screens' => array( State::STATE_START, State::STATE_ONBOARDED ),
'requirements' => array(),

View file

@ -10,8 +10,6 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Settings;
use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\AdminNotices\Entity\Message;
use WooCommerce\PayPalCommerce\AdminNotices\Repository\Repository;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingAgreementsEndpoint;
@ -23,9 +21,7 @@ use WooCommerce\PayPalCommerce\Onboarding\Helper\OnboardingUrl;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCProductStatus;
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceProductStatus;
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
use WooCommerce\PayPalCommerce\Webhooks\WebhookRegistrar;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\WooCommerce\Logging\Logger\NullLogger;
/**
@ -161,13 +157,6 @@ class SettingsListener {
*/
private $billing_agreements_endpoint;
/**
* The subscription helper
*
* @var SubscriptionHelper
*/
protected $subscription_helper;
/**
* The logger.
*
@ -193,7 +182,6 @@ class SettingsListener {
* @param string $partner_merchant_id_production Partner merchant ID production.
* @param string $partner_merchant_id_sandbox Partner merchant ID sandbox.
* @param BillingAgreementsEndpoint $billing_agreements_endpoint Billing Agreements endpoint.
* @param SubscriptionHelper $subscription_helper The subscription helper.
* @param ?LoggerInterface $logger The logger.
*/
public function __construct(
@ -212,7 +200,6 @@ class SettingsListener {
string $partner_merchant_id_production,
string $partner_merchant_id_sandbox,
BillingAgreementsEndpoint $billing_agreements_endpoint,
SubscriptionHelper $subscription_helper,
LoggerInterface $logger = null
) {
@ -231,7 +218,6 @@ class SettingsListener {
$this->partner_merchant_id_production = $partner_merchant_id_production;
$this->partner_merchant_id_sandbox = $partner_merchant_id_sandbox;
$this->billing_agreements_endpoint = $billing_agreements_endpoint;
$this->subscription_helper = $subscription_helper;
$this->logger = $logger ?: new NullLogger();
}
@ -394,7 +380,18 @@ class SettingsListener {
if ( $reference_transaction_enabled !== true ) {
$this->settings->set( 'vault_enabled', false );
$this->settings->set( 'subscriptions_mode', 'subscriptions_api' );
/**
* If Vaulting-API was previously enabled, then fall-back to the
* PayPal subscription mode, to ensure subscriptions are still
* possible on this shop.
*
* This can happen when switching to a different PayPal merchant account
*/
if ( 'vaulting_api' === $subscription_mode ) {
$this->settings->set( 'subscriptions_mode', 'subscriptions_api' );
}
$this->settings->persist();
}
@ -403,11 +400,6 @@ class SettingsListener {
$this->settings->persist();
}
if ( $this->subscription_helper->plugin_is_active() ) {
$this->settings->set( 'blocks_final_review_enabled', true );
$this->settings->persist();
}
if ( $subscription_mode === 'disable_paypal_subscriptions' && $vault_enabled === '1' ) {
$this->settings->set( 'vault_enabled', false );
$this->settings->persist();
@ -745,4 +737,28 @@ class SettingsListener {
}
}
/**
* Filter settings based on a condition.
*
* @param bool $condition The condition.
* @param string $setting_slug The setting slug.
* @param callable $filter_function The filter function.
* @param bool $persist Whether to persist the settings.
*/
public function filter_settings( bool $condition, string $setting_slug, callable $filter_function, bool $persist = true ): void {
if ( ! $this->is_valid_site_request() || State::STATE_ONBOARDED !== $this->state->current_state() ) {
return;
}
$existing_setting_value = $this->settings->has( $setting_slug ) ? $this->settings->get( $setting_slug ) : null;
if ( $condition ) {
$new_setting_value = $filter_function( $existing_setting_value );
$this->settings->set( $setting_slug, $new_setting_value );
if ( $persist ) {
$this->settings->persist();
}
}
}
}

View file

@ -1,6 +1,6 @@
{
"name": "woocommerce-paypal-payments",
"version": "2.7.1",
"version": "2.8.0",
"description": "WooCommerce PayPal Payments",
"repository": "https://github.com/woocommerce/woocommerce-paypal-payments",
"license": "GPL-2.0",
@ -94,6 +94,7 @@
"dotenv": "^16.0.3",
"npm-run-all": "^4.1.5",
"playwright": "^1.43.0",
"run-s": "^0.0.0"
"run-s": "^0.0.0",
"uuid": "^9.0.1"
}
}

View file

@ -4,7 +4,7 @@ Tags: woocommerce, paypal, payments, ecommerce, checkout, cart, pay later, apple
Requires at least: 5.3
Tested up to: 6.5
Requires PHP: 7.2
Stable tag: 2.7.1
Stable tag: 2.8.0
License: GPLv2
License URI: http://www.gnu.org/licenses/gpl-2.0.html
@ -179,7 +179,21 @@ If you encounter issues with the PayPal buttons not appearing after an update, p
== Changelog ==
= 2.7.1 - xxxx-xx-xx =
= 2.8.0 - 2024-06-11 =
* Fix - Calculate totals after adding shipping to include taxes #2296
* Fix - Package tracking integration throws error in 2.7.1 #2289
* Fix - Make PayPal Subscription products unique in cart #2265
* Fix - PayPal declares subscription support when merchant not enabled for Reference Transactions #2282
* Fix - Google Pay and Apple Pay Settings button from Connection tab have wrong links #2273
* Fix - Smart Buttons in Block Checkout not respecting the location setting (2830) #2278
* Fix - Disable Pay Upon Invoice if billing/shipping country not set #2281
* Fix - Critical error on pay for order page when we try to pay with ACDC gateway #2321
* Enhancement - Enable shipping callback for WC subscriptions #2259
* Enhancement - Disable the shipping callback for "venmo" when vaulting is active #2269
* Enhancement - Improve "Could not retrieve order" error message #2271
* Enhancement - Add block Checkout compatibility to Advanced Card Processing #2246
= 2.7.1 - 2024-05-28 =
* Fix - Ensure package tracking data is sent to original PayPal transaction #2180
* Fix - Set the 'Woo_PPCP' as a default value for data-partner-attribution-id #2188
* Fix - Allow PUI Gateway for refund processor #2192

View file

@ -60,7 +60,6 @@ class SettingsListenerTest extends ModularTestCase
'',
'',
$billing_agreement_endpoint,
$subscription_helper,
$logger
);

View file

@ -3,14 +3,14 @@
* Plugin Name: WooCommerce PayPal Payments
* Plugin URI: https://woocommerce.com/products/woocommerce-paypal-payments/
* Description: PayPal's latest complete payments processing solution. Accept PayPal, Pay Later, credit/debit cards, alternative digital wallets local payment types and bank accounts. Turn on only PayPal options or process a full suite of payment methods. Enable global transaction with extensive currency and country coverage.
* Version: 2.7.1
* Version: 2.8.0
* Author: WooCommerce
* Author URI: https://woocommerce.com/
* License: GPL-2.0
* Requires PHP: 7.2
* Requires Plugins: woocommerce
* WC requires at least: 3.9
* WC tested up to: 8.8
* WC tested up to: 8.9
* Text Domain: woocommerce-paypal-payments
*
* @package WooCommerce\PayPalCommerce
@ -26,7 +26,7 @@ define( 'PAYPAL_API_URL', 'https://api-m.paypal.com' );
define( 'PAYPAL_URL', 'https://www.paypal.com' );
define( 'PAYPAL_SANDBOX_API_URL', 'https://api-m.sandbox.paypal.com' );
define( 'PAYPAL_SANDBOX_URL', 'https://www.sandbox.paypal.com' );
define( 'PAYPAL_INTEGRATION_DATE', '2024-05-13' );
define( 'PAYPAL_INTEGRATION_DATE', '2024-06-03' );
! defined( 'CONNECT_WOO_CLIENT_ID' ) && define( 'CONNECT_WOO_CLIENT_ID', 'AcCAsWta_JTL__OfpjspNyH7c1GGHH332fLwonA5CwX4Y10mhybRZmHLA0GdRbwKwjQIhpDQy0pluX_P' );
! defined( 'CONNECT_WOO_SANDBOX_CLIENT_ID' ) && define( 'CONNECT_WOO_SANDBOX_CLIENT_ID', 'AYmOHbt1VHg-OZ_oihPdzKEVbU3qg0qXonBcAztuzniQRaKE0w1Hr762cSFwd4n8wxOl-TCWohEa0XM_' );

View file

@ -598,6 +598,11 @@ unbox-primitive@^1.0.2:
has-symbols "^1.0.3"
which-boxed-primitive "^1.0.2"
uuid@^9.0.1:
version "9.0.1"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30"
integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==
validate-npm-package-license@^3.0.1:
version "3.0.4"
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"