Skip to content
Draft
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
776db9f
fix: subscriptions with free trial in ECE
frosso Feb 10, 2026
53d8e14
Merge branch 'develop' into fix/subscriptions-with-free-trial-ece
frosso Feb 19, 2026
512e4d4
wip
frosso Feb 19, 2026
172d9a7
fix data type mismatch
frosso Feb 19, 2026
beb95c8
avoiding additional cart api call
frosso Feb 19, 2026
c98b3e4
fix notice
frosso Feb 19, 2026
eb42bb1
fix display items on block-based pages
frosso Feb 19, 2026
192fe7a
wip
frosso Feb 19, 2026
301c3a4
tests
frosso Feb 19, 2026
859279b
wip
frosso Feb 19, 2026
a2c7a51
wip
frosso Feb 19, 2026
d1473df
fix rate selection
frosso Feb 19, 2026
4e36da7
Merge branch 'develop' into fix/subscriptions-with-free-trial-ece
frosso Feb 19, 2026
4a4f9ac
undo unwanted change
frosso Feb 20, 2026
f6cf746
Merge branch 'develop' into fix/subscriptions-with-free-trial-ece
frosso Feb 20, 2026
4511540
feat: dynamic place order button - full
frosso Feb 20, 2026
82082c8
better structure
frosso Feb 20, 2026
c7159a0
the things we do for consistency
frosso Feb 20, 2026
7f794f6
wip
frosso Feb 20, 2026
430c2c0
wip
frosso Feb 20, 2026
d33cafa
wip
frosso Feb 20, 2026
14e1558
more cleanup
frosso Feb 20, 2026
2ad0496
wip
frosso Feb 20, 2026
27b01b2
Merge branch 'develop' into fix/subscriptions-with-free-trial-ece
frosso Feb 20, 2026
3166864
Merge branch 'develop' into fix/subscriptions-with-free-trial-ece
frosso Feb 20, 2026
7b27f42
Merge branch 'fix/subscriptions-with-free-trial-ece' into feat/dynami…
frosso Feb 20, 2026
3869965
wip some refactoring
frosso Feb 20, 2026
c51e523
attempting to re-organize files and create chunks
frosso Feb 20, 2026
3578ae2
Merge branch 'develop' into fix/subscriptions-with-free-trial-ece
frosso Feb 21, 2026
a0d4b13
Merge branch 'fix/subscriptions-with-free-trial-ece' into feat/dynami…
frosso Feb 21, 2026
5cd9c8e
wip
frosso Feb 21, 2026
9922cae
Merge branch 'develop' into feat/dynamic-place-order-buttons-combo-im…
frosso Feb 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions changelog/fix-subscriptions-with-free-trial-ece
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: fix

fix: ensure that subscriptions with free trials are consistently handled by the ECE
37 changes: 31 additions & 6 deletions client/checkout/blocks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,8 @@ import PaymentMethodLabel from './payment-method-label';
import request from '../utils/request';
import enqueueFraudScripts from 'fraud-scripts';
import {
expressCheckoutElementApplePay,
expressCheckoutElementGooglePay,
expressCheckoutElementAmazonPay,
makeExpressCheckoutElement,
makeDynamicPlaceOrderButton,
} from 'wcpay/express-checkout/blocks';

import { getDeferredIntentCreationUPEFields } from './payment-elements';
Expand All @@ -46,8 +45,11 @@ const api = new WCPayAPI(
request
);

const EXPRESS_CHECKOUT_METHODS = [ 'apple_pay', 'google_pay', 'amazon_pay' ];

Object.entries( enabledPaymentMethodsConfig )
.filter( ( [ upeName ] ) => upeName !== 'link' )
.filter( ( [ upeName ] ) => ! EXPRESS_CHECKOUT_METHODS.includes( upeName ) )
.forEach( ( [ upeName, upeConfig ] ) => {
// Label component renders the payment method title using the standard
// PaymentMethodLabel from WooCommerce Blocks, with icons as a sibling
Expand Down Expand Up @@ -137,16 +139,39 @@ if (
getUPEConfig( 'isPaymentRequestEnabled' ) &&
! getUPEConfig( 'isExpressCheckoutInPaymentMethodsEnabled' )
) {
registerExpressPaymentMethod( expressCheckoutElementApplePay( api ) );
registerExpressPaymentMethod( expressCheckoutElementGooglePay( api ) );
registerExpressPaymentMethod(
makeExpressCheckoutElement( api, 'applePay' )
);
registerExpressPaymentMethod(
makeExpressCheckoutElement( api, 'googlePay' )
);
}

if (
getUPEConfig( 'isAmazonPayEnabled' ) &&
! getUPEConfig( 'isExpressCheckoutInPaymentMethodsEnabled' )
) {
registerExpressPaymentMethod( expressCheckoutElementAmazonPay( api ) );
registerExpressPaymentMethod(
makeExpressCheckoutElement( api, 'amazonPay' )
);
}

// Register dynamic place order buttons when express checkout methods
// should appear in the payment methods list instead of as separate buttons.
if ( getUPEConfig( 'isExpressCheckoutInPaymentMethodsEnabled' ) ) {
if ( getUPEConfig( 'isPaymentRequestEnabled' ) ) {
registerPaymentMethod( makeDynamicPlaceOrderButton( api, 'applePay' ) );
registerPaymentMethod(
makeDynamicPlaceOrderButton( api, 'googlePay' )
);
}
if ( getUPEConfig( 'isAmazonPayEnabled' ) ) {
registerPaymentMethod(
makeDynamicPlaceOrderButton( api, 'amazonPay' )
);
}
}

window.addEventListener( 'load', () => {
enqueueFraudScripts( getUPEConfig( 'fraudServices' ) );
addCheckoutTracking();
Expand Down
117 changes: 117 additions & 0 deletions client/checkout/classic/__tests__/express-payment-methods.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/**
* Internal dependencies
*/
import { initExpressPaymentMethods } from '../express-payment-methods';
import { getUPEConfig } from 'wcpay/utils/checkout';

jest.mock( 'wcpay/utils/checkout', () => ( {
getUPEConfig: jest.fn(),
} ) );

jest.mock( 'wcpay/checkout/utils/fingerprint', () => ( {
getFingerprint: jest.fn().mockResolvedValue( { visitorId: 'fp_123' } ),
appendFingerprintInputToForm: jest.fn(),
} ) );

jest.mock( 'wcpay/express-checkout/utils/payment-method-overrides', () => ( {
getPaymentMethodsOverride: jest.fn( () => ( {
paymentMethods: { applePay: 'always', googlePay: 'never' },
} ) ),
} ) );

describe( 'initExpressPaymentMethods', () => {
beforeEach( () => {
jest.clearAllMocks();

// Reset the global wc object
delete global.wc;
} );

test( 'does nothing when wc.customPlaceOrderButton API is not available', () => {
global.wc = undefined;

getUPEConfig.mockReturnValue( true );

// Should not throw
initExpressPaymentMethods( {} );
} );

test( 'does nothing when wc object exists but customPlaceOrderButton is missing', () => {
global.wc = {};

getUPEConfig.mockReturnValue( true );

initExpressPaymentMethods( {} );
} );

test( 'does nothing when feature flag is disabled', () => {
const mockRegister = jest.fn();
global.wc = {
customPlaceOrderButton: {
register: mockRegister,
},
};

getUPEConfig.mockImplementation( ( key ) => {
if ( key === 'isExpressCheckoutInPaymentMethodsEnabled' ) {
return false;
}
return null;
} );

initExpressPaymentMethods( {} );

expect( mockRegister ).not.toHaveBeenCalled();
} );

test( 'calls registerExpressPaymentMethods when API is available and feature is enabled', () => {
const mockRegister = jest.fn();
global.wc = {
customPlaceOrderButton: {
register: mockRegister,
},
};

getUPEConfig.mockImplementation( ( key ) => {
if ( key === 'isExpressCheckoutInPaymentMethodsEnabled' ) {
return true;
}
if ( key === 'currency' ) {
return 'usd';
}
if ( key === 'cartTotal' ) {
return 1000;
}
if ( key === 'paymentMethodsConfig' ) {
return {
apple_pay: {
isExpressCheckout: true,
gatewayId: 'woocommerce_payments_apple_pay',
},
};
}
return null;
} );

// Mock the DOM for checkExpressPaymentMethodsAvailability
const mockApi = {
getStripe: jest.fn().mockResolvedValue( {
elements: jest.fn().mockReturnValue( {
create: jest.fn().mockReturnValue( {
on: jest.fn(),
mount: jest.fn(),
unmount: jest.fn(),
} ),
} ),
} ),
};

initExpressPaymentMethods( mockApi );

// The function is async internally but we can verify it started
// by checking that the feature flag was consulted.
expect( getUPEConfig ).toHaveBeenCalledWith(
'isExpressCheckoutInPaymentMethodsEnabled'
);
} );
} );
14 changes: 14 additions & 0 deletions client/checkout/classic/event-handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* Internal dependencies
*/
import './style.scss';
import { initExpressPaymentMethods } from './express-payment-methods';
import { getUPEConfig } from 'wcpay/utils/checkout';
import { isLinkEnabled } from '../utils/upe';
import {
Expand Down Expand Up @@ -64,6 +65,9 @@ jQuery( function ( $ ) {
apiRequest
);

// Initialize express payment methods (Apple Pay, Google Pay) in the payment methods list.
initExpressPaymentMethods( api );

blockUI( $forms );
showAuthenticationModalIfRequired( api ).finally( () => {
unblockUI( $forms );
Expand All @@ -72,6 +76,8 @@ jQuery( function ( $ ) {
$( document.body ).on( 'updated_checkout', () => {
maybeMountStripePaymentElement( 'shortcode_checkout' );
injectPaymentMethodLogos();
// Re-check express payment method availability after checkout updates.
initExpressPaymentMethods( api );
} );

$( `[name="${ SHORTCODE_BILLING_ADDRESS_FIELDS.country }"]` ).on(
Expand Down Expand Up @@ -374,6 +380,14 @@ jQuery( function ( $ ) {

function processPaymentIfNotUsingSavedMethod( $form ) {
const paymentMethodType = getSelectedUPEGatewayPaymentMethod();
// Skip express checkout methods — they handle their own payment flow
// via the Custom Place Order Button API.
const paymentMethodConfig = getUPEConfig( 'paymentMethodsConfig' )?.[
paymentMethodType
];
if ( paymentMethodConfig?.isExpressCheckout ) {
return;
}
if ( ! isUsingSavedPaymentMethod( paymentMethodType ) ) {
return processPayment( api, $form, paymentMethodType );
}
Expand Down
Loading
Loading