Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
8467c25
Allow purchase of free trials with ECE
wjrosa Aug 4, 2025
c17d9de
Changelog and readme entries
wjrosa Aug 4, 2025
33e176c
Unit tests
wjrosa Aug 5, 2025
1e638dc
Merge branch 'develop' into add/allow-ece-purchases-for-free-trials
wjrosa Aug 5, 2025
63d45fd
Remove condition inside canMakePayment
wjrosa Aug 5, 2025
bfc3b1e
Merge branch 'develop' into add/allow-ece-purchases-for-free-trials
wjrosa Aug 19, 2025
cdca351
Temporary changes to debug
wjrosa Aug 19, 2025
79f7d4c
Merge branch 'develop' into add/allow-ece-purchases-for-free-trials
wjrosa Aug 26, 2025
da2cd5c
Merge branch 'develop' into add/allow-ece-purchases-for-free-trials
wjrosa Aug 27, 2025
48ebe0d
Method to check for existing free trial in cart
wjrosa Aug 27, 2025
fa57d69
Adding the JS property to flag the free trial
wjrosa Aug 27, 2025
1b99ba7
Checking for free trials to change the configuration mode parameter
wjrosa Aug 27, 2025
2daf2b9
Remove debug code
wjrosa Aug 27, 2025
9a71639
Revert unnecessary change
wjrosa Aug 27, 2025
c91368f
Revert unnecessary change
wjrosa Aug 27, 2025
3dc3e41
Helper class for WC_Subscriptions_Product
wjrosa Aug 27, 2025
41f23fc
Simplifying logic + unit test
wjrosa Aug 27, 2025
2ad897b
Merge branch 'develop' into add/allow-ece-purchases-for-free-trials
wjrosa Sep 5, 2025
ad40478
Fix payment method creation
wjrosa Sep 5, 2025
e6143d1
Merge branch 'develop' into add/allow-ece-purchases-for-free-trials
wjrosa Sep 11, 2025
0bd5184
Revert removal of free product check + adding the free trial check al…
wjrosa Sep 11, 2025
80d57e3
Adding the free trial parameter to missing methods
wjrosa Sep 11, 2025
ea853ca
Fix shortcode checkout
wjrosa Sep 12, 2025
c9dd64e
Simplifying setting of mode parameter
wjrosa Sep 12, 2025
66882bd
Merge branch 'develop' into add/allow-ece-purchases-for-free-trials
wjrosa Sep 12, 2025
886aa63
Merge branch 'develop' into add/allow-ece-purchases-for-free-trials
wjrosa Sep 15, 2025
0da2c62
Merge branch 'develop' into add/allow-ece-purchases-for-free-trials
wjrosa Sep 16, 2025
5ed09d9
Merge branch 'add/allow-ece-purchases-for-free-trials' of https://git…
wjrosa Sep 16, 2025
618e9af
Merge branch 'develop' into add/allow-ece-purchases-for-free-trials
wjrosa Sep 16, 2025
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
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
*** Changelog ***

= 9.10.0 - xxxx-xx-xx =
* Add - Allow the purchase of free trials using the Express Payment methods when the product does not require shipping
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QQ: why is it supported only for the products that do not require shipping? Is there any catch with the shippable products?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am honestly not sure. The original comment was included by you here. I just kept the limitation 😅 . However, maybe it is not needed at all (I can't imagine any reasons why it would be). Original code:

// Don't show if the total price is 0.
		// ToDo: support free trials. Free trials should be supported if the product does not require shipping.
		if ( ( ! ( $this->is_pay_for_order_page() || $this->is_product() ) && 0.0 === (float) WC()->cart->get_total( false ) )
			|| ( $this->is_product() && 0.0 === (float) $this->get_product()->get_price() )
		) {
			return false;
		}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me try to remember the reason 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, so I don't exactly remember the reason but I remembered following what we have in WooPayments. I found this epic where item 3 says the expected behavior is ECE would be hidden. This epic was created later but there were something in WooPayments that I followed at that time 🤔
Not a blocker for this PR, but we should assess separately if support for shippable products should/can be added.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the additional context! Yeah, I think we can tackle this specific rule later. I created STRIPE-715 for that

* Update - Changes the documentation page URL for the Optimized Checkout feature to https://woocommerce.com/document/stripe/admin-experience/optimized-checkout-suite/
* Update - Changes the background color and spacing for the Woo logo shown in the account modal
* Add - Allow Klarna to be used for recurring payments and subscriptions
Expand Down
8 changes: 6 additions & 2 deletions client/blocks/express-checkout/express-checkout-container.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ import {

export const ExpressCheckoutContainer = ( props ) => {
const { stripe, billing, expressPaymentMethod } = props;
const hasFreeTrial = getExpressCheckoutData( 'has_free_trial' );
const options = {
mode: 'payment',
...( isManualPaymentMethodCreation( expressPaymentMethod ) && {
mode: hasFreeTrial ? 'subscription' : 'payment',
...( isManualPaymentMethodCreation(
expressPaymentMethod,
hasFreeTrial
) && {
paymentMethodCreation: 'manual',
} ),
amount: billing.cartTotal.value,
Expand Down
6 changes: 5 additions & 1 deletion client/blocks/express-checkout/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
EXPRESS_PAYMENT_METHOD_SETTING_GOOGLE_PAY,
EXPRESS_PAYMENT_METHOD_SETTING_LINK,
} from 'wcstripe/stripe-utils/constants';
import { getExpressCheckoutData } from 'wcstripe/express-checkout/utils';

/** @typedef {import('react')} React */

Expand Down Expand Up @@ -83,7 +84,10 @@ const expressCheckoutElement = ( expressPaymentMethod, api ) => {
);
const edit = getEditorElement( expressPaymentMethod );
const canMakePayment = ( { cart } ) => {
if ( parseFloat( cart.cartTotals.total_price ) === 0.0 ) {
if (
parseFloat( cart.cartTotals.total_price ) === 0.0 &&
! getExpressCheckoutData( 'has_free_trial' )
) {
return false;
}

Expand Down
18 changes: 12 additions & 6 deletions client/entrypoints/express-checkout/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -331,11 +331,16 @@ jQuery( function ( $ ) {
return;
}

const hasFreeTrial = getExpressCheckoutData( 'has_free_trial' );

const elements = api.getStripe().elements( {
mode: options.mode ? options.mode : 'payment',
mode: hasFreeTrial ? 'subscription' : 'payment',
amount: options.total,
currency: options.currency,
...( isManualPaymentMethodCreation( expressPaymentType ) && {
...( isManualPaymentMethodCreation(
expressPaymentType,
hasFreeTrial
) && {
paymentMethodCreation: 'manual',
} ),
appearance: getExpressCheckoutButtonAppearance(),
Expand Down Expand Up @@ -462,6 +467,7 @@ jQuery( function ( $ ) {
event,
order,
orderDetails,
hasFreeTrial,
} );
} );

Expand Down Expand Up @@ -518,7 +524,6 @@ jQuery( function ( $ ) {
}

wcStripeECE.startExpressCheckout( {
mode: 'payment',
total,
currency:
getExpressCheckoutData( 'checkout' ).currency_code,
Expand All @@ -538,7 +543,6 @@ jQuery( function ( $ ) {
const displayItems =
getExpressCheckoutData( 'product' ).displayItems ?? [];
wcStripeECE.startExpressCheckout( {
mode: 'payment',
total: getExpressCheckoutData( 'product' )?.total
.amount,
currency: getExpressCheckoutData( 'product' )?.currency,
Expand All @@ -562,13 +566,15 @@ jQuery( function ( $ ) {
cart.totals
);

if ( total === 0 ) {
if (
total === 0 &&
! getExpressCheckoutData( 'has_free_trial' )
) {
wcStripeECE.hide();
return;
}

wcStripeECE.startExpressCheckout( {
mode: 'payment',
total,
currency:
getExpressCheckoutData( 'checkout' )?.currency_code,
Expand Down
9 changes: 7 additions & 2 deletions client/express-checkout/event-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,19 @@ export const shippingRateChangeHandler = async ( api, event, elements ) => {
};

export const onConfirmHandler = async ( params ) => {
const { abortPayment, elements, event } = params;
const { abortPayment, elements, event, hasFreeTrial } = params;

const submitResponse = await elements.submit();
if ( submitResponse?.error ) {
return abortPayment( event, submitResponse?.error?.message );
}

if ( ! isManualPaymentMethodCreation( event.expressPaymentType ) ) {
if (
! isManualPaymentMethodCreation(
event.expressPaymentType,
hasFreeTrial
)
) {
return handleConfirmationTokenFlow( params );
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createRoot } from 'react-dom/client';
import { ExpressCheckoutElement, Elements } from '@stripe/react-stripe-js';
import { memoize } from 'lodash';
import {
getExpressCheckoutData,
getPaymentMethodTypesForExpressMethod,
isManualPaymentMethodCreation,
} from 'wcstripe/express-checkout/utils';
Expand All @@ -15,6 +16,8 @@ import {
export const checkPaymentMethodIsAvailable = memoize(
( paymentMethod, api, cart ) => {
return new Promise( ( resolve ) => {
const hasFreeTrial = getExpressCheckoutData( 'has_free_trial' );

// Create the DIV container on the fly
const containerEl = document.createElement( 'div' );

Expand All @@ -29,8 +32,11 @@ export const checkPaymentMethodIsAvailable = memoize(
<Elements
stripe={ api.loadStripe() }
options={ {
mode: 'payment',
...( isManualPaymentMethodCreation( paymentMethod ) && {
mode: hasFreeTrial ? 'subscription' : 'payment',
...( isManualPaymentMethodCreation(
paymentMethod,
hasFreeTrial
) && {
paymentMethodCreation: 'manual',
} ),
amount: Number( cart.cartTotals.total_price ),
Expand Down
18 changes: 12 additions & 6 deletions client/express-checkout/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -389,12 +389,18 @@ export const expressCheckoutNoticeDelay = async () => {
/**
* Determine if the express payment type should use manual payment method creation.
*
* @param {string} expressPaymentType The express payment type, e.g 'googlePay' or 'google_pay'
* @param {string} expressPaymentType The express payment type, e.g 'googlePay' or 'google_pay'
* @param {boolean} hasFreeTrial Whether the product being purchased has a free trial.
* @return {boolean} True if manual payment method creation should be used, false otherwise.
*/
export const isManualPaymentMethodCreation = ( expressPaymentType ) => {
return ! [
EXPRESS_PAYMENT_METHOD_SETTING_AMAZON_PAY,
PAYMENT_METHOD_AMAZON_PAY,
].includes( expressPaymentType );
export const isManualPaymentMethodCreation = (
expressPaymentType,
hasFreeTrial
) => {
return (
! [
EXPRESS_PAYMENT_METHOD_SETTING_AMAZON_PAY,
PAYMENT_METHOD_AMAZON_PAY,
].includes( expressPaymentType ) || hasFreeTrial
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ public function javascript_params() {
'taxes_based_on_billing' => wc_tax_enabled() && get_option( 'woocommerce_tax_based_on' ) === 'billing',
'allowed_shipping_countries' => $this->express_checkout_helper->get_allowed_shipping_countries(),
'custom_checkout_fields' => ( new WC_Stripe_Express_Checkout_Custom_Fields() )->get_custom_checkout_fields(),
'has_free_trial' => $this->express_checkout_helper->has_free_trial(),
];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,31 @@ public function has_subscription_product() {
return false;
}

/**
* Checks whether the subscription product has a free trial.
*
* @return bool
*/
public function has_free_trial() {
if ( $this->is_product() ) {
$product = $this->get_product();
if ( ! $product ) {
return false;
}
if ( class_exists( 'WC_Subscriptions_Product' )
&& WC_Subscriptions_Product::is_subscription( $product )
&& WC_Subscriptions_Product::get_trial_length( $product ) > 0 ) {
return true;
}
} elseif ( WC_Stripe_Helper::has_cart_or_checkout_on_current_page() ) {
if ( class_exists( 'WC_Subscriptions_Cart' ) && WC_Subscriptions_Cart::cart_contains_free_trial() ) {
return true;
}
}

return false;
}

/**
* Checks if this is a product page or content contains a product_page shortcode.
*
Expand Down Expand Up @@ -685,10 +710,9 @@ public function should_show_express_checkout_button() {
return false;
}

// Don't show in the product page if the product price is 0.
// ToDo: support free trials. Free trials should be supported if the product does not require shipping.
if ( $is_product && $product && 0.0 === (float) $product->get_price() ) {
WC_Stripe_Logger::log( 'Stripe Express Checkout does not support free products.' );
// Don't show in the product page if the product price is 0 and the product requires shipping.
if ( $is_product && $product && 0.0 === (float) $product->get_price() && $this->product_or_cart_needs_shipping() ) {
WC_Stripe_Logger::log( 'Stripe Express Checkout does not support free products that requires shipping.' );
return false;
}

Expand Down
1 change: 1 addition & 0 deletions readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ If you get stuck, you can ask for help in the [Plugin Forum](https://wordpress.o
== Changelog ==

= 9.10.0 - xxxx-xx-xx =
* Add - Allow the purchase of free trials using the Express Payment methods when the product does not require shipping
* Update - Changes the documentation page URL for the Optimized Checkout feature to https://woocommerce.com/document/stripe/admin-experience/optimized-checkout-suite/
* Update - Changes the background color and spacing for the Woo logo shown in the account modal
* Add - Allow Klarna to be used for recurring payments and subscriptions
Expand Down
61 changes: 61 additions & 0 deletions tests/phpunit/Helpers/WC_Subscriptions_Product.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php
/**
* Subscription WC_Subscriptions_Product helper.
*/

/**
* Class WC_Subscriptions_Product.
*
* This helper class should ONLY be used for unit tests!.
*/
class WC_Subscriptions_Product {
/**
* The mocked value for get_trial_length().
*
* @var int
*/
public static int $get_trial_length_result;

/**
* The mocked value for is_subscription().
*
* @var bool
*/
public static bool $is_subscription_result;

/**
* Get the length of the trial period for a subscription product.
*
* @return int
*/
public static function get_trial_length(): int {
return self::$get_trial_length_result;
}

/**
* @param int $result The value to return from get_trial_length().
* @return void
*/
public static function set_trial_length( int $result ): void {
self::$get_trial_length_result = $result;
}

/**
* Determine if a product is a subscription.
*
* @return bool
*/
public static function is_subscription(): bool {
return self::$is_subscription_result;
}

/**
* Set the value to be returned from is_subscription().
*
* @param bool $result The value to return from is_subscription().
* @return void
*/
public static function set_is_subscription( bool $result ): void {
self::$is_subscription_result = $result;
}
}
Loading
Loading