import {useEffect, useState} from '@wordpress/element'; import {registerExpressPaymentMethod, registerPaymentMethod} from '@woocommerce/blocks-registry'; import {mergeWcAddress, paypalAddressToWc, paypalOrderToWcAddresses} from "./Helper/Address"; import { loadPaypalScriptPromise } from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading' import { normalizeStyleForFundingSource } from '../../../ppcp-button/resources/js/modules/Helper/Style' import buttonModuleWatcher from "../../../ppcp-button/resources/js/modules/ButtonModuleWatcher"; const config = wc.wcSettings.getSetting('ppcp-gateway_data'); window.ppcpFundingSource = config.fundingSource; let registeredContext = false; const PayPalComponent = ({ onClick, onClose, onSubmit, onError, eventRegistration, emitResponse, activePaymentMethod, shippingData, isEditing, fundingSource, }) => { const {onPaymentSetup, onCheckoutFail, onCheckoutValidation} = eventRegistration; const {responseTypes} = emitResponse; const [paypalOrder, setPaypalOrder] = useState(null); const methodId = fundingSource ? `${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) { return; } const paypalAddresses = paypalOrderToWcAddresses(config.scriptData.continuation.order); const wcAddresses = wp.data.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').setShippingAddress(addresses.shippingAddress); } // this useEffect should run only once, but adding this in case of some kind of full re-rendering window.ppcpContinuationFilled = true; }, []) const createOrder = async () => { try { const res = await fetch(config.scriptData.ajax.create_order.endpoint, { method: 'POST', credentials: 'same-origin', body: JSON.stringify({ nonce: config.scriptData.ajax.create_order.nonce, bn_code: '', context: config.scriptData.context, payment_method: 'ppcp-gateway', createaccount: false }), }); const json = await res.json(); if (!json.success) { if (json.data?.details?.length > 0) { throw new Error(json.data.details.map(d => `${d.issue} ${d.description}`).join('
')); } else if (json.data?.message) { throw new Error(json.data.message); } throw new Error(config.scriptData.labels.error.generic); } return json.data.id; } catch (err) { console.error(err); onError(err.message); onClose(); throw err; } }; const getCheckoutRedirectUrl = () => { const checkoutUrl = new URL(config.scriptData.redirect); // sometimes some browsers may load some kind of cached version of the page, // so adding a parameter to avoid that checkoutUrl.searchParams.append('ppcp-continuation-redirect', (new Date()).getTime().toString()); return checkoutUrl.toString(); } const handleApprove = async (data, actions) => { try { const order = await actions.order.get(); if (order) { const addresses = paypalOrderToWcAddresses(order); let promises = [ // save address on server wp.data.dispatch('wc/store/cart').updateCustomerData({ billing_address: addresses.billingAddress, shipping_address: addresses.shippingAddress, }), ]; if (!config.finalReviewEnabled) { // set address in UI promises.push(wp.data.dispatch('wc/store/cart').setBillingAddress(addresses.billingAddress)); if (shippingData.needsShipping) { promises.push(wp.data.dispatch('wc/store/cart').setShippingAddress(addresses.shippingAddress)) } } await Promise.all(promises); } setPaypalOrder(order); const res = await fetch(config.scriptData.ajax.approve_order.endpoint, { method: 'POST', credentials: 'same-origin', body: JSON.stringify({ nonce: config.scriptData.ajax.approve_order.nonce, order_id: data.orderID, funding_source: window.ppcpFundingSource ?? 'paypal', }) }); const json = await res.json(); if (!json.success) { if (typeof actions !== 'undefined' && typeof actions.restart !== 'undefined') { return actions.restart(); } if (json.data?.message) { throw new Error(json.data.message); } throw new Error(config.scriptData.labels.error.generic) } if (config.finalReviewEnabled) { location.href = getCheckoutRedirectUrl(); } else { onSubmit(); } } catch (err) { console.error(err); onError(err.message); onClose(); throw err; } }; useEffect(() => { const unsubscribe = onCheckoutValidation(() => { if (config.scriptData.continuation) { return true; } if (wp.data.select('wc/store/validation').hasValidationErrors()) { location.href = getCheckoutRedirectUrl(); return { type: responseTypes.ERROR }; } return true; }); return unsubscribe; }, [onCheckoutValidation] ); const handleClick = (data, actions) => { if (isEditing) { return actions.reject(); } window.ppcpFundingSource = data.fundingSource; onClick(); }; let handleShippingChange = null; if (shippingData.needsShipping && !config.finalReviewEnabled) { handleShippingChange = async (data, actions) => { try { const shippingOptionId = data.selected_shipping_option?.id; if (shippingOptionId) { await shippingData.setSelectedRates(shippingOptionId); } const address = paypalAddressToWc(data.shipping_address); await wp.data.dispatch('wc/store/cart').updateCustomerData({ shipping_address: address, }); await shippingData.setShippingAddress(address); const res = await fetch(config.ajax.update_shipping.endpoint, { method: 'POST', credentials: 'same-origin', body: JSON.stringify({ nonce: config.ajax.update_shipping.nonce, order_id: data.orderID, }) }); const json = await res.json(); if (!json.success) { throw new Error(json.data.message); } } catch (e) { console.error(e); actions.reject(); } }; } useEffect(() => { if (activePaymentMethod !== methodId) { return; } const unsubscribeProcessing = onPaymentSetup(() => { if (config.scriptData.continuation) { return { type: responseTypes.SUCCESS, meta: { paymentMethodData: { 'paypal_order_id': config.scriptData.continuation.order_id, 'funding_source': window.ppcpFundingSource ?? 'paypal', } }, }; } const addresses = paypalOrderToWcAddresses(paypalOrder); return { type: responseTypes.SUCCESS, meta: { paymentMethodData: { 'paypal_order_id': paypalOrder.id, 'funding_source': window.ppcpFundingSource ?? 'paypal', }, ...addresses, }, }; }); return () => { unsubscribeProcessing(); }; }, [onPaymentSetup, paypalOrder, activePaymentMethod]); useEffect(() => { if (activePaymentMethod !== methodId) { return; } const unsubscribe = onCheckoutFail(({ processingResponse }) => { console.error(processingResponse) if (onClose) { onClose(); } if (config.scriptData.continuation) { return true; } if (!config.finalReviewEnabled) { location.href = getCheckoutRedirectUrl(); } return true; }); return unsubscribe; }, [onCheckoutFail, onClose, activePaymentMethod]); if (config.scriptData.continuation) { return (
) } if (!registeredContext) { buttonModuleWatcher.registerContextBootstrap(config.scriptData.context, { createOrder: () => { return createOrder(); }, onApprove: (data, actions) => { return handleApprove(data, actions); }, }); registeredContext = true; } const style = normalizeStyleForFundingSource(config.scriptData.button.style, fundingSource); const PayPalButton = window.paypal.Buttons.driver("react", { React, ReactDOM }); return ( ); } const features = ['products']; if ((config.addPlaceOrderMethod || config.usePlaceOrder) && !config.scriptData.continuation) { let descriptionElement =
; if (config.placeOrderButtonDescription) { descriptionElement =

; } registerPaymentMethod({ name: config.id, label:
, content: descriptionElement, edit: descriptionElement, placeOrderButtonLabel: config.placeOrderButtonText, ariaLabel: config.title, canMakePayment: () => config.enabled, supports: { features: features, }, }); } if (config.scriptData.continuation) { registerPaymentMethod({ name: config.id, label:
, content: , edit: , ariaLabel: config.title, canMakePayment: () => true, supports: { features: [...features, 'ppcp_continuation'], }, }); } else if (!config.usePlaceOrder) { const paypalScriptPromise = loadPaypalScriptPromise(config.scriptData); for (const fundingSource of ['paypal', ...config.enabledFundingSources]) { registerExpressPaymentMethod({ name: `${config.id}-${fundingSource}`, paymentMethodId: config.id, label:
, content: , edit: , ariaLabel: config.title, canMakePayment: async () => { await paypalScriptPromise; return paypal.Buttons({fundingSource}).isEligible(); }, supports: { features: features, }, }); } }