UNPKG

@churchapps/apphelper-donations

Version:

Donation components for ChurchApps AppHelper

114 lines 6.44 kB
"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