Merge branch 'trunk' of github.com:woocommerce/woocommerce-paypal-payments into PCP-3202-retrieve-button-styling-properties-from-woo-commerce-checkout-block-ver-2

This commit is contained in:
Daniel Dudzic 2024-10-31 11:39:40 +01:00
commit 79cdf84618
No known key found for this signature in database
GPG key ID: 31B40D33E3465483
544 changed files with 87210 additions and 6892 deletions

View file

@ -7,18 +7,21 @@ import {
} from '@paypal/react-paypal-js';
import { CheckoutHandler } from './checkout-handler';
import { createOrder, onApprove } from '../card-fields-config';
import {
createOrder,
onApprove,
createVaultSetupToken,
onApproveSavePayment,
} from '../card-fields-config';
import { cartHasSubscriptionProducts } from '../Helper/Subscription';
export function CardFields( {
config,
eventRegistration,
emitResponse,
components,
} ) {
const { onPaymentSetup } = eventRegistration;
const { responseTypes } = emitResponse;
const { PaymentMethodIcons } = components;
const [ cardFieldsForm, setCardFieldsForm ] = useState();
const getCardFieldsForm = ( cardFieldsForm ) => {
@ -70,17 +73,26 @@ export function CardFields( {
} }
>
<PayPalCardFieldsProvider
createOrder={ createOrder }
onApprove={ onApprove }
createVaultSetupToken={
config.scriptData.is_free_trial_cart
? createVaultSetupToken
: undefined
}
createOrder={
config.scriptData.is_free_trial_cart
? undefined
: createOrder
}
onApprove={
config.scriptData.is_free_trial_cart
? onApproveSavePayment
: onApprove
}
onError={ ( err ) => {
console.error( err );
} }
>
<PayPalCardFieldsForm />
<PaymentMethodIcons
icons={ config.card_icons }
align="left"
/>
<CheckoutHandler
getCardFieldsForm={ getCardFieldsForm }
getSavePayment={ getSavePayment }

View file

@ -1,9 +1,44 @@
export const debounce = ( callback, delayMs ) => {
let timeoutId = null;
return ( ...args ) => {
window.clearTimeout( timeoutId );
timeoutId = window.setTimeout( () => {
callback.apply( null, args );
}, delayMs );
const state = {
timeoutId: null,
args: null,
};
/**
* Cancels any pending debounced execution.
*/
const cancel = () => {
if ( state.timeoutId ) {
window.clearTimeout( state.timeoutId );
}
state.timeoutId = null;
state.args = null;
};
/**
* Immediately executes the debounced function if there's a pending execution.
* @return {void}
*/
const flush = () => {
// If there's nothing pending, return early.
if ( ! state.timeoutId ) {
return;
}
callback.apply( null, state.args || [] );
cancel();
};
const debouncedFunc = ( ...args ) => {
cancel();
state.args = args;
state.timeoutId = window.setTimeout( flush, delayMs );
};
// Attach utility methods
debouncedFunc.cancel = cancel;
debouncedFunc.flush = flush;
return debouncedFunc;
};

View file

@ -1,19 +1,30 @@
import { registerPaymentMethod } from '@woocommerce/blocks-registry';
import { CardFields } from './Components/card-fields';
import {registerPaymentMethod} from '@woocommerce/blocks-registry';
import {CardFields} from './Components/card-fields';
const config = wc.wcSettings.getSetting( 'ppcp-credit-card-gateway_data' );
const config = wc.wcSettings.getSetting('ppcp-credit-card-gateway_data');
registerPaymentMethod( {
name: config.id,
label: <div dangerouslySetInnerHTML={ { __html: config.title } } />,
content: <CardFields config={ config } />,
edit: <div></div>,
ariaLabel: config.title,
canMakePayment: () => {
return true;
},
supports: {
showSavedCards: true,
features: config.supports,
},
} );
const Label = ({components, config}) => {
const {PaymentMethodIcons} = components;
return <>
<span dangerouslySetInnerHTML={{__html: config.title}}/>
<PaymentMethodIcons
icons={ config.card_icons }
align="right"
/>
</>
}
registerPaymentMethod({
name: config.id,
label: <Label config={config}/>,
content: <CardFields config={config}/>,
edit: <CardFields config={config}/>,
ariaLabel: config.title,
canMakePayment: () => {
return true;
},
supports: {
showSavedCards: true,
features: config.supports,
},
});

View file

@ -44,3 +44,61 @@ export async function onApprove( data ) {
console.error( err );
} );
}
export async function createVaultSetupToken() {
const config = wc.wcSettings.getSetting( 'ppcp-credit-card-gateway_data' );
return fetch( config.scriptData.ajax.create_setup_token.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify( {
nonce: config.scriptData.ajax.create_setup_token.nonce,
payment_method: 'ppcp-credit-card-gateway',
} ),
} )
.then( ( response ) => response.json() )
.then( ( result ) => {
console.log( result );
return result.data.id;
} )
.catch( ( err ) => {
console.error( err );
} );
}
export async function onApproveSavePayment( { vaultSetupToken } ) {
const config = wc.wcSettings.getSetting( 'ppcp-credit-card-gateway_data' );
let endpoint =
config.scriptData.ajax.create_payment_token_for_guest.endpoint;
let bodyContent = {
nonce: config.scriptData.ajax.create_payment_token_for_guest.nonce,
vault_setup_token: vaultSetupToken,
};
if ( config.scriptData.user.is_logged_in ) {
endpoint = config.scriptData.ajax.create_payment_token.endpoint;
bodyContent = {
nonce: config.scriptData.ajax.create_payment_token.nonce,
vault_setup_token: vaultSetupToken,
is_free_trial_cart: config.scriptData.is_free_trial_cart,
};
}
const response = await fetch( endpoint, {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify( bodyContent ),
} );
const result = await response.json();
if ( result.success !== true ) {
console.error( result );
}
}

View file

@ -3,6 +3,7 @@ import {
registerExpressPaymentMethod,
registerPaymentMethod,
} from '@woocommerce/blocks-registry';
import { __ } from '@wordpress/i18n';
import {
mergeWcAddress,
paypalAddressToWc,
@ -14,21 +15,21 @@ import {
cartHasSubscriptionProducts,
isPayPalSubscription,
} from './Helper/Subscription';
import { loadPaypalScriptPromise } from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading';
import { loadPayPalScript } from '../../../ppcp-button/resources/js/modules/Helper/PayPalScriptLoading';
import { PayPalScriptProvider, PayPalButtons } from '@paypal/react-paypal-js';
import { normalizeStyleForFundingSource } from '../../../ppcp-button/resources/js/modules/Helper/Style';
import buttonModuleWatcher from '../../../ppcp-button/resources/js/modules/ButtonModuleWatcher';
import BlockCheckoutMessagesBootstrap from './Bootstrap/BlockCheckoutMessagesBootstrap';
import { keysToCamelCase } from '../../../ppcp-button/resources/js/modules/Helper/Utils';
import { handleShippingOptionsChange } from '../../../ppcp-button/resources/js/modules/Helper/ShippingHandler';
const namespace = 'ppcpBlocksPaypalExpressButtons';
const config = wc.wcSettings.getSetting( 'ppcp-gateway_data' );
window.ppcpFundingSource = config.fundingSource;
let registeredContext = false;
let paypalScriptPromise = null;
const PAYPAL_GATEWAY_ID = 'ppcp-gateway';
const PayPalComponent = ( {
onClick,
onClose,
@ -47,6 +48,7 @@ const PayPalComponent = ( {
const { responseTypes } = emitResponse;
const [ paypalOrder, setPaypalOrder ] = useState( null );
const [ continuationFilled, setContinuationFilled ] = useState( false );
const [ gotoContinuationOnError, setGotoContinuationOnError ] =
useState( false );
@ -55,7 +57,10 @@ const PayPalComponent = ( {
if ( ! paypalScriptLoaded ) {
if ( ! paypalScriptPromise ) {
// for editor, since canMakePayment was not called
paypalScriptPromise = loadPaypalScriptPromise( config.scriptData );
paypalScriptPromise = loadPayPalScript(
namespace,
config.scriptData
);
}
paypalScriptPromise.then( () => setPaypalScriptLoaded( true ) );
}
@ -64,15 +69,33 @@ const PayPalComponent = ( {
? `${ config.id }-${ fundingSource }`
: config.id;
useEffect( () => {
// fill the form if in continuation (for product or mini-cart buttons)
if (
! config.scriptData.continuation ||
! config.scriptData.continuation.order ||
window.ppcpContinuationFilled
) {
/**
* The block cart displays express checkout buttons. Those buttons are handled by the
* PAYPAL_GATEWAY_ID method on the server ("PayPal Smart Buttons").
*
* A possible bug in WooCommerce does not use the correct payment method ID for the express
* payment buttons inside the cart, but sends the ID of the _first_ active payment method.
*
* This function uses an internal WooCommerce dispatcher method to set the correct method ID.
*/
const enforcePaymentMethodForCart = () => {
// Do nothing, unless we're handling block cart express payment buttons.
if ( 'cart-block' !== config.scriptData.context ) {
return;
}
// Set the active payment method to PAYPAL_GATEWAY_ID.
wp.data
.dispatch( 'wc/store/payment' )
.__internalSetActivePaymentMethod( PAYPAL_GATEWAY_ID, {} );
};
useEffect( () => {
// fill the form if in continuation (for product or mini-cart buttons)
if ( continuationFilled || ! config.scriptData.continuation?.order ) {
return;
}
try {
const paypalAddresses = paypalOrderToWcAddresses(
config.scriptData.continuation.order
@ -81,9 +104,11 @@ const PayPalComponent = ( {
.select( 'wc/store/cart' )
.getCustomerData();
const addresses = mergeWcAddress( wcAddresses, paypalAddresses );
wp.data
.dispatch( 'wc/store/cart' )
.setBillingAddress( addresses.billingAddress );
if ( shippingData.needsShipping ) {
wp.data
.dispatch( 'wc/store/cart' )
@ -93,9 +118,10 @@ const PayPalComponent = ( {
// sometimes the PayPal address is missing, skip in this case.
console.log( err );
}
// this useEffect should run only once, but adding this in case of some kind of full re-rendering
window.ppcpContinuationFilled = true;
}, [] );
setContinuationFilled( true );
}, [ shippingData, continuationFilled ] );
const createOrder = async ( data, actions ) => {
try {
@ -228,10 +254,11 @@ const PayPalComponent = ( {
throw new Error( config.scriptData.labels.error.generic );
}
if ( ! shouldHandleShippingInPayPal() ) {
if ( ! shouldskipFinalConfirmation() ) {
location.href = getCheckoutRedirectUrl();
} else {
setGotoContinuationOnError( true );
enforcePaymentMethodForCart();
onSubmit();
}
} catch ( err ) {
@ -319,10 +346,11 @@ const PayPalComponent = ( {
throw new Error( config.scriptData.labels.error.generic );
}
if ( ! shouldHandleShippingInPayPal() ) {
if ( ! shouldskipFinalConfirmation() ) {
location.href = getCheckoutRedirectUrl();
} else {
setGotoContinuationOnError( true );
enforcePaymentMethodForCart();
onSubmit();
}
} catch ( err ) {
@ -365,6 +393,10 @@ const PayPalComponent = ( {
};
const shouldHandleShippingInPayPal = () => {
return shouldskipFinalConfirmation() && config.needShipping;
};
const shouldskipFinalConfirmation = () => {
if ( config.finalReviewEnabled ) {
return false;
}
@ -545,7 +577,7 @@ const PayPalComponent = ( {
if ( config.scriptData.continuation ) {
return true;
}
if ( shouldHandleShippingInPayPal() ) {
if ( shouldskipFinalConfirmation() ) {
location.href = getCheckoutRedirectUrl();
}
return true;
@ -596,7 +628,10 @@ const PayPalComponent = ( {
return null;
}
const PayPalButton = paypal.Buttons.driver( 'react', { React, ReactDOM } );
const PayPalButton = ppcpBlocksPaypalExpressButtons.Buttons.driver(
'react',
{ React, ReactDOM }
);
const getOnShippingOptionsChange = ( fundingSource ) => {
if ( fundingSource === 'venmo' ) {
@ -741,11 +776,8 @@ if ( cartHasSubscriptionProducts( config.scriptData ) ) {
features.push( 'subscriptions' );
}
if ( block_enabled && config.enabled ) {
if (
( config.addPlaceOrderMethod || config.usePlaceOrder ) &&
! config.scriptData.continuation
) {
if ( block_enabled ) {
if ( config.placeOrderEnabled && ! config.scriptData.continuation ) {
let descriptionElement = (
<div
dangerouslySetInnerHTML={ { __html: config.description } }
@ -778,7 +810,7 @@ if ( block_enabled && config.enabled ) {
placeOrderButtonLabel: config.placeOrderButtonText,
ariaLabel: config.title,
canMakePayment: () => {
return config.enabled;
return true;
},
supports: {
features,
@ -791,7 +823,7 @@ if ( block_enabled && config.enabled ) {
name: config.id,
label: <div dangerouslySetInnerHTML={ { __html: config.title } } />,
content: <PayPalComponent isEditing={ false } />,
edit: <BlockEditorPayPalComponent />,
edit: <BlockEditorPayPalComponent fundingSource={ 'paypal' }/>,
ariaLabel: config.title,
canMakePayment: () => {
return true;
@ -800,13 +832,19 @@ if ( block_enabled && config.enabled ) {
features: [ ...features, 'ppcp_continuation' ],
},
} );
} else if ( ! config.usePlaceOrder ) {
} else if ( config.smartButtonsEnabled ) {
for ( const fundingSource of [
'paypal',
...config.enabledFundingSources,
] ) {
registerExpressPaymentMethod( {
name: `${ config.id }-${ fundingSource }`,
title: 'PayPal',
description: __(
'Eligible users will see the PayPal button.',
'woocommerce-paypal-payments'
),
gatewayId: 'ppcp-gateway',
paymentMethodId: config.id,
label: (
<div dangerouslySetInnerHTML={ { __html: config.title } } />
@ -825,7 +863,8 @@ if ( block_enabled && config.enabled ) {
ariaLabel: config.title,
canMakePayment: async () => {
if ( ! paypalScriptPromise ) {
paypalScriptPromise = loadPaypalScriptPromise(
paypalScriptPromise = loadPayPalScript(
namespace,
config.scriptData
);
paypalScriptPromise.then( () => {
@ -838,7 +877,9 @@ if ( block_enabled && config.enabled ) {
}
await paypalScriptPromise;
return paypal.Buttons( { fundingSource } ).isEligible();
return ppcpBlocksPaypalExpressButtons
.Buttons( { fundingSource } )
.isEligible();
},
supports: {
features,