From bb0a6cbff5f85d58d89f4ee9bdab313e6b3f4e1f Mon Sep 17 00:00:00 2001 From: Danae Millan Date: Mon, 5 Feb 2024 07:45:56 -0600 Subject: [PATCH 1/3] Display Terms for reusable APMs in the classic checkout --- client/classic/upe/deferred-intent.js | 18 +++++++ client/classic/upe/index.js | 15 +----- client/classic/upe/payment-processing.js | 25 ++++++++- client/stripe-utils/utils.js | 68 +++++++++++++----------- 4 files changed, 79 insertions(+), 47 deletions(-) diff --git a/client/classic/upe/deferred-intent.js b/client/classic/upe/deferred-intent.js index c4628e861..2641910eb 100644 --- a/client/classic/upe/deferred-intent.js +++ b/client/classic/upe/deferred-intent.js @@ -11,6 +11,7 @@ import { processPayment, mountStripePaymentElement, createAndConfirmSetupIntent, + renderTerms, confirmVoucherPayment, } from './payment-processing'; @@ -43,6 +44,23 @@ jQuery( function ( $ ) { } } + $( document ).on( 'change', function ( event ) { + // TODO: get a static array with the IDs instead of retrieving the selected one. + const selectedPaymentMethod = getSelectedUPEGatewayPaymentMethod(); + const newPaymentMethodInputId = + selectedPaymentMethod === 'card' + ? '' + : `_${ selectedPaymentMethod }`; + + if ( + event.target && + event.target.id === + `wc-stripe${ newPaymentMethodInputId }-new-payment-method` + ) { + renderTerms( event ); + } + } ); + // Mount the Stripe Payment Elements onto the Add Payment Method page and Pay for Order page. if ( $( 'form#add_payment_method' ).length || diff --git a/client/classic/upe/index.js b/client/classic/upe/index.js index 3a0692338..aac76ffb7 100644 --- a/client/classic/upe/index.js +++ b/client/classic/upe/index.js @@ -1,6 +1,6 @@ import jQuery from 'jquery'; import WCStripeAPI from '../../api'; -import { getStripeServerData, getUPETerms } from '../../stripe-utils'; +import { getStripeServerData } from '../../stripe-utils'; import { legacyHashchangeHandler } from './legacy-support'; import './style.scss'; import './deferred-intent.js'; @@ -283,19 +283,6 @@ jQuery( function ( $ ) { } } ); - // Add terms parameter to UPE if save payment information checkbox is checked. - // This shows required legal mandates when customer elects to save payment method during checkout. - $( document ).on( 'change', '#wc-stripe-new-payment-method', () => { - const value = $( '#wc-stripe-new-payment-method' ).is( ':checked' ) - ? 'always' - : 'never'; - if ( isUPEEnabled && upeElement ) { - upeElement.update( { - terms: getUPETerms( value ), - } ); - } - } ); - // On every page load, check to see whether we should display the authentication // modal and display it if it should be displayed. maybeShowAuthenticationModal(); diff --git a/client/classic/upe/payment-processing.js b/client/classic/upe/payment-processing.js index bea96f93e..cacb123ce 100644 --- a/client/classic/upe/payment-processing.js +++ b/client/classic/upe/payment-processing.js @@ -4,8 +4,10 @@ import { initializeUPEAppearance, getStripeServerData, getUpeSettings, + getTerms, showErrorCheckout, appendSetupIntentToForm, + getSelectedUPEGatewayPaymentMethod, } from '../../stripe-utils'; import { getFontRulesFromPage } from '../../styles/upe'; @@ -77,7 +79,7 @@ function createStripePaymentElement( api, paymentMethodType = null ) { const elements = api.getStripe().elements( options ); const createdStripePaymentElement = elements.create( 'payment', { - ...getUpeSettings(), + ...getUpeSettings( paymentMethodType ), wallets: { applePay: 'never', googlePay: 'never', @@ -276,6 +278,27 @@ export const createAndConfirmSetupIntent = ( } ); }; +/** + * Updates the terms parameter in the Payment Element based on the "save payment information" checkbox. + * + * @param {Event} event The change event that triggers the function. + */ +export function renderTerms( event ) { + const isChecked = event.target.checked; + const value = isChecked ? 'always' : 'never'; + const paymentMethodType = getSelectedUPEGatewayPaymentMethod(); + if ( ! paymentMethodType ) { + return; + } + + const upeElement = gatewayUPEComponents[ paymentMethodType ].upeElement; + if ( upeElement ) { + upeElement.update( { + terms: getTerms( paymentMethodType, value ), + } ); + } +} + /** * Handles displaying the Boleto or Oxxo voucher to the customer and then redirecting * them to the order received page once they close the voucher window. diff --git a/client/stripe-utils/utils.js b/client/stripe-utils/utils.js index 1254e5aa8..9c696e195 100644 --- a/client/stripe-utils/utils.js +++ b/client/stripe-utils/utils.js @@ -125,22 +125,44 @@ const getErrorMessageForTypeAndCode = ( type, code = '' ) => { return null; }; +function shouldIncludeTerms( paymentMethodType ) { + if ( getStripeServerData()?.cartContainsSubscription ) { + return true; + } + + const config = getStripeServerData()?.paymentMethodsConfig; + if ( + ! config[ paymentMethodType ] || + ! config[ paymentMethodType ].isReusable + ) { + return false; + } + + const paymentMethodId = + paymentMethodType === 'card' ? '' : `_${ paymentMethodType }`; + const checkboxId = `wc-stripe${ paymentMethodId }-new-payment-method`; + + const savePaymentMethodCheckbox = document.getElementById( checkboxId ); + + if ( + savePaymentMethodCheckbox !== null && + savePaymentMethodCheckbox.checked + ) { + return true; + } + + return false; +} + /** * Generates terms parameter for UPE, with value set for reusable payment methods * - * @param {string} value The terms value for each available payment method. + * @param {string} paymentMethodType The payment method type for which we're passing the Terms parameter. + * @param {string} value The terms value for the passed payment method. * @return {Object} Terms parameter fit for UPE. */ -export const getUPETerms = ( value = 'always' ) => { - const config = getStripeServerData()?.paymentMethodsConfig; - const reusablePaymentMethods = Object.keys( config ).filter( - ( method ) => config[ method ].isReusable - ); - - return reusablePaymentMethods.reduce( ( obj, method ) => { - obj[ method ] = value; - return obj; - }, {} ); +export const getTerms = ( paymentMethodType, value = 'always' ) => { + return { [ paymentMethodType ]: value }; }; /** @@ -248,24 +270,6 @@ export const getPaymentMethodTypes = ( paymentMethodType = null ) => { return paymentMethodTypes; }; -function shouldIncludeTerms() { - if ( getStripeServerData()?.cartContainsSubscription ) { - return true; - } - - const savePaymentMethodCheckbox = document.getElementById( - 'wc-stripe-new-payment-method' - ); - if ( - savePaymentMethodCheckbox !== null && - savePaymentMethodCheckbox.checked - ) { - return true; - } - - 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}" @@ -380,11 +384,11 @@ export const getHiddenBillingFields = ( enabledBillingFields ) => { }; }; -export const getUpeSettings = () => { +export const getUpeSettings = ( paymentMethodType ) => { const upeSettings = {}; - const showTerms = shouldIncludeTerms() ? 'always' : 'never'; + const value = shouldIncludeTerms( paymentMethodType ) ? 'always' : 'never'; - upeSettings.terms = getUPETerms( showTerms ); + upeSettings.terms = getTerms( paymentMethodType, value ); if ( getStripeServerData()?.isCheckout && From ac40a91655b6bebe704ef0d1a7d66ded40a8a7a1 Mon Sep 17 00:00:00 2001 From: Danae Millan Date: Mon, 5 Feb 2024 09:20:48 -0600 Subject: [PATCH 2/3] Update the key for terms used for SEPA --- client/stripe-utils/utils.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/client/stripe-utils/utils.js b/client/stripe-utils/utils.js index 9c696e195..2023f376d 100644 --- a/client/stripe-utils/utils.js +++ b/client/stripe-utils/utils.js @@ -162,7 +162,12 @@ function shouldIncludeTerms( paymentMethodType ) { * @return {Object} Terms parameter fit for UPE. */ export const getTerms = ( paymentMethodType, value = 'always' ) => { - return { [ paymentMethodType ]: value }; + // The key for SEPA debit is different from the slug we use in paymentMethodType. + // Ref: https://stripe.com/docs/js/elements_object/create_payment_element#payment_element_create-options-terms + const termKey = + paymentMethodType !== 'sepa_debit' ? paymentMethodType : 'sepaDebit'; + + return { [ termKey ]: value }; }; /** From f9c0fc0758cb21fc9914525334829cfb0cbabf28 Mon Sep 17 00:00:00 2001 From: Danae Millan Date: Sat, 10 Feb 2024 14:56:26 -0600 Subject: [PATCH 3/3] Use a static array instead for checking the changing input name --- client/classic/upe/deferred-intent.js | 12 +++--------- client/stripe-utils/utils.js | 11 +++++++++++ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/client/classic/upe/deferred-intent.js b/client/classic/upe/deferred-intent.js index 2641910eb..100f420cc 100644 --- a/client/classic/upe/deferred-intent.js +++ b/client/classic/upe/deferred-intent.js @@ -2,6 +2,7 @@ import jQuery from 'jquery'; import WCStripeAPI from '../../api'; import { generateCheckoutEventNames, + generateCheckoutSavePaymentMethodInputId, getSelectedUPEGatewayPaymentMethod, getStripeServerData, isUsingSavedPaymentMethod, @@ -44,18 +45,11 @@ jQuery( function ( $ ) { } } + const savePaymentMethodInputIds = generateCheckoutSavePaymentMethodInputId(); $( document ).on( 'change', function ( event ) { - // TODO: get a static array with the IDs instead of retrieving the selected one. - const selectedPaymentMethod = getSelectedUPEGatewayPaymentMethod(); - const newPaymentMethodInputId = - selectedPaymentMethod === 'card' - ? '' - : `_${ selectedPaymentMethod }`; - if ( event.target && - event.target.id === - `wc-stripe${ newPaymentMethodInputId }-new-payment-method` + savePaymentMethodInputIds.includes( event.target.id ) ) { renderTerms( event ); } diff --git a/client/stripe-utils/utils.js b/client/stripe-utils/utils.js index 2023f376d..2f3ead834 100644 --- a/client/stripe-utils/utils.js +++ b/client/stripe-utils/utils.js @@ -287,6 +287,17 @@ export const generateCheckoutEventNames = () => { .join( ' ' ); }; +/** + * Returns an array with the ID of the inputs to save a new Payment Method. + * + * @return {Array} Array of input IDs. + */ +export const generateCheckoutSavePaymentMethodInputId = () => { + return Object.values( getPaymentMethodsConstants() ).map( + ( method ) => `wc-${ method }-new-payment-method` + ); +}; + export const appendPaymentMethodIdToForm = ( form, paymentMethodId ) => { form.append( ``