mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-04 08:47:23 +08:00
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:
commit
79cdf84618
544 changed files with 87210 additions and 6892 deletions
|
@ -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 }
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue