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

Integrate split UPE payments with WooCommerce Blocks #4681

Merged
merged 23 commits into from
Nov 9, 2022
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
931516d
Use 'card' for the default block UPE checkout
harriswong Sep 1, 2022
46d4297
Attempt to load multiple payment methods for blocks (draft)
harriswong Sep 7, 2022
8b6bb36
Merge branch 'poc/upe-instances-multiplied' of https://github.com/Aut…
Oct 6, 2022
3ea4122
Display only enabled UPE payment methods
Oct 10, 2022
080a1f8
Display corresponding payment method titles
Oct 10, 2022
0711a80
Render specific testing instructions whenever appropriate
Oct 11, 2022
90a3829
Provide the right gatewayConfig for each payment method
Oct 12, 2022
e85f6cf
Fix the action of updating a payment intent with AJAX request
Oct 13, 2022
82d3f47
Merge branch 'poc/upe-instances-multiplied' of https://github.com/Aut…
Oct 13, 2022
2b40054
Fix tests
Oct 14, 2022
6a10a65
Supply the Card Element instead of the Card from Stripe Payment Elements
Oct 16, 2022
959d0b4
Eliminate unnecessary upe payment methods for blocks
Oct 16, 2022
f1c0893
Avoid enqueuing identical scripts and styles twice
Oct 27, 2022
4993981
Create a changelog file
Oct 28, 2022
53e37c9
Align a react node responsible for the preview of the method in the e…
Oct 28, 2022
6304292
Apply cosmetic changes to variable names and scopes
Oct 28, 2022
0efb398
Merge branch 'poc/upe-instances-multiplied' of https://github.com/Aut…
Oct 28, 2022
d818472
Leverage the session storage with created payment intent
Nov 2, 2022
7dd6bfe
Align the definition of method responsible for supplying testing inst…
Nov 2, 2022
f208c9e
Update the state with paymentIntent to avoid a recurring intent creation
Nov 7, 2022
c16cfec
Align method invocation with its definition
Nov 8, 2022
e3ceb21
Get rid of the changelog file due to the nature of changes
Nov 8, 2022
f17d4a4
Make sure callbacks are added to hooks for non-card payment methods i…
Nov 8, 2022
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
4 changes: 4 additions & 0 deletions changelog/fix-4603-block-split-upe
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: fix

Integrate split UPE payments with WooCommerce Blocks
FangedParakeet marked this conversation as resolved.
Show resolved Hide resolved
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 );
FangedParakeet marked this conversation as resolved.
Show resolved Hide resolved
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;
FangedParakeet marked this conversation as resolved.
Show resolved Hide resolved

// 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' ) );
} );
35 changes: 9 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
Loading