Merge pull request #3601 from woocommerce/trunk

Merge trunk into develop
This commit is contained in:
Emili Castells 2025-08-19 14:35:08 +02:00 committed by GitHub
commit 0535faad10
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 226 additions and 46 deletions

View file

@ -170,6 +170,10 @@ return array(
$matrix['GB'] = array( 'GBP' );
}
if ( $container->get( 'axo.au.enabled' ) ) {
$matrix['AU'] = array( 'AUD' );
}
/**
* Returns which countries and currency combinations can be used for AXO.
*/
@ -206,6 +210,14 @@ return array(
);
}
if ( $container->get( 'axo.au.enabled' ) ) {
$matrix['AU'] = array(
'VISA',
'MASTERCARD',
'AMEX',
);
}
/**
* Returns which countries and card type combinations can be used for AXO.
*/
@ -417,4 +429,17 @@ return array(
);
// phpcs:enable WordPress.NamingConventions.ValidHookName.UseUnderscores
},
'axo.au.enabled' => static function ( ContainerInterface $container ): bool {
// phpcs:disable WordPress.NamingConventions.ValidHookName.UseUnderscores
/**
* Filter to determine if Fastlane AU should be enabled.
*
* @param bool $enabled Whether Fastlane AU is enabled.
*/
return apply_filters(
'woocommerce.feature-flags.woocommerce_paypal_payments.axo_au_enabled',
getenv( 'PCP_AXO_AU_ENABLED' ) !== '0'
);
// phpcs:enable WordPress.NamingConventions.ValidHookName.UseUnderscores
},
);

View file

@ -13,6 +13,9 @@ use Exception;
use WC_Cart;
use WC_Order;
use WC_Order_Item_Product;
use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply;
use WooCommerce\PayPalCommerce\Settings\Data\SettingsModel;
use WooCommerce\PayPalCommerce\Settings\SettingsModule;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;
@ -91,6 +94,67 @@ class CompatModule implements ServiceModule, ExtendingModule, ExecutableModule {
$this->legacy_ui_card_payment_mapping( $c );
/**
* Automatically enable Pay Later messaging for eligible stores during plugin update.
*
* This action runs during plugin updates to automatically enable Pay Later messaging for stores
* that meet the following criteria:
* - Feature flag 'paylater_messaging_force_enabled' is enabled (default: true, can be disabled via filter)
* - Pay Later messaging is available for the store's country
* - The "Stay updated" checkbox is enabled (checked in either old or new UI)
*
* The "Stay updated" setting is retrieved differently based on the UI version:
* - Legacy UI: Retrieved from wcgateway.settings
* - New UI: Retrieved from settings.data.settings model
*
* When all conditions are met, this will:
* - Enable Pay Later messaging
* - Add default messaging locations (product, cart, checkout) to existing selections
*
* @todo Remove this auto-enablement logic after the next release
*
* @hook woocommerce_paypal_payments_gateway_migrate_on_update
*/
add_action(
'woocommerce_paypal_payments_gateway_migrate_on_update',
static function() use ( $c ) {
if ( ! apply_filters(
// phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
'woocommerce.feature-flags.woocommerce_paypal_payments.paylater_messaging_force_enabled',
true
) ) {
return;
}
$messages_apply = $c->get( 'button.helper.messages-apply' );
assert( $messages_apply instanceof MessagesApply );
$settings_model = $c->get( 'settings.data.settings' );
assert( $settings_model instanceof SettingsModel );
$settings = $c->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
$stay_updated = SettingsModule::should_use_the_old_ui()
? $settings->has( 'stay_updated' ) && $settings->get( 'stay_updated' )
: $settings_model->get_stay_updated();
if ( ! $messages_apply->for_country() || ! $stay_updated ) {
return;
}
$selected_locations = $settings->has( 'pay_later_messaging_locations' ) ? $settings->get( 'pay_later_messaging_locations' ) : array();
$settings->set( 'pay_later_messaging_enabled', true );
$settings->set(
'pay_later_messaging_locations',
array_unique( array_merge( $selected_locations, array( 'product', 'cart', 'checkout' ) ) )
);
$settings->persist();
}
);
return true;
}

View file

@ -10,6 +10,7 @@ const StayUpdated = () => {
return (
<SettingsBlock className="ppcp--pay-now-experience">
<ControlToggleButton
id="ppcp-stay-updated"
label={ __( 'Stay Updated', 'woocommerce-paypal-payments' ) }
description={ __(
'Get the latest PayPal features and capabilities as they are released. When the extension is updated, new features, payment methods, styling options, and more will automatically update.',

View file

@ -1,13 +1,7 @@
document.addEventListener( 'DOMContentLoaded', () => {
const config = ppcpSwitchSettingsUi;
const button = document.querySelector(
'.button.button-settings-switch-ui'
);
const link = document.querySelector(
'.ppcp-notice-wrapper:not(.inline) a.settings-switch-ui'
);
if ( typeof config === 'undefined' || ( ! button && ! link ) ) {
if ( typeof config === 'undefined' ) {
return;
}
@ -36,20 +30,24 @@ document.addEventListener( 'DOMContentLoaded', () => {
return response.json();
} )
.then( ( data ) => {
window.location.reload();
window.location.href = config.settingsUrl;
} )
.catch( ( error ) => {
console.error( 'Error:', error );
} );
};
if ( button ) {
button.addEventListener( 'click', handleClick );
}
document.addEventListener( 'click', ( event ) => {
const linkElement = event.target.closest( 'a.settings-switch-ui' );
if ( linkElement ) {
if (
event.target.closest( '.button.button-settings-switch-ui' ) ||
event.target.closest(
'.ppcp-notice-wrapper:not(.inline) a.settings-switch-ui'
) ||
event.target.closest( 'a[name="settings-switch-ui"]' ) ||
event.target
.closest( '.woocommerce-inbox-note__action-button' )
?.textContent.includes( 'Switch to New Settings' )
) {
handleClick( event );
}
} );

View file

@ -165,6 +165,7 @@ class SettingsModule implements ServiceModule, ExecutableModule {
'Are you sure you want to switch to the new settings interface?This action cannot be undone.',
'woocommerce-paypal-payments'
),
'settingsUrl' => admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway' ),
)
);

View file

@ -21,11 +21,13 @@ use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
use WooCommerce\PayPalCommerce\Applepay\ApplePayGateway;
use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway;
use WooCommerce\PayPalCommerce\Axo\Helper\PropertiesDictionary;
use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply;
use WooCommerce\PayPalCommerce\Button\Helper\MessagesDisclaimers;
use WooCommerce\PayPalCommerce\Common\Pattern\SingletonDecorator;
use WooCommerce\PayPalCommerce\Googlepay\GooglePayGateway;
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingOptionsRenderer;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Settings\Data\SettingsModel;
use WooCommerce\PayPalCommerce\Settings\SettingsModule;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Admin\FeesRenderer;
@ -2161,10 +2163,52 @@ return array(
*
* @returns InboxNoteInterface[]
*/
'wcgateway.settings.inbox-notes' => static function( ContainerInterface $container ): array {
'wcgateway.settings.inbox-notes' => static function( ContainerInterface $container ): array {
$inbox_note_factory = $container->get( 'wcgateway.settings.inbox-note-factory' );
assert( $inbox_note_factory instanceof InboxNoteFactory );
$settings = $container->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
$settings_model = $container->get( 'settings.data.settings' );
assert( $settings_model instanceof SettingsModel );
$messages_apply = $container->get( 'button.helper.messages-apply' );
assert( $messages_apply instanceof MessagesApply );
$is_working_capital_feature_flag_enabled = apply_filters(
// phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores -- feature flags use this convention
'woocommerce.feature-flags.woocommerce_paypal_payments.working_capital_enabled',
getenv( 'PCP_WORKING_CAPITAL_ENABLED' ) === '1'
);
$is_paylater_messaging_force_enabled_feature_flag_enabled = apply_filters(
// phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores -- feature flags use this convention
'woocommerce.feature-flags.woocommerce_paypal_payments.paylater_messaging_force_enabled',
true
);
$stay_updated = SettingsModule::should_use_the_old_ui()
? $settings->has( 'stay_updated' ) && $settings->get( 'stay_updated' )
: $settings_model->get_stay_updated();
$stay_updated_field_link = SettingsModule::should_use_the_old_ui()
? admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway&ppcp-tab=ppcp-connection#ppcp-stay_updated_field' )
: admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway&panel=settings#ppcp-stay-updated' );
$paylater_messaging_tab_link = SettingsModule::should_use_the_old_ui()
? admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway&ppcp-tab=ppcp-pay-later' )
: admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway&panel=pay-later-messaging' );
$message = sprintf(
// translators: %1$s is the URL for the startup guide.
__(
'We\'ve redesigned the settings for better performance and usability. Starting late October, this improved design will be the default for all WooCommerce installations to enjoy faster navigation, cleaner organization, and improved performance. Check out the <a href="%1$s" target="_blank">Startup Guide</a>, then click <a href="#" name="settings-switch-ui"><strong>Switch to New Settings</strong></a> to activate it.',
'woocommerce-paypal-payments'
),
'https://woocommerce.com/document/woocommerce-paypal-payments/paypal-payments-startup-guide/'
);
return array(
$inbox_note_factory->create_note(
__( 'PayPal Working Capital', 'woocommerce-paypal-payments' ),
@ -2172,6 +2216,7 @@ return array(
Note::E_WC_ADMIN_NOTE_INFORMATIONAL,
'ppcp-working-capital-inbox-note',
Note::E_WC_ADMIN_NOTE_UNACTIONED,
$is_working_capital_feature_flag_enabled && $container->get( 'api.shop.country' ) === 'US' && $stay_updated,
new InboxNoteAction(
'apply_now',
__( 'Apply now', 'woocommerce-paypal-payments' ),
@ -2180,6 +2225,43 @@ return array(
true
)
),
$inbox_note_factory->create_note(
__( '📢 Important: New PayPal Payments settings UI becoming default in October!', 'woocommerce-paypal-payments' ),
$message,
Note::E_WC_ADMIN_NOTE_INFORMATIONAL,
'ppcp-settings-migration-inbox-note',
Note::E_WC_ADMIN_NOTE_UNACTIONED,
SettingsModule::should_use_the_old_ui(),
new InboxNoteAction(
'switch_to_new_settings',
__( 'Switch to New Settings', 'woocommerce-paypal-payments' ),
admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway' ),
Note::E_WC_ADMIN_NOTE_UNACTIONED,
true
)
),
$inbox_note_factory->create_note(
__( 'Pay Later messaging now optimizing conversions', 'woocommerce-paypal-payments' ),
sprintf(
// translators: %1$s is the URL for Pay Later messaging documentation.
__(
'PayPal Pay Later messages are now displaying on your store through your <a href="%1$s">Stay Updated</a> preference, helping customers see flexible payment options. Merchants typically see 20-40% higher conversion rates with these purchase incentives. The messages appear contextually near prices, not as ads. You control this completely—disable anytime if your conversion metrics don\'t improve.',
'woocommerce-paypal-payments'
),
$stay_updated_field_link
),
Note::E_WC_ADMIN_NOTE_INFORMATIONAL,
'ppcp-settings-paylater-messaging-force-enabled-inbox-note',
Note::E_WC_ADMIN_NOTE_UNACTIONED,
$is_paylater_messaging_force_enabled_feature_flag_enabled && $messages_apply->for_country() && $stay_updated,
new InboxNoteAction(
'review_pay_later_settings',
__( 'Review Pay Later settings', 'woocommerce-paypal-payments' ),
$paylater_messaging_tab_link,
Note::E_WC_ADMIN_NOTE_UNACTIONED,
true
)
),
);
},

View file

@ -221,12 +221,16 @@ class OrderProcessor {
throw new Exception( __( 'Could not retrieve PayPal order.', 'woocommerce-paypal-payments' ) );
}
} else {
$this->logger->warning(
sprintf(
'No PayPal order ID found in order #%d meta.',
$wc_order->get_id()
)
);
$is_paypal_return = isset( $_GET['wc-ajax'] ) && wc_clean( wp_unslash( $_GET['wc-ajax'] ) ) === 'ppc-return-url'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( $is_paypal_return ) {
$this->logger->warning(
sprintf(
'No PayPal order ID found for WooCommerce order #%d.',
$wc_order->get_id()
)
);
}
throw new PayPalOrderMissingException(
esc_attr__(

View file

@ -17,6 +17,7 @@ class InboxNote implements InboxNoteInterface {
protected string $type;
protected string $name;
protected string $status;
protected bool $is_enabled;
protected InboxNoteActionInterface $action;
public function __construct(
@ -25,14 +26,16 @@ class InboxNote implements InboxNoteInterface {
string $type,
string $name,
string $status,
bool $is_enabled,
InboxNoteActionInterface $action
) {
$this->title = $title;
$this->content = $content;
$this->type = $type;
$this->name = $name;
$this->status = $status;
$this->action = $action;
$this->title = $title;
$this->content = $content;
$this->type = $type;
$this->name = $name;
$this->status = $status;
$this->is_enabled = $is_enabled;
$this->action = $action;
}
public function title(): string {
@ -55,6 +58,10 @@ class InboxNote implements InboxNoteInterface {
return $this->status;
}
public function is_enabled(): bool {
return $this->is_enabled;
}
public function action(): InboxNoteActionInterface {
return $this->action;
}

View file

@ -18,8 +18,9 @@ class InboxNoteFactory {
string $type,
string $name,
string $status,
bool $is_enabled,
InboxNoteActionInterface $action
): InboxNoteInterface {
return new InboxNote( $title, $content, $type, $name, $status, $action );
return new InboxNote( $title, $content, $type, $name, $status, $is_enabled, $action );
}
}

View file

@ -16,5 +16,6 @@ interface InboxNoteInterface {
public function type(): string;
public function name(): string;
public function status(): string;
public function is_enabled(): bool;
public function action(): InboxNoteActionInterface;
}

View file

@ -30,8 +30,17 @@ class InboxNoteRegistrar {
public function register(): void {
foreach ( $this->inbox_notes as $inbox_note ) {
$inbox_note_name = $inbox_note->name();
$existing_note = Notes::get_note_by_name( $inbox_note_name );
if ( Notes::get_note_by_name( $inbox_note_name ) ) {
if ( ! $inbox_note->is_enabled() ) {
if ( $existing_note instanceof Note ) {
$data_store = Notes::load_data_store();
$data_store->delete( $existing_note );
}
continue;
}
if ( $existing_note ) {
continue;
}

View file

@ -966,26 +966,13 @@ class WCGatewayModule implements ServiceModule, ExtendingModule, ExecutableModul
add_action(
'admin_init',
static function () use ( $container ): void {
$settings = $container->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
$is_working_capital_feature_flag_enabled = apply_filters(
// phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores -- feature flags use this convention
'woocommerce.feature-flags.woocommerce_paypal_payments.working_capital_enabled',
getenv( 'PCP_WORKING_CAPITAL_ENABLED' ) === '1'
);
$is_working_capital_eligible = $container->get( 'api.shop.country' ) === 'US' && $settings->has( 'stay_updated' ) && $settings->get( 'stay_updated' );
if ( ! $is_working_capital_feature_flag_enabled || ! $is_working_capital_eligible ) {
return;
}
$logger = $container->get( 'woocommerce.logger.woocommerce' );
assert( $logger instanceof LoggerInterface );
$inbox_note_registrar = $container->get( 'wcgateway.settings.inbox-note-registrar' );
assert( $inbox_note_registrar instanceof InboxNoteRegistrar );
try {
$inbox_note_registrar = $container->get( 'wcgateway.settings.inbox-note-registrar' );
assert( $inbox_note_registrar instanceof InboxNoteRegistrar );
$inbox_note_registrar->register();
} catch ( Exception $exception ) {
$logger->error( 'Failed to add note to the WooCommerce inbox section. ' . $exception->getMessage() );