From f3b30902db0c76d15d9a5e321c1c5f55a6e8ee2d Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Wed, 10 Jan 2024 18:12:01 +0000 Subject: [PATCH] Add subscription support for Block Cart and Block Checkout. --- .../resources/js/Helper/Address.js | 29 ++++- .../resources/js/Helper/Subscription.js | 16 +++ .../resources/js/checkout-block.js | 111 +++++++++++++++++- 3 files changed, 151 insertions(+), 5 deletions(-) create mode 100644 modules/ppcp-blocks/resources/js/Helper/Subscription.js diff --git a/modules/ppcp-blocks/resources/js/Helper/Address.js b/modules/ppcp-blocks/resources/js/Helper/Address.js index 2c3e9eae1..19ca36fdd 100644 --- a/modules/ppcp-blocks/resources/js/Helper/Address.js +++ b/modules/ppcp-blocks/resources/js/Helper/Address.js @@ -63,7 +63,7 @@ export const paypalAddressToWc = (address) => { * @returns {Object} */ export const paypalShippingToWc = (shipping) => { - const [firstName, lastName] = splitFullName(shipping.name.full_name); + const [firstName, lastName] = (shipping.name ? splitFullName(shipping.name.full_name) : ['','']); return { ...paypalAddressToWc(shipping.address), first_name: firstName, @@ -87,6 +87,22 @@ export const paypalPayerToWc = (payer) => { } } +/** + * @param {Object} subscriber + * @returns {Object} + */ +export const paypalSubscriberToWc = (subscriber) => { + const firstName = subscriber?.name?.given_name ?? ''; + const lastName = subscriber?.name?.surname ?? ''; + const address = subscriber.address ? paypalAddressToWc(subscriber.shipping_address.address) : {}; + return { + ...address, + first_name: firstName, + last_name: lastName, + email: subscriber.email_address, + } +} + /** * @param {Object} order * @returns {Object} @@ -130,6 +146,17 @@ export const paypalOrderToWcAddresses = (order) => { return {billingAddress, shippingAddress}; } +/** + * + * @param subscription + * @returns {{shippingAddress: Object, billingAddress: Object}} + */ +export const paypalSubscriptionToWcAddresses = (subscription) => { + const shippingAddress = paypalSubscriberToWc(subscription.subscriber); + let billingAddress = shippingAddress; + return {billingAddress, shippingAddress}; +} + /** * Merges two WC addresses. * The objects can contain either the WC form fields or billingAddress, shippingAddress objects. diff --git a/modules/ppcp-blocks/resources/js/Helper/Subscription.js b/modules/ppcp-blocks/resources/js/Helper/Subscription.js new file mode 100644 index 000000000..b274fea5e --- /dev/null +++ b/modules/ppcp-blocks/resources/js/Helper/Subscription.js @@ -0,0 +1,16 @@ +/** + * @param {Object} scriptData + * @returns {Boolean} + */ +export const isPayPalSubscription = (scriptData) => { + return scriptData.data_client_id.has_subscriptions + && scriptData.data_client_id.paypal_subscriptions_enabled; +} + +/** + * @param {Object} scriptData + * @returns {Boolean} + */ +export const cartHasSubscriptionProducts = (scriptData) => { + return !! scriptData?.locations_with_subscription_product?.cart; +} diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index 448cbcaed..c6f91bfc6 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -1,6 +1,15 @@ import {useEffect, useState} from '@wordpress/element'; import {registerExpressPaymentMethod, registerPaymentMethod} from '@woocommerce/blocks-registry'; -import {mergeWcAddress, paypalAddressToWc, paypalOrderToWcAddresses} from "./Helper/Address"; +import { + mergeWcAddress, + paypalAddressToWc, + paypalOrderToWcAddresses, + paypalSubscriptionToWcAddresses +} from "./Helper/Address"; +import { + cartHasSubscriptionProducts, + isPayPalSubscription +} from "./Helper/Subscription"; import { loadPaypalScriptPromise } from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading' @@ -9,7 +18,6 @@ import { } from '../../../ppcp-button/resources/js/modules/Helper/Style' import buttonModuleWatcher from "../../../ppcp-button/resources/js/modules/ButtonModuleWatcher"; import BlockCheckoutMessagesBootstrap from "./Bootstrap/BlockCheckoutMessagesBootstrap"; - const config = wc.wcSettings.getSetting('ppcp-gateway_data'); window.ppcpFundingSource = config.fundingSource; @@ -106,6 +114,78 @@ const PayPalComponent = ({ } }; + const createSubscription = async (data, actions) => { + return actions.subscription.create({ + 'plan_id': config.scriptData.subscription_plan_id + }); + }; + + const handleApproveSubscription = async (data, actions) => { + try { + const subscription = await actions.subscription.get(); + + if (subscription) { + const addresses = paypalSubscriptionToWcAddresses(subscription); + + 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(subscription); + + const res = await fetch(config.scriptData.ajax.approve_subscription.endpoint, { + method: 'POST', + credentials: 'same-origin', + body: JSON.stringify({ + nonce: config.scriptData.ajax.approve_subscription.nonce, + order_id: data.orderID, + subscription_id: data.subscriptionID + }) + }); + + 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 { + setGotoContinuationOnError(true); + onSubmit(); + } + } 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, @@ -328,6 +408,21 @@ const PayPalComponent = ({ const PayPalButton = paypal.Buttons.driver("react", { React, ReactDOM }); + if(isPayPalSubscription(config.scriptData)) { + return ( + + ); + } + return ( ; if (config.placeOrderButtonDescription) { @@ -360,7 +459,9 @@ if ((config.addPlaceOrderMethod || config.usePlaceOrder) && !config.scriptData.c edit: descriptionElement, placeOrderButtonLabel: config.placeOrderButtonText, ariaLabel: config.title, - canMakePayment: () => config.enabled, + canMakePayment: () => { + return config.enabled; + }, supports: { features: features, }, @@ -374,7 +475,9 @@ if (config.scriptData.continuation) { content: , edit: , ariaLabel: config.title, - canMakePayment: () => true, + canMakePayment: () => { + return true; + }, supports: { features: [...features, 'ppcp_continuation'], },