@churchapps/apphelper-donations
Version:
Donation components for ChurchApps AppHelper
114 lines • 6.44 kB
JavaScript
"use client";
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useEffect, useRef, useState, forwardRef, useImperativeHandle } from "react";
// Lightweight loader for the PayPal JS SDK with hosted-fields component
function loadPayPalSdk(clientId, clientToken) {
return new Promise((resolve, reject) => {
if (typeof window === "undefined") {
reject(new Error("Window not available"));
return;
}
if (window.paypal && window.paypal.HostedFields) {
resolve(window.paypal);
return;
}
// Avoid adding script multiple times
const existing = document.querySelector('script[data-apphelper-paypal-sdk="true"]');
if (existing) {
existing.addEventListener("load", () => resolve(window.paypal));
existing.addEventListener("error", (e) => reject(e));
return;
}
const script = document.createElement("script");
script.src = `https://www.paypal.com/sdk/js?client-id=${encodeURIComponent(clientId)}&components=hosted-fields&intent=capture&commit=true`;
script.async = true;
script.dataset.apphelperPaypalSdk = "true";
if (clientToken)
script.dataset.clientToken = clientToken;
script.addEventListener("load", () => resolve(window.paypal));
script.addEventListener("error", (e) => reject(e));
document.body.appendChild(script);
});
}
export const PayPalHostedFields = forwardRef((props, ref) => {
const containerRef = useRef(null);
const hostedFieldsRef = useRef(null);
const [isReady, setIsReady] = useState(false);
const [isValid, setIsValid] = useState(false);
useImperativeHandle(ref, () => ({
submit: async () => {
if (!hostedFieldsRef.current)
throw new Error("Hosted Fields not ready");
const result = await hostedFieldsRef.current.submit({ contingencies: ["3D_SECURE"] });
return result;
},
isReady
}), [isReady]);
useEffect(() => {
let cancelled = false;
(async () => {
try {
// Require HTTPS (allow localhost for development). Hosted Fields won't function over plain HTTP.
if (typeof window !== "undefined") {
const isHttps = window.location.protocol === "https:" || window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1";
if (!isHttps) {
props.onIneligible?.("PayPal HostedFields requires HTTPS");
return;
}
}
let clientToken;
if (props.getClientToken) {
try {
clientToken = await props.getClientToken();
}
catch { /* ignore */ }
}
const paypal = await loadPayPalSdk(props.clientId, clientToken);
if (cancelled)
return;
if (!paypal || !paypal.HostedFields) {
throw new Error("PayPal HostedFields unavailable");
}
if (!paypal.HostedFields.isEligible()) {
props.onIneligible?.("PayPal HostedFields not eligible (missing client token or merchant not enabled)");
// Do not throw to allow parent to render a fallback
return;
}
const hf = await paypal.HostedFields.render({
createOrder: async () => {
const orderId = await props.createOrder();
return orderId;
},
styles: {
"input": { "font-size": "16px" },
":focus": { "color": "black" },
".invalid": { "color": "red" },
".valid": { "color": "green" }
},
fields: {
number: { selector: "#pp-hf-number", placeholder: "4111 1111 1111 1111" },
cvv: { selector: "#pp-hf-cvv", placeholder: "123" },
expirationDate: { selector: "#pp-hf-expiry", placeholder: "MM/YY" }
}
});
hostedFieldsRef.current = hf;
setIsReady(true);
hf.on("validityChange", (event) => {
const allValid = Object.values(event.fields || {}).every((f) => f.isValid);
setIsValid(allValid);
props.onValidityChange?.(allValid);
});
}
catch (e) {
console.error("Failed to initialize PayPal Hosted Fields:", e);
setIsReady(false);
props.onValidityChange?.(false);
props.onIneligible?.(e?.message || "Initialization failed");
}
})();
return () => { cancelled = true; };
}, [props.clientId]);
return (_jsxs("div", { ref: containerRef, children: [_jsxs("div", { style: { padding: 10, border: "1px solid #CCC", borderRadius: 5, backgroundColor: "white" }, children: [_jsx("label", { htmlFor: "pp-hf-number", style: { display: "block", fontWeight: 600 }, children: "Card Number" }), _jsx("div", { id: "pp-hf-number", style: { padding: 8, border: "1px solid #eee", borderRadius: 4, marginBottom: 8 } }), _jsxs("div", { style: { display: "flex", gap: 8 }, children: [_jsxs("div", { style: { flex: 1 }, children: [_jsx("label", { htmlFor: "pp-hf-expiry", style: { display: "block", fontWeight: 600 }, children: "Expiry" }), _jsx("div", { id: "pp-hf-expiry", style: { padding: 8, border: "1px solid #eee", borderRadius: 4 } })] }), _jsxs("div", { style: { width: 120 }, children: [_jsx("label", { htmlFor: "pp-hf-cvv", style: { display: "block", fontWeight: 600 }, children: "CVV" }), _jsx("div", { id: "pp-hf-cvv", style: { padding: 8, border: "1px solid #eee", borderRadius: 4 } })] })] })] }), !isReady && _jsx("div", { style: { marginTop: 8, color: "#666" }, children: "Loading PayPal secure card fields\u2026" }), isReady && !isValid && _jsx("div", { style: { marginTop: 8, color: "#666" }, children: "Enter full card details to continue." })] }));
});
export default PayPalHostedFields;
//# sourceMappingURL=PayPalHostedFields.js.map