Skip to content

Commit

Permalink
Integrate split UPE payments with WooCommerce Blocks (#4681)
Browse files Browse the repository at this point in the history
* Use 'card' for the default block UPE checkout

* Attempt to load multiple payment methods for blocks (draft)

* Display only enabled UPE payment methods

* Display corresponding payment method titles

* Render specific testing instructions whenever appropriate

* Provide the right gatewayConfig for each payment method

* Fix the action of updating a payment intent with AJAX request

* Fix tests

* Supply the Card Element instead of the Card from Stripe Payment Elements

* Eliminate unnecessary upe payment methods for blocks

* Avoid enqueuing identical scripts and styles twice

* Create a changelog file

* Align a react node responsible for the preview of the method in the editor with the main payment method UI

* Apply cosmetic changes to variable names and scopes

* Leverage the session storage with created payment intent

* Align the definition of method responsible for supplying testing instructions text

* Update the state with paymentIntent to avoid a recurring intent creation

* Align method invocation with its definition

* Get rid of the changelog file due to the nature of changes

* Make sure callbacks are added to hooks for non-card payment methods in blocks

Co-authored-by: Timur Karimov <[email protected]>
Co-authored-by: Timur Karimov <[email protected]>
  • Loading branch information
3 people authored Nov 9, 2022
1 parent 88be4a9 commit 6444432
Show file tree
Hide file tree
Showing 24 changed files with 251 additions and 130 deletions.
5 changes: 4 additions & 1 deletion client/checkout/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,10 @@ export default class WCPayAPI {
paymentCountry
) {
return this.request(
buildAjaxURL( getConfig( 'wcAjaxUrl' ), 'update_payment_intent' ),
buildAjaxURL(
getConfig( 'wcAjaxUrl' ),
`update_payment_intent_${ selectedUPEPaymentType }`
),
{
wcpay_order_id: orderId,
wc_payment_intent_id: paymentIntentId,
Expand Down
27 changes: 21 additions & 6 deletions client/checkout/blocks/fields.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
CardElement,
} from '@stripe/react-stripe-js';
import { useEffect, useState } from '@wordpress/element';
import { getConfig } from 'utils/checkout';

/**
* Internal dependencies
Expand All @@ -30,6 +31,17 @@ const WCPayFields = ( {
shouldSavePayment,
} ) => {
const [ errorMessage, setErrorMessage ] = useState( null );
const isTestMode = getConfig( 'testMode' );
const testingInstructions = (
<p>
<strong>Test mode:</strong> use the test VISA card 4242424242424242
with any expiry date and CVC, or any test card numbers listed{ ' ' }
<a href="https://woocommerce.com/document/payments/testing/#test-cards">
here
</a>
.
</p>
);

// When it's time to process the payment, generate a Stripe payment method object.
useEffect(
Expand Down Expand Up @@ -86,12 +98,15 @@ const WCPayFields = ( {
};

return (
<div className="wc-block-gateway-container wc-inline-card-element">
<CardElement
options={ elementOptions }
onChange={ checkForErrors }
/>
</div>
<>
{ isTestMode ? testingInstructions : '' }
<div className="wc-block-gateway-container wc-inline-card-element">
<CardElement
options={ elementOptions }
onChange={ checkForErrors }
/>
</div>
</>
);
};

Expand Down
83 changes: 63 additions & 20 deletions client/checkout/blocks/upe-fields.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@ import { useEffect, useState } from '@wordpress/element';
*/
import './style.scss';
import confirmUPEPayment from './confirm-upe-payment.js';
import { getConfig } from 'utils/checkout';
import { getTerms } from '../utils/upe';
import { PAYMENT_METHOD_NAME_CARD, WC_STORE_CART } from '../constants.js';
import { getConfig, getUPEConfig } from 'utils/checkout';
import {
getTerms,
getPaymentIntentFromSession,
getCookieValue,
} from '../utils/upe';
import { WC_STORE_CART } from '../constants.js';
import enableStripeLinkPaymentMethod from 'wcpay/checkout/stripe-link';
import { useDispatch, useSelect } from '@wordpress/data';
import { getAppearance, getFontRulesFromPage } from '../upe-styles';
Expand Down Expand Up @@ -57,12 +61,14 @@ const useCustomerData = () => {
const WCPayUPEFields = ( {
api,
activePaymentMethod,
testingInstructions,
billing: { billingData },
eventRegistration: {
onPaymentProcessing,
onCheckoutAfterProcessingWithSuccess,
},
emitResponse,
paymentMethodId,
paymentIntentId,
paymentIntentSecret,
errorMessage,
Expand All @@ -78,15 +84,11 @@ const WCPayUPEFields = ( {
const [ paymentCountry, setPaymentCountry ] = useState( null );

const paymentMethodsConfig = getConfig( 'paymentMethodsConfig' );
const testMode = getConfig( 'testMode' );
const testCopy = (
<p>
<strong>Test mode:</strong> use the test VISA card 4242424242424242
with any expiry date and CVC.
</p>
);

const gatewayConfig = getPaymentMethods()[ PAYMENT_METHOD_NAME_CARD ];
const isTestMode = getConfig( 'testMode' );
const testingInstructionsIfAppropriate = isTestMode
? testingInstructions
: '';
const gatewayConfig = getPaymentMethods()[ paymentMethodId ];
const customerData = useCustomerData();

useEffect( () => {
Expand Down Expand Up @@ -200,7 +202,7 @@ const WCPayUPEFields = ( {
useEffect(
() =>
onPaymentProcessing( () => {
if ( PAYMENT_METHOD_NAME_CARD !== activePaymentMethod ) {
if ( paymentMethodId !== activePaymentMethod ) {
return;
}

Expand Down Expand Up @@ -234,7 +236,7 @@ const WCPayUPEFields = ( {
type: 'success',
meta: {
paymentMethodData: {
paymentMethod: PAYMENT_METHOD_NAME_CARD,
paymentMethod: paymentMethodId,
wc_payment_intent_id: paymentIntentId,
wcpay_selected_upe_payment_type: selectedUPEPaymentType,
},
Expand Down Expand Up @@ -338,7 +340,12 @@ const WCPayUPEFields = ( {

return (
<>
{ testMode ? testCopy : '' }
<p
className="content"
dangerouslySetInnerHTML={ {
__html: testingInstructionsIfAppropriate,
} }
></p>
<PaymentElement
options={ elementOptions }
onChange={ upeOnChange }
Expand All @@ -364,6 +371,7 @@ const ConsumableWCPayFields = ( { api, ...props } ) => {
getConfig( 'wcBlocksUPEAppearance' )
);
const [ fontRules ] = useState( getFontRulesFromPage() );
const paymentMethodsConfig = getUPEConfig( 'paymentMethodsConfig' );

useEffect( () => {
async function generateUPEAppearance() {
Expand All @@ -381,10 +389,20 @@ const ConsumableWCPayFields = ( { api, ...props } ) => {
if ( paymentIntentId || hasRequestedIntent ) {
return;
}

async function createIntent() {
async function createIntent( paymentMethodId ) {
try {
const response = await api.createIntent();
const response = await api.createIntent( paymentMethodId );
const cartHash = getCookieValue( 'woocommerce_cart_hash' );
if ( cartHash ) {
paymentMethodsConfig[
paymentMethodId
].upePaymentIntentData =
cartHash +
'-' +
response.id +
'-' +
response.client_secret;
}
setPaymentIntentId( response.id );
setClientSecret( response.client_secret );
} catch ( error ) {
Expand All @@ -395,9 +413,34 @@ const ConsumableWCPayFields = ( { api, ...props } ) => {
);
}
}

function getOrCreateIntent( paymentMethodId ) {
const {
intentId,
clientSecret: paymentClientSecret,
} = getPaymentIntentFromSession(
paymentMethodsConfig,
paymentMethodId
);
if ( ! intentId ) {
createIntent( paymentMethodId );
} else {
setPaymentIntentId( intentId );
setClientSecret( paymentClientSecret );
}
}

setHasRequestedIntent( true );
createIntent();
}, [ paymentIntentId, hasRequestedIntent, api, errorMessage, appearance ] );
getOrCreateIntent( props.paymentMethodId );
}, [
props.paymentMethodId,
paymentIntentId,
paymentMethodsConfig,
hasRequestedIntent,
api,
errorMessage,
appearance,
] );

if ( ! clientSecret ) {
if ( errorMessage ) {
Expand Down
94 changes: 65 additions & 29 deletions client/checkout/blocks/upe.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,53 +13,89 @@ import {
/**
* Internal dependencies
*/
import { PAYMENT_METHOD_NAME_CARD } from '../constants.js';
import { getConfig } from 'utils/checkout';
import { getUPEConfig } from 'utils/checkout';
import WCPayAPI from './../api';
import WCPayUPEFields from './upe-fields.js';
import { SavedTokenHandler } from './saved-token-handler';
import request from '../utils/request';
import enqueueFraudScripts from 'fraud-scripts';
import paymentRequestPaymentMethod from '../../payment-request/blocks';
import {
PAYMENT_METHOD_NAME_CARD,
PAYMENT_METHOD_NAME_BANCONTACT,
PAYMENT_METHOD_NAME_BECS,
PAYMENT_METHOD_NAME_EPS,
PAYMENT_METHOD_NAME_GIROPAY,
PAYMENT_METHOD_NAME_IDEAL,
PAYMENT_METHOD_NAME_P24,
PAYMENT_METHOD_NAME_SEPA,
PAYMENT_METHOD_NAME_SOFORT,
} from '../constants.js';

const upeMethods = {
card: PAYMENT_METHOD_NAME_CARD,
bancontact: PAYMENT_METHOD_NAME_BANCONTACT,
au_becs_debit: PAYMENT_METHOD_NAME_BECS,
eps: PAYMENT_METHOD_NAME_EPS,
giropay: PAYMENT_METHOD_NAME_GIROPAY,
ideal: PAYMENT_METHOD_NAME_IDEAL,
p24: PAYMENT_METHOD_NAME_P24,
sepa_debit: PAYMENT_METHOD_NAME_SEPA,
sofort: PAYMENT_METHOD_NAME_SOFORT,
};

const paymentMethodsConfig = getConfig( 'paymentMethodsConfig' );
const enabledPaymentMethodsConfig = getUPEConfig( 'paymentMethodsConfig' );
const isStripeLinkEnabled =
paymentMethodsConfig.link !== undefined &&
paymentMethodsConfig.card !== undefined;
enabledPaymentMethodsConfig.link !== undefined &&
enabledPaymentMethodsConfig.card !== undefined;

// Create an API object, which will be used throughout the checkout.
const api = new WCPayAPI(
{
publishableKey: getConfig( 'publishableKey' ),
accountId: getConfig( 'accountId' ),
forceNetworkSavedCards: getConfig( 'forceNetworkSavedCards' ),
locale: getConfig( 'locale' ),
isUPEEnabled: getConfig( 'isUPEEnabled' ),
publishableKey: getUPEConfig( 'publishableKey' ),
accountId: getUPEConfig( 'accountId' ),
forceNetworkSavedCards: getUPEConfig( 'forceNetworkSavedCards' ),
locale: getUPEConfig( 'locale' ),
isUPEEnabled: getUPEConfig( 'isUPEEnabled' ),
isStripeLinkEnabled,
},
request
);

registerPaymentMethod( {
name: PAYMENT_METHOD_NAME_CARD,
content: <WCPayUPEFields api={ api } />,
edit: <WCPayUPEFields api={ api } />,
savedTokenComponent: <SavedTokenHandler api={ api } />,
canMakePayment: () => !! api.getStripe(),
paymentMethodId: PAYMENT_METHOD_NAME_CARD,
label: getConfig( 'checkoutTitle' ),
ariaLabel: __( 'WooCommerce Payments', 'woocommerce-payments' ),
supports: {
showSavedCards: getConfig( 'isSavedCardsEnabled' ) ?? false,
showSaveOption:
( getConfig( 'isSavedCardsEnabled' ) &&
! getConfig( 'cartContainsSubscription' ) ) ??
false,
features: getConfig( 'features' ),
},
} );
Object.entries( enabledPaymentMethodsConfig ).map( ( [ upeName, upeConfig ] ) =>
registerPaymentMethod( {
name: upeName,
content: (
<WCPayUPEFields
paymentMethodId={ upeName }
api={ api }
testingInstructions={ upeConfig.testingInstructions }
/>
),
edit: (
<WCPayUPEFields
paymentMethodId={ upeName }
api={ api }
testingInstructions={ upeConfig.testingInstructions }
/>
),
savedTokenComponent: <SavedTokenHandler api={ api } />,
canMakePayment: () => !! api.getStripe(),
paymentMethodId: upeMethods[ upeName ],
label: upeConfig.title,
ariaLabel: __( 'WooCommerce Payments', 'woocommerce-payments' ),
supports: {
showSavedCards: getUPEConfig( 'isSavedCardsEnabled' ) ?? false,
showSaveOption:
( getUPEConfig( 'isSavedCardsEnabled' ) &&
! getUPEConfig( 'cartContainsSubscription' ) ) ??
false,
features: getUPEConfig( 'features' ),
},
} )
);

registerExpressPaymentMethod( paymentRequestPaymentMethod( api ) );
window.addEventListener( 'load', () => {
enqueueFraudScripts( getConfig( 'fraudServices' ) );
enqueueFraudScripts( getUPEConfig( 'fraudServices' ) );
} );
36 changes: 10 additions & 26 deletions client/checkout/classic/upe.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ import { getUPEConfig } from 'utils/checkout';
import WCPayAPI from '../api';
import enqueueFraudScripts from 'fraud-scripts';
import { getFontRulesFromPage, getAppearance } from '../upe-styles';
import { getTerms, getCookieValue, isWCPayChosen } from '../utils/upe';
import {
getTerms,
isWCPayChosen,
getPaymentIntentFromSession,
} from '../utils/upe';
import enableStripeLinkPaymentMethod from '../stripe-link';
import apiRequest from '../utils/request';
import showErrorCheckout from '../utils/show-error-checkout';
Expand Down Expand Up @@ -230,7 +234,10 @@ jQuery( function ( $ ) {

let { intentId, clientSecret } = isSetupIntent
? getSetupIntentFromSession( paymentMethodType )
: getPaymentIntentFromSession( paymentMethodType );
: getPaymentIntentFromSession(
paymentMethodsConfig,
paymentMethodType
);

const $upeContainer = $( upeDOMElement );
blockUI( $upeContainer );
Expand Down Expand Up @@ -667,30 +674,6 @@ jQuery( function ( $ ) {
);
}

/**
* Returns the cached payment intent for the current cart state.
*
* @param {string} paymentMethodType Stripe payment method type ID.
* @return {Object} The intent id and client secret required for mounting the UPE element.
*/
function getPaymentIntentFromSession( paymentMethodType ) {
const cartHash = getCookieValue( 'woocommerce_cart_hash' );
const upePaymentIntentData =
paymentMethodsConfig[ paymentMethodType ].upePaymentIntentData;

if (
cartHash &&
upePaymentIntentData &&
upePaymentIntentData.startsWith( cartHash )
) {
const intentId = upePaymentIntentData.split( '-' )[ 1 ];
const clientSecret = upePaymentIntentData.split( '-' )[ 2 ];
return { intentId, clientSecret };
}

return {};
}

/**
* Returns the cached setup intent.
*
Expand Down Expand Up @@ -721,6 +704,7 @@ jQuery( function ( $ ) {
return upeComponents.paymentIntentClientSecret;
}
const { clientSecret } = getPaymentIntentFromSession(
paymentMethodsConfig,
paymentMethodType
);
return clientSecret ? clientSecret : null;
Expand Down
Loading

0 comments on commit 6444432

Please sign in to comment.