@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
347 lines (297 loc) • 8.67 kB
JavaScript
import React, { useCallback } from "react";
import Url from "@lib/urlparser.js";
import uuid from "@lib/uuid";
import useData from "@hooks/useData";
import { useCampaignConfig } from "@hooks/useConfig";
import dispatch from "@lib/event";
import {
PayPalButtons,
FUNDING,
usePayPalScriptReducer,
DISPATCH_ACTION,
} from "@paypal/react-paypal-js";
import { addDonateContact } from "@lib/server.js";
import { Box, Button, CircularProgress, makeStyles } from "@material-ui/core";
const _addContactFromPayPal = (setFormData, 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;
setFormData("firstname", contact.firstname);
setFormData("lastname", contact.lastname);
setFormData("email", contact.email);
setFormData("phone", contact.phone);
const address = payer?.address || payer?.shipping_address?.address;
if (address) {
contact.country = address?.country_code;
contact.postcode = address?.postal_code;
}
};
// // -------------- Subscriptions ---------------------------------------------------
async function onApproveSubscription({
setFormData,
formData,
actionPage,
paypalResponse,
actions,
onComplete,
isTest,
}) {
// const order = await actions.order.get();
const subscription = await actions.subscription.get();
const procaRequest = {
...formData,
uuid: uuid(false),
tracking: Url.utm(),
};
_addContactFromPayPal(setFormData, 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",
actionPage,
procaRequest
);
dispatch(
"donate:complete",
{
payment: "paypal",
uuid: procaResponse.contactRef,
test: isTest,
firstname: formData.firstname,
amount: formData.amount,
currency: subscriptionAmount.currency_code,
frequency: formData.frequency || "monthly",
country: formData.country,
},
procaRequest
);
onComplete(procaResponse);
}
// -------------- OneOff ---------------------------------------------------
const onApproveOrder = async ({
setFormData,
formData,
actionPage,
onComplete,
actions,
paypalResponse,
isTest,
}) => {
const order = await actions.order.capture();
const procaRequest = {
...formData,
uuid: uuid(false),
tracking: Url.utm(),
};
_addContactFromPayPal(setFormData, procaRequest, order.payer);
const purchased = order.purchase_units[0];
procaRequest.donation = {
amount: Math.floor(purchased.amount.value * 100),
currency: purchased.amount.currency_code,
payload: {
response: paypalResponse,
order: order,
values: formData,
},
};
const procaResponse = await addDonateContact(
"paypal",
actionPage,
procaRequest
);
dispatch(
"donate:complete",
{
payment: "paypal",
uuid: procaResponse.contactRef,
test: isTest,
firstname: formData.firstname,
amount: formData.amount,
currency: purchased.amount.currency_code,
frequency: "oneoff",
country: formData.country,
},
procaRequest
);
onComplete(procaResponse);
};
const onCreateOrder = ({ amount, description, actions }) => {
console.debug(amount,description,actions);
return actions.order.create({
purchase_units: [{ amount: { value: Number.parseFloat(amount) } }],
description: description,
application_context: {
shipping_preference: "NO_SHIPPING",
},
});
};
const useStyles = makeStyles({
root: {
borderRadius: "4px",
backgroundColor: "#ffc439",
color: "#003087",
textAlign: "center",
height: "45px",
minHeight: "45px",
},
});
const LoadingSpinner = () => {
const classes = useStyles();
const config = useCampaignConfig();
return (
<Button
className="submit-button"
name="submit"
color="primary"
variant={
config.layout?.button?.submit?.variant ||
config.layout?.button?.variant ||
"contained"
}
fullWidth
type="submit"
size="large"
classes={{ root: classes.root }}
>
<CircularProgress color="inherit" size={30} />
</Button>
);
};
const useFrequencyChange = frequency => {
const [{ options }, dispatch] = usePayPalScriptReducer();
let changed = false;
// anything but oneoff is a subscription
if (frequency !== "oneoff" && options.intent !== "subscription") {
changed = true;
options.intent = "subscription";
options.vault = "true";
// oneoff should never have intent
} else if (frequency === "oneoff" && options.intent !== "") {
changed = true;
options.intent = "";
options.value = "";
} else {
// noop - we're already in sync
}
if (changed) {
dispatch({
type: DISPATCH_ACTION.RESET_OPTIONS,
value: options,
});
}
return frequency;
};
const ProcaPayPalButton = props => {
const config = useCampaignConfig();
const donateConfig = config.component.donation;
const [formData, setFormData] = useData();
const amount = formData.amount;
const description = config.campaign.title || "Donation";
const actionPage = config.actionPage;
const classes = useStyles();
const [{ isPending }] = usePayPalScriptReducer();
// const frequency = props.frequency;
const frequency = useFrequencyChange(props.frequency);
const createOrder = useCallback(
(paypalResponse, actions) => {
setFormData("paymentMethod", "paypal");
return onCreateOrder({
amount: amount,
description: description,
paypalResponse: paypalResponse,
actions: actions,
isTest: !!config.test,
});
},
[amount, config.test, description, setFormData]
);
const approveOrder = useCallback(
(paypalResponse, actions) => {
return onApproveOrder({
setFormData: setFormData,
formData: formData,
actionPage: actionPage,
paypalResponse: paypalResponse,
actions: actions,
onComplete: props.onComplete,
});
},
[setFormData, formData, actionPage, props.onComplete]
);
const plan_id = donateConfig.paypal.planId;
const createSubscription = useCallback(
(_data, actions) => {
setFormData("paymentMethod", "paypal");
return actions.subscription.create({
plan_id: plan_id,
quantity: Math.floor(amount * 100).toString(), // PayPal wants a string
application_context: {
shipping_preference: "NO_SHIPPING",
},
});
},
[amount, plan_id, setFormData]
);
const approveSubscription = useCallback(
(paypalResponse, actions) => {
return onApproveSubscription({
paypalResponse: paypalResponse,
actions: actions,
setFormData: setFormData,
formData: formData,
onComplete: props.onComplete,
actionPage: actionPage,
isTest: !!config.test,
});
},
[setFormData, formData, props.onComplete, actionPage, config.test]
);
const configuredStyles = donateConfig.paypal?.styles || {
color: "gold", // how do I read the variant to know this?
height: 45,
};
const sharedOptions = {
fundingSource: FUNDING.PAYPAL,
commit: true, // no server-side calls
style: configuredStyles,
...props,
};
const orderOptions = {
...sharedOptions,
createOrder: createOrder,
onApprove: approveOrder,
};
const subscriptionOptions = {
...sharedOptions,
createSubscription: createSubscription,
onApprove: approveSubscription,
};
const buttonOptions =
frequency === "oneoff" ? orderOptions : subscriptionOptions;
console.log(buttonOptions);
return (
<Box classes={{ root: classes.root }} className="proca-MuiButton-contained">
{" "}
{isPending ? <LoadingSpinner /> : <PayPalButtons {...buttonOptions} />}
</Box>
);
};
export default ProcaPayPalButton;