UNPKG

@proca/widget

Version:

Proca is an open-source campaign toolkit designed to empower activists and organisations in their digital advocacy efforts. It provides a flexible and customisable platform for creating and managing online petitions, email campaigns, and other forms of di

323 lines (277 loc) 8.99 kB
import { useCallback, useLayoutEffect, useState } from "react"; import PaypalIcon from "../images/Paypal.js"; import { addAction, addDonateContact } from "@lib/server.js"; import { useCampaignConfig } from "@hooks/useConfig"; import Url from "@lib/urlparser.js"; import uuid from "@lib/uuid"; let buttons; const usePaypal = ({ completed, failed, amount, campaign, dom, formData }) => { const [loadState, setLoadState] = useState({ loading: false, loaded: false }); const config = useCampaignConfig(); const donateConfig = config.component.donation; // const formData = useData(); const addClick = useCallback( (event, payload) => { addAction(config.actionPage, event, { uuid: uuid(), // tracking: Url.utm(), payload: payload, }); }, [config] ); const _addContactFromPayPal = (contact, payer) => { if (!payer) return; contact.firstname = payer.name?.given_name; contact.lastname = payer.name?.surname; contact.email = payer.email_address; contact.phone = payer.phone?.phone_number?.national_number; const address = payer?.address || payer?.shipping_address?.address; if (address) { contact.country = address?.country_code; contact.postcode = address?.postal_code; } }; const donationStart = (data, actions) => { console.log("donationStart", actions); addClick("donation_start", { source: data.fundingSource, amount: amount, }); }; const donationError = err => { console.log("onError", err); failed({ message: "There was a problem processing your donation. If you'd like to try again, just click the PayPal button again.", error: err, }); addClick("donation_error", { source: "paypal", amount: amount, }); }; const donationCancel = (data, actions) => { console.log("onCancel", data, actions); failed({ message: "Oops, changed your mind? If you'd like to continue, just click the Paypal button again.", }); addClick("donation_cancel", { source: "paypal", amount: amount, }); }; const sharedParameters = { commit: true, onClick: donationStart, onError: donationError, onCancel: donationCancel, style: { shape: "rect", color: "silver", size: "responsive", height: 30, layout: "vertical", label: "paypal", ...(donateConfig?.paypal?.styles || {}), }, }; const renderSubscriptionButton = useCallback(() => { const paypal = window.paypal; // TODO: You can't dynamically modify the billing cycle unit (weeks, months, etc) // so we need to configure and use multiple plans here const plan_id = donateConfig.paypal.planId; buttons = paypal.Buttons({ fundingSource: paypal.FUNDING.PAYPAL, createSubscription: (_data, actions) => actions.subscription.create({ plan_id: plan_id, quantity: Math.floor(amount * 100).toString(), // PayPal wants a string application_context: { shipping_preference: "NO_SHIPPING", }, }), onApprove: async (paypalResponse, actions) => { const order = await actions.order.get(); const subscription = await actions.subscription.get(); const procaRequest = { ...formData, uuid: uuid(false), tracking: Url.utm(), }; _addContactFromPayPal(procaRequest, subscription.subscriber); const subscriptionAmount = subscription.billing_info.cycle_executions[0].total_price_per_cycle .gross_amount; procaRequest.donation = { amount: Math.floor(Number.parseFloat(subscriptionAmount.value) * 100), currency: subscriptionAmount.currency_code, frequencyUnit: formData.frequency, payload: { response: paypalResponse, subscriptionId: subscription.id, customerId: subscription.payer_id, subscription: subscription, order: order, formValues: formData, }, }; if (config.test) procaRequest.donation.payload.test = true; procaRequest.tracking = Url.utm(); const procaResponse = await addDonateContact( "paypal", config.actionPage, procaRequest ); completed(procaResponse); }, ...sharedParameters, }); document.querySelector(dom || "#paypal-container").innerHTML = ""; buttons.render(dom || "#paypal-container"); return () => { // https://github.com/paypal/paypal-checkout-components/issues/1506 try { if (buttons && buttons.close) { buttons.close(); } } catch (e) { console.error("Ignore error trying to close PayPal buttons", e); } }; }, [ amount, completed, config.actionPage, config.test, dom, donateConfig.paypal.planId, formData, sharedParameters, ]); const renderOneOffButton = useCallback(() => { const paypal = window.paypal; buttons = paypal.Buttons({ createOrder: (data, actions) => { console.debug("create donation", data); return actions.order.create({ purchase_units: [{ amount: { value: Number.parseFloat(amount) } }], description: campaign || "Donation", application_context: { shipping_preference: "NO_SHIPPING", }, }); }, fundingSource: paypal.FUNDING.PAYPAL, onApprove: async (_data, actions) => { const don = await actions.order.capture(); console.log("onApprove paypalResponse OneOff", don); const procaRequest = { ...formData, uuid: uuid(false), tracking: Url.utm(), }; _addContactFromPayPal(procaRequest, don.payer); const purchased = don.purchase_units[0]; procaRequest.donation = { amount: Math.floor(purchased.amount.value * 100), currency: purchased.amount.currency_code, payload: { // onApprove: data, order: don, values: formData, }, }; const procaResponse = await addDonateContact( "paypal", config.actionPage, procaRequest ); completed(procaResponse); }, onError: err => { addClick("donation_error", { source: "paypal", amount: amount, }); console.log("error", err); }, style: { shape: "rect", color: "silver", size: "responsive", height: 30, layout: "vertical", label: "paypal", ...(donateConfig?.paypal?.styles || {}), }, }); document.querySelector(dom || "#paypal-container").innerHTML = ""; buttons.render(dom || "#paypal-container"); }, [ addClick, amount, campaign, completed, config.actionPage, dom, donateConfig.paypal.styles, formData, ]); useLayoutEffect(() => { if (!amount || amount === 0 || loadState.loading) return; const isSubscription = formData.frequency !== "oneoff"; if (isSubscription && formData.frequency !== "monthly") { throw Error("Only 'monthly' with Paypal for now."); } if (loadState.loaded) { return isSubscription ? renderSubscriptionButton() : renderOneOffButton(); } setLoadState({ loading: true, loaded: false }); const script = document.createElement("script"); if (!donateConfig?.paypal?.clientId) return; //TODO: merchant-id:XXX or data-partner-attribution-id const search = new URLSearchParams(); search.append("components", "buttons"); search.append("currency", donateConfig.currency.code); console.log(config.component.donation.paypal.clientId); search.append("client-id", config.component.donation.paypal.clientId); if (isSubscription) { search.append("intent", "subscription"); search.append("vault", "true"); } const src = new URL("https://www.paypal.com/sdk/js"); src.search = search; script.src = src; script.async = true; script.addEventListener("load", () => { setLoadState({ loading: false, loaded: true }); }); document.body.appendChild(script); return () => { // https://github.com/paypal/paypal-checkout-components/issues/1506 try { if (buttons && buttons.close) { buttons.close(); } } catch (e) { console.error("Ignore error trying to close PayPal buttons", e); } }; }, [ loadState, completed, amount, campaign, dom, config.component.donation.paypal.clientId, donateConfig.currency.code, donateConfig.paypal.clientId, formData.frequency, renderOneOffButton, renderSubscriptionButton, ]); return amount > 0 ? "span" : PaypalIcon; }; export default usePaypal;