Skip to content

Commit 16f1dbf

Browse files
authored
Adding the Single Payment Element feature to the block checkout (#4023)
* Initial work for SPE in the blocks checkout * Initial work for SPE in the blocks checkout * Including method to check if SPE is enabled * Implementing the new method * Implementing the new method * Fix payment method types param * Adding specific unit tests * Putting back tests removed by mistake * Putting back tests removed by mistake * Replacing payment method configuration ID * Changelog and readme entries * Mninor change to PHP code coverage script * Updating feature flag case
1 parent 52bbd2d commit 16f1dbf

File tree

9 files changed

+93
-19
lines changed

9 files changed

+93
-19
lines changed

.github/workflows/php-code-coverage.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,5 @@ jobs:
7070
number: ${{ github.event.number }}
7171
header: code-coverage
7272
recreate: true
73-
skip_unchanged: true
7473
path: code-coverage-results-filtered.md
7574

changelog.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
*** Changelog ***
22

33
= 9.3.0 - xxxx-xx-xx =
4+
* Add - Implements the Single Payment Element feature for the new checkout experience on the block checkout page.
45
* Dev - Additional replacements for payment method constant values on the backend.
56
* Fix - Improves the checking for existing customer attribute when retrieving a payment method that may be detached from a subscription.
67
* Fix - Reverts the default value for the `capture_method` property to avoid breaking Amazon Pay when creating a payment intent.

client/blocks/upe/index.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
registerExpressPaymentMethod,
44
} from '@woocommerce/blocks-registry';
55
import {
6+
PAYMENT_METHOD_CARD,
67
PAYMENT_METHOD_GIROPAY,
78
PAYMENT_METHOD_LINK,
89
} from '../../stripe-utils/constants';
@@ -42,11 +43,16 @@ const methodsToFilter = [
4243
];
4344

4445
// Register UPE Elements.
45-
Object.entries( paymentMethodsConfig )
46-
.filter( ( [ method ] ) => ! methodsToFilter.includes( method ) )
47-
.forEach( ( [ method, config ] ) => {
48-
registerPaymentMethod( upeElement( method, api, config ) );
49-
} );
46+
if ( getBlocksConfiguration()?.isSPEEnabled ) {
47+
const config = { ...paymentMethodsConfig.card, title: 'Stripe' };
48+
registerPaymentMethod( upeElement( PAYMENT_METHOD_CARD, api, config ) );
49+
} else {
50+
Object.entries( paymentMethodsConfig )
51+
.filter( ( [ method ] ) => ! methodsToFilter.includes( method ) )
52+
.forEach( ( [ method, config ] ) => {
53+
registerPaymentMethod( upeElement( method, api, config ) );
54+
} );
55+
}
5056

5157
// Register Express Checkout Elements.
5258
if (

client/blocks/upe/upe-deferred-intent-creation/payment-elements.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,27 @@ const PaymentElements = ( {
129129
mode: amount < 1 ? 'setup' : 'payment',
130130
amount,
131131
currency: getBlocksConfiguration()?.currency.toLowerCase(),
132-
paymentMethodTypes: getPaymentMethodTypes( paymentMethodId ),
133132
},
134133
};
135134

135+
if ( getBlocksConfiguration()?.isSPEEnabled ) {
136+
options = {
137+
...options,
138+
...{
139+
paymentMethodConfiguration: 'pmc_...',
140+
},
141+
};
142+
} else {
143+
options = {
144+
...options,
145+
...{
146+
paymentMethodTypes: getPaymentMethodTypes(
147+
paymentMethodId
148+
),
149+
},
150+
};
151+
}
152+
136153
// If the cart contains a subscription or the payment method supports saving, we need to use off_session setup so Stripe can display appropriate terms and conditions.
137154
if (
138155
getBlocksConfiguration()?.cartContainsSubscription ||

client/blocks/upe/upe-deferred-intent-creation/payment-processor.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ const getStripeElementOptions = () => {
5252
applePay: 'never',
5353
googlePay: 'never',
5454
},
55+
layout: 'accordion',
5556
};
5657

5758
// Prefill Link customer data if available.

includes/class-wc-stripe-intent-controller.php

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -767,10 +767,6 @@ public function create_and_confirm_payment_intent( $payment_information ) {
767767
$request['payment_method_options'] = $payment_information['payment_method_options'];
768768
}
769769

770-
if ( $this->request_needs_redirection( $payment_method_types ) ) {
771-
$request['return_url'] = $payment_information['return_url'];
772-
}
773-
774770
// Using a saved token will also be confirmed immediately. For voucher and wallet payment methods type like Boleto, Oxxo, Multibanco, and Cash App we shouldn't confirm
775771
// the intent immediately as this is done on the front-end when displaying the voucher to the customer.
776772
// When the intent is confirmed, Stripe sends a webhook to the store which puts the order on-hold, which we only want to happen after successfully displaying the voucher.
@@ -965,7 +961,8 @@ private function build_base_payment_intent_request_params( $payment_information
965961
$request = WC_Stripe_Helper::add_mandate_data( $request );
966962
}
967963

968-
if ( $this->request_needs_redirection( $payment_method_types ) ) {
964+
// Does not set the return URL if Single Payment Element is enabled or if the request needs redirection.
965+
if ( $this->get_upe_gateway()->is_spe_enabled() || $this->request_needs_redirection( $payment_method_types ) ) {
969966
$request['return_url'] = $payment_information['return_url'];
970967
}
971968

@@ -1042,7 +1039,8 @@ public function create_and_confirm_setup_intent( $payment_information ) {
10421039
$request['confirm'] = 'false';
10431040
}
10441041

1045-
if ( ! $this->request_needs_redirection( $request['payment_method_types'] ) ) {
1042+
// Removes the return URL if Single Payment Element is not enabled or if the request doesn't need redirection.
1043+
if ( ! ( $this->get_upe_gateway()->is_spe_enabled() || $this->request_needs_redirection( $request['payment_method_types'] ) ) ) {
10461044
unset( $request['return_url'] );
10471045
}
10481046

includes/payment-methods/class-wc-stripe-upe-payment-gateway.php

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,13 @@ class WC_Stripe_UPE_Payment_Gateway extends WC_Gateway_Stripe {
102102
*/
103103
public $sepa_tokens_for_other_methods;
104104

105+
/**
106+
* Is Single Payment Element enabled?
107+
*
108+
* @var bool
109+
*/
110+
public $spe_enabled;
111+
105112
/**
106113
* API access secret key
107114
*
@@ -211,6 +218,7 @@ public function __construct() {
211218
$this->enabled = $this->get_option( 'enabled' );
212219
$this->saved_cards = 'yes' === $this->get_option( 'saved_cards' );
213220
$this->sepa_tokens_for_other_methods = 'yes' === $this->get_option( 'sepa_tokens_for_other_methods' );
221+
$this->spe_enabled = WC_Stripe_Feature_Flags::is_spe_available() && 'yes' === $this->get_option( 'single_payment_element' );
214222
$this->testmode = WC_Stripe_Mode::is_test();
215223
$this->publishable_key = ! empty( $main_settings['publishable_key'] ) ? $main_settings['publishable_key'] : '';
216224
$this->secret_key = ! empty( $main_settings['secret_key'] ) ? $main_settings['secret_key'] : '';
@@ -464,6 +472,9 @@ public function javascript_params() {
464472
// BLIK LPM Feature flag.
465473
$stripe_params['is_blik_enabled'] = WC_Stripe_Feature_Flags::is_blik_lpm_enabled();
466474

475+
// Single Payment Element feature flag + setting.
476+
$stripe_params['isSPEEnabled'] = $this->spe_enabled;
477+
467478
$cart_total = ( WC()->cart ? WC()->cart->get_total( '' ) : 0 );
468479
$currency = get_woocommerce_currency();
469480

@@ -1753,6 +1764,15 @@ public function is_sepa_tokens_for_other_methods_enabled() {
17531764
return $this->sepa_tokens_for_other_methods;
17541765
}
17551766

1767+
/**
1768+
* Checks if the Single Payment Element setting is enabled.
1769+
*
1770+
* @return bool Whether the Single Payment Element setting is enabled.
1771+
*/
1772+
public function is_spe_enabled() {
1773+
return $this->spe_enabled;
1774+
}
1775+
17561776
/**
17571777
* Set formatted readable payment method title for order,
17581778
* using payment method details from accompanying charge.
@@ -2187,10 +2207,13 @@ protected function prepare_payment_information_from_request( WC_Order $order ) {
21872207
$payment_method_id = sanitize_text_field( wp_unslash( $_POST['wc-stripe-payment-method'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
21882208
}
21892209

2210+
$payment_method_details = ! empty( $payment_method_id ) ? WC_Stripe_API::get_payment_method( $payment_method_id ) : (object) [];
2211+
21902212
$payment_method_types = $this->get_payment_method_types_for_intent_creation(
21912213
$selected_payment_type,
21922214
$order->get_id(),
2193-
$this->get_express_payment_type_from_request()
2215+
$this->get_express_payment_type_from_request(),
2216+
( $payment_method_details->type ?? null )
21942217
);
21952218

21962219
$payment_information = [
@@ -2210,7 +2233,7 @@ protected function prepare_payment_information_from_request( WC_Order $order ) {
22102233
'use_stripe_sdk' => 'true', // We want to use the SDK to handle next actions via the client payment elements. See https://docs.stripe.com/api/setup_intents/create#create_setup_intent-use_stripe_sdk
22112234
'has_subscription' => $this->has_subscription( $order->get_id() ),
22122235
'payment_method' => '',
2213-
'payment_method_details' => [],
2236+
'payment_method_details' => $payment_method_details,
22142237
'payment_type' => 'single', // single | recurring.
22152238
];
22162239

@@ -2219,9 +2242,7 @@ protected function prepare_payment_information_from_request( WC_Order $order ) {
22192242
}
22202243

22212244
if ( ! empty( $payment_method_id ) ) {
2222-
$payment_method_details = WC_Stripe_API::get_payment_method( $payment_method_id );
22232245
$payment_information['payment_method'] = $payment_method_id;
2224-
$payment_information['payment_method_details'] = $payment_method_details;
22252246
$payment_information['save_payment_method_to_store'] = $save_payment_method_to_store;
22262247
$payment_information['payment_method_options'] = $this->get_payment_method_options(
22272248
$selected_payment_type,
@@ -2688,15 +2709,22 @@ private function get_existing_compatible_payment_intent( $order, $payment_method
26882709
*
26892710
* @param string $selected_payment_type The payment type the shopper selected, if any.
26902711
* @param int $order_id ID of the WC order we're handling.
2691-
* @param string|void $express_payment_type The express payment type, if any.
2712+
* @param string|null $express_payment_type The express payment type, if any.
2713+
* @param string|null $payment_method_type The payment method type, if any.
26922714
*
26932715
* @return array
26942716
*/
26952717
private function get_payment_method_types_for_intent_creation(
26962718
string $selected_payment_type,
26972719
int $order_id,
2698-
$express_payment_type = ''
2720+
?string $express_payment_type = null,
2721+
?string $payment_method_type = null
26992722
): array {
2723+
// If Single Payment Element is enabled, return only the payment method type.
2724+
if ( $this->spe_enabled && ! empty( $payment_method_type ) ) {
2725+
return [ $payment_method_type ];
2726+
}
2727+
27002728
// If the shopper didn't select a payment type, return all the enabled ones.
27012729
if ( '' === $selected_payment_type ) {
27022730
return $this->get_upe_enabled_at_checkout_payment_method_ids( $order_id );

readme.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ If you get stuck, you can ask for help in the [Plugin Forum](https://wordpress.o
111111
== Changelog ==
112112

113113
= 9.3.0 - xxxx-xx-xx =
114+
* Add - Implements the Single Payment Element feature for the new checkout experience on the block checkout page.
114115
* Dev - Additional replacements for payment method constant values on the backend.
115116
* Fix - Improves the checking for existing customer attribute when retrieving a payment method that may be detached from a subscription.
116117
* Fix - Reverts the default value for the `capture_method` property to avoid breaking Amazon Pay when creating a payment intent.

tests/phpunit/payment-methods/test-class-wc-stripe-upe-payment-gateway.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2845,4 +2845,27 @@ public function test_filter_thankyou_order_received_text() {
28452845
$expected = $default_text . '<p class="woocommerce-info">The payment is being processed and it might take a few minutes before it&#039;s confirmed.</p>';
28462846
$this->assertEquals( $expected, $actual );
28472847
}
2848+
2849+
/**
2850+
* Test that a failed payment intent is not reused and a new one is created instead.
2851+
*
2852+
* @return void
2853+
*/
2854+
public function test_is_spe_enabled() {
2855+
// Disabled
2856+
update_option( WC_Stripe_Feature_Flags::SPE_FEATURE_FLAG_NAME, 'no' );
2857+
2858+
$gateway = new WC_Stripe_UPE_Payment_Gateway();
2859+
$this->assertFalse( $gateway->is_spe_enabled() );
2860+
2861+
// Enabled
2862+
update_option( WC_Stripe_Feature_Flags::SPE_FEATURE_FLAG_NAME, 'yes' );
2863+
2864+
$stripe_settings = WC_Stripe_Helper::get_stripe_settings();
2865+
$stripe_settings['single_payment_element'] = 'yes';
2866+
WC_Stripe_Helper::update_main_stripe_settings( $stripe_settings );
2867+
2868+
$gateway = new WC_Stripe_UPE_Payment_Gateway();
2869+
$this->assertTrue( $gateway->is_spe_enabled() );
2870+
}
28482871
}

0 commit comments

Comments
 (0)