Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add split PE support for APMs (eg GiroPay) with deferred intent on the classic Checkout #2827

Merged
merged 33 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
a5c2a97
Add suport for Stripe APMs to be used on block checkout pages with sp…
james-allan Jan 4, 2024
968f911
Add support for processing payments using split UPE on the block chec…
james-allan Jan 5, 2024
7f1231f
Use Stipe gateway ID constant rather than hardcoding 'stripe_'
james-allan Jan 5, 2024
625aa5a
Merge branch 'add/deferred-intent' into add/deferred-intent-split-apm…
james-allan Jan 5, 2024
4d10d92
Use already created instances of the upe payment methods
james-allan Jan 8, 2024
5545ec7
Hide Stripe APMs on the WooCommerce > Settings > Payments screen
james-allan Jan 8, 2024
66deb6a
Test troubleshooting
james-allan Jan 8, 2024
797cc2b
Attempt to fix test
james-allan Jan 8, 2024
8452320
revert test troubleshooting code
james-allan Jan 8, 2024
227862a
Ensure the Link payment method is available before returning from get…
james-allan Jan 9, 2024
26c392c
Add unit testing troubleshooting code
james-allan Jan 9, 2024
6be2b12
add track which function is failing unit test
james-allan Jan 9, 2024
9cd39f9
Troubleshoot why Stripe Link isnt available
james-allan Jan 9, 2024
612f1e6
Introduce new function to deteremine if a UPE methods is available de…
james-allan Jan 9, 2024
72150cc
Revert unit test troubleshooting code
james-allan Jan 9, 2024
6161bbe
Remove non-split temporary code added while developing deferred intents
mattallan Jan 12, 2024
8871208
Update generateCheckoutEventNames() so that it attaches checkout hook…
mattallan Jan 12, 2024
6606450
Sends gatewayId in JS params to fix getSelectedUPEGatewayPaymentMethod()
mattallan Jan 12, 2024
1beba9d
Don't add the UPE CC class to the list of WC gateways as it's already…
mattallan Jan 12, 2024
6492c7a
Adds necessary payment_fields() function to UPE payment method base c…
mattallan Jan 12, 2024
f879a16
Clean up main constuctor after adding helper functions
mattallan Jan 12, 2024
2272769
Merge branch 'add/deferred-intent' into add/deferred-intent-split-apm…
james-allan Jan 15, 2024
259ab86
Consolidate logic to handle the redirect url for 3ds, vouchers and ba…
james-allan Jan 15, 2024
b60906f
Defend against possible uncallable function
james-allan Jan 15, 2024
527959c
Remove non-split PE support code and manually set selected UPE Paymen…
mattallan Jan 16, 2024
74a5809
Fix isUsingSavedPaymentMethod util function to work in a split PE env…
mattallan Jan 16, 2024
f411461
Remove get_upe_enabled_payment_method_ids() as it doesn't make sense …
mattallan Jan 16, 2024
5bb5a2e
Fix is_enabled() so that it returns a boolean again
mattallan Jan 16, 2024
2351511
Don't show Stripe payment methods on checkout that have been disabled…
mattallan Jan 16, 2024
be433ad
Make sure stripe payment elements are unique by using IDs with 'strip…
mattallan Jan 16, 2024
2844b66
Merge branch 'add/deferred-intent-split-apms-block-checkout' into add…
mattallan Jan 17, 2024
8678894
Merge branch 'add/deferred-intent' into add/split-pe-classic-checkout
mattallan Jan 18, 2024
13a640e
Remove gateway description from payment fields and display testing in…
mattallan Jan 18, 2024
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
65 changes: 9 additions & 56 deletions client/classic/upe/payment-processing.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,6 @@ function createStripePaymentElement( api, paymentMethodType = null ) {
},
} );

// To be removed with Split PE.
if ( paymentMethodType === null ) {
paymentMethodType = 'stripe';
gatewayUPEComponents.stripe = {
elements: null,
upeElement: null,
};
}

gatewayUPEComponents[ paymentMethodType ].elements = elements;
gatewayUPEComponents[
paymentMethodType
Expand Down Expand Up @@ -166,8 +157,6 @@ function createStripePaymentMethod(
* Mounts the existing Stripe Payment Element to the DOM element.
* Creates the Stripe Payment Element instance if it doesn't exist and mounts it to the DOM element.
*
* @todo Make it only Split when implemented.
*
* @param {Object} api The API object.
* @param {string} domElement The selector of the DOM element of particular payment method to mount the UPE element to.
**/
Expand All @@ -184,31 +173,19 @@ export async function mountStripePaymentElement( api, domElement ) {
const event = new Event( 'wc-credit-card-form-init' );
document.body.dispatchEvent( event );

const paymentMethodType = domElement.dataset.paymentMethodType;
let upeElement;
let paymentMethodType = domElement.dataset.paymentMethodType;

// Non-split PE. To be removed.
if ( typeof paymentMethodType === 'undefined' ) {
upeElement = await createStripePaymentElement( api );

upeElement.on( 'change', ( e ) => {
const selectedUPEPaymentType = e.value.type;
const isPaymentMethodReusable =
paymentMethodsConfig[ selectedUPEPaymentType ].isReusable;
showNewPaymentMethodCheckbox( isPaymentMethodReusable );
setSelectedUPEPaymentType( selectedUPEPaymentType );
} );
} else {
// Split PE.
if ( ! gatewayUPEComponents[ paymentMethodType ] ) {
return;
}
paymentMethodType = 'card';
}

upeElement =
gatewayUPEComponents[ paymentMethodType ].upeElement ||
( await createStripePaymentElement( api, paymentMethodType ) );
if ( ! gatewayUPEComponents[ paymentMethodType ] ) {
return;
}

const upeElement =
gatewayUPEComponents[ paymentMethodType ].upeElement ||
( await createStripePaymentElement( api, paymentMethodType ) );
upeElement.mount( domElement );
}

Expand All @@ -219,26 +196,6 @@ function setSelectedUPEPaymentType( paymentType ) {
).value = paymentType;
}

// Show or hide save payment information checkbox
function showNewPaymentMethodCheckbox( show = true ) {
const saveCardElement = document.querySelector(
'.woocommerce-SavedPaymentMethods-saveNew'
);

if ( saveCardElement ) {
saveCardElement.style.visibility = show ? 'visible' : 'hidden';
}

const stripeSaveCardCheckbox = document.querySelector(
'input#wc-stripe-new-payment-method'
);

if ( ! show && stripeSaveCardCheckbox ) {
stripeSaveCardCheckbox.setAttribute( 'checked', false );
stripeSaveCardCheckbox.dispatchEvent( new Event( 'change' ) );
}
}

/**
* Handles the checkout process for the provided jQuery form and Stripe payment method type. The function blocks the
* form UI to prevent duplicate submission and validates the Stripe elements. It then creates a Stripe payment method
Expand All @@ -264,11 +221,6 @@ export const processPayment = (

blockUI( jQueryForm );

// Non split. To be removed.
if ( paymentMethodType === null ) {
paymentMethodType = 'stripe';
}

const elements = gatewayUPEComponents[ paymentMethodType ].elements;

( async () => {
Expand All @@ -280,6 +232,7 @@ export const processPayment = (
jQueryForm,
paymentMethodType
);
setSelectedUPEPaymentType( paymentMethodType );
appendIsUsingDeferredIntentToForm( jQueryForm );
appendPaymentMethodIdToForm(
jQueryForm,
Expand Down
26 changes: 26 additions & 0 deletions client/stripe-utils/constants.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,29 @@
export const PAYMENT_METHOD_NAME_CARD = 'stripe';
export const PAYMENT_METHOD_NAME_GIROPAY = 'stripe_giropay';
export const PAYMENT_METHOD_NAME_EPS = 'stripe_eps';
export const PAYMENT_METHOD_NAME_IDEAL = 'stripe_ideal';
export const PAYMENT_METHOD_NAME_P24 = 'stripe_p24';
export const PAYMENT_METHOD_NAME_SEPA = 'stripe_sepa_debit';
export const PAYMENT_METHOD_NAME_SOFORT = 'stripe_sofort';
export const PAYMENT_METHOD_NAME_BOLETO = 'stripe_boleto';
export const PAYMENT_METHOD_NAME_OXXO = 'stripe_oxxo';
export const PAYMENT_METHOD_NAME_BANCONTACT = 'stripe_bancontact';

export function getPaymentMethodsConstants() {
return {
card: PAYMENT_METHOD_NAME_CARD,
giropay: PAYMENT_METHOD_NAME_GIROPAY,
eps: PAYMENT_METHOD_NAME_EPS,
ideal: PAYMENT_METHOD_NAME_IDEAL,
p24: PAYMENT_METHOD_NAME_P24,
sepa: PAYMENT_METHOD_NAME_SEPA,
sofort: PAYMENT_METHOD_NAME_SOFORT,
boleto: PAYMENT_METHOD_NAME_BOLETO,
oxxo: PAYMENT_METHOD_NAME_OXXO,
bancontact: PAYMENT_METHOD_NAME_BANCONTACT,
};
}

export const errorTypes = {
INVALID_EMAIL: 'email_invalid',
INVALID_REQUEST: 'invalid_request_error',
Expand Down
40 changes: 33 additions & 7 deletions client/stripe-utils/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

import { __ } from '@wordpress/i18n';
import { getAppearance } from '../styles/upe';
import { errorTypes, errorCodes } from './constants';
import {
errorTypes,
errorCodes,
getPaymentMethodsConstants,
} from './constants';

/**
* @typedef {import('./type-defs').StripeServerData} StripeServerData
Expand Down Expand Up @@ -262,10 +266,14 @@ function shouldIncludeTerms() {
return false;
}

/**
* Returns a string of event names to be used for registering checkout submission handlers.
* For example: "checkout_place_order_stripe checkout_place_order_stripe_ideal ...checkout_place_order_{paymentMethod}"
*
* @return {string} String of event names.
*/
export const generateCheckoutEventNames = () => {
const paymentMethods = [ 'stripe' ];

return paymentMethods
return Object.values( getPaymentMethodsConstants() )
.map( ( method ) => `checkout_place_order_${ method }` )
.join( ' ' );
};
Expand All @@ -292,13 +300,17 @@ export const appendSetupIntentToForm = ( form, setupIntent ) => {
/**
* Checks if the customer is using a saved payment method.
*
* @param {string} paymentMethodType The payment method type ('card', 'ideal', etc.).
*
* @return {boolean} Boolean indicating whether or not a saved payment method is being used.
*/
export const isUsingSavedPaymentMethod = () => {
export const isUsingSavedPaymentMethod = ( paymentMethodType ) => {
const paymentMethod = getPaymentMethodName( paymentMethodType );
return (
document.querySelector( '#wc-stripe-new-payment-method' )?.length &&
document.querySelector( `#wc-${ paymentMethod }-new-payment-method` )
?.length &&
! document
.querySelector( '#wc-stripe-new-payment-method' )
.querySelector( `#wc-${ paymentMethod }-new-payment-method` )
.is( ':checked' )
);
};
Expand Down Expand Up @@ -466,3 +478,17 @@ export const initializeUPEAppearance = () => {

return appearance;
};

/**
* Gets the payment method name from the given payment method type.
* For example, when passed 'card' returns 'stripe' and for 'ideal' returns 'stripe_ideal'.
*
* Defaults to 'stripe' if the given payment method type is not found in the list of payment methods constants.
*
* @param {string} paymentMethodType The payment method type ('card', 'ideal', etc.).
*
* @return {string} The payment method name.
*/
export const getPaymentMethodName = ( paymentMethodType ) => {
return getPaymentMethodsConstants()[ paymentMethodType ] || 'stripe';
};
17 changes: 17 additions & 0 deletions includes/admin/class-wc-stripe-settings-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ public function __construct( WC_Stripe_Account $account ) {
$this->account = $account;
add_action( 'admin_enqueue_scripts', [ $this, 'admin_scripts' ] );
add_action( 'wc_stripe_gateway_admin_options_wrapper', [ $this, 'admin_options' ] );

// Priority 5 so we can manipulate the registered gateways before they are shown.
add_action( 'woocommerce_admin_field_payment_gateways', [ $this, 'hide_gateways_on_settings_page' ], 5 );
}

/**
Expand Down Expand Up @@ -128,4 +131,18 @@ public function admin_scripts( $hook_suffix ) {
wp_enqueue_script( 'woocommerce_stripe_admin' );
wp_enqueue_style( 'woocommerce_stripe_admin' );
}

/**
* Removes all Stripe alternative payment methods (eg Bancontact, giropay) on the WooCommerce Settings page.
*
* Note: This function is hooked onto `woocommerce_admin_field_payment_gateways` which is the hook used
* to display the payment gateways on the WooCommerce Settings page.
*/
public static function hide_gateways_on_settings_page() {
foreach ( WC()->payment_gateways->payment_gateways as $index => $payment_gateway ) {
if ( $payment_gateway instanceof WC_Stripe_UPE_Payment_Method ) {
unset( WC()->payment_gateways->payment_gateways[ $index ] );
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ public function javascript_params() {

$is_change_payment_method = $this->is_changing_payment_method_for_subscription();
$stripe_params = [
'gatewayId' => self::ID,
'title' => $this->title,
'isUPEEnabled' => true,
'key' => $this->publishable_key,
Expand Down Expand Up @@ -443,11 +444,12 @@ public function get_upe_available_payment_methods() {
$available_payment_methods = [];

foreach ( $this->payment_methods as $payment_method ) {
if ( ! $payment_method->is_available() ) {
if ( ! $payment_method->is_available_for_account_country() ) {
continue;
}
$available_payment_methods[] = $payment_method->get_id();
}

return $available_payment_methods;
}

Expand Down Expand Up @@ -704,8 +706,8 @@ private function process_payment_with_deferred_intent( int $order_id ) {

$redirect = $this->get_return_url( $order );

// If the payment intent requires action, respond with the pi and client secret so it can confirmed on checkout.
if ( 'requires_action' === $payment_intent->status ) {
// If the payment intent requires action, respond with redirect URL or the pi and client secret so it can confirmed on checkout.
if ( 'requires_action' === $payment_intent->status || 'requires_confirmation' === $payment_intent->status ) {
if ( isset( $payment_intent->next_action->type ) && 'redirect_to_url' === $payment_intent->next_action->type && ! empty( $payment_intent->next_action->redirect_to_url->url ) ) {
$redirect = $payment_intent->next_action->redirect_to_url->url;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,24 @@ public function create_payment_token_for_user( $user_id, $payment_method ) {
}

/**
* Returns true if the UPE method is available.
* Determines if the Stripe Account country this UPE method supports.
*
* @return bool
*/
public function is_available() {
//if merchant is outside US, Link payment method should not be available
public function is_available_for_account_country() {
// If merchant is outside US, Link payment method should not be available.
$cached_account_data = WC_Stripe::get_instance()->account->get_cached_account_data();
$account_country = $cached_account_data['country'] ?? null;

return 'US' === $account_country && parent::is_available();
return 'US' === $account_country;
}

/**
* Returns true if the UPE method is available.
*
* @return bool
*/
public function is_available() {
return $this->is_available_for_account_country() && parent::is_available();
}
}
Loading
Loading