UNPKG

@daimo/pay

Version:

Seamless crypto payments. Onboard users from any chain, any coin into your app with one click.

301 lines (298 loc) 10.1 kB
import { jsx, jsxs } from 'react/jsx-runtime'; import { debugJson, DaimoPayOrderMode, DaimoPayOrderStatusSource } from '@daimo/pay-common'; import { Buffer } from 'buffer'; import React, { useMemo, useState, useRef, useCallback, useEffect, createElement } from 'react'; import { ThemeProvider } from 'styled-components'; import { WagmiContext } from 'wagmi'; import { DaimoPayModal } from '../components/DaimoPayModal/index.js'; import { ROUTES } from '../constants/routes.js'; import { REQUIRED_CHAINS } from '../defaultConfig.js'; import { useChains } from '../hooks/useChains.js'; import { useConnectCallback } from '../hooks/useConnectCallback.js'; import { useDaimoPay } from '../hooks/useDaimoPay.js'; import { usePaymentState } from '../hooks/usePaymentState.js'; import { PaymentProvider, PaymentContext } from './PaymentProvider.js'; import defaultTheme from '../styles/defaultTheme.js'; import { createTrpcClient } from '../utils/trpc.js'; import { setInWalletPaymentUrlFromApiUrl } from '../wallets/walletConfigs.js'; import { PayContext } from './PayContext.js'; import { SolanaContextProvider } from './SolanaContextProvider.js'; import { Web3ContextProvider } from './Web3ContextProvider.js'; const DaimoPayUIProvider = ({ children, theme = "auto", mode = "auto", customTheme, options, onConnect, onDisconnect, debugMode = false, payApiUrl, log }) => { if (!React.useContext(PaymentContext)) { throw Error("DaimoPayProvider must be within a PaymentProvider"); } if (!React.useContext(WagmiContext)) { throw Error("DaimoPayProvider must be within a WagmiProvider"); } if (React.useContext(PayContext)) { throw new Error( "Multiple, nested usages of DaimoPayProvider detected. Please use only one." ); } useConnectCallback({ onConnect, onDisconnect }); const chains = useChains(); for (const requiredChain of REQUIRED_CHAINS) { if (!chains.some((c) => c.id === requiredChain.id)) { throw new Error( `Daimo Pay requires chains ${REQUIRED_CHAINS.map((c) => c.name).join(", ")}. Use \`getDefaultConfig\` to automatically configure required chains.` ); } } const defaultOptions = { language: "en-US", hideBalance: false, hideTooltips: false, hideQuestionMarkCTA: false, hideRecentBadge: false, avoidLayoutShift: true, embedGoogleFonts: false, truncateLongENSAddress: true, reducedMotion: false, disclaimer: null, bufferPolyfill: true, customAvatar: void 0, initialChainId: void 0, enforceSupportedChains: false, ethereumOnboardingUrl: void 0, walletOnboardingUrl: void 0, overlayBlur: void 0, disableMobileInjector: false }; const opts = Object.assign( {}, defaultOptions, options ); if (typeof window !== "undefined") { if (opts.bufferPolyfill) window.Buffer = window.Buffer ?? Buffer; } const pay = useDaimoPay(); const [ckTheme, setTheme] = useState(theme); const [ckMode, setMode] = useState(mode); const [ckCustomTheme, setCustomTheme] = useState( customTheme ?? {} ); const [ckLang, setLang] = useState("en-US"); const [disableMobileInjector, setDisableMobileInjector] = useState( opts.disableMobileInjector ?? false ); const onOpenRef = useRef(); const onCloseRef = useRef(); const setOnOpen = useCallback((fn) => { onOpenRef.current = fn; }, []); const setOnClose = useCallback((fn) => { onCloseRef.current = fn; }, []); const [open, setOpenState] = useState(false); const [lockPayParams, setLockPayParams] = useState(false); const [paymentCompleted, setPaymentCompleted] = useState(false); const [route, setRouteState] = useState(ROUTES.SELECT_METHOD); const [modalOptions, setModalOptions] = useState(); const [uniquePaymentMethodPage, setUniquePaymentMethodPage] = useState(ROUTES.SELECT_METHOD); const [pendingConnectorId, setPendingConnectorId] = useState(void 0); const [sessionId] = useState(() => crypto.randomUUID().replaceAll("-", "")); const [solanaConnector, setSolanaConnector] = useState(); const [errorMessage, setErrorMessage] = useState(""); const [showContactSupport, setShowContactSupport] = useState(true); const [confirmationMessage, setConfirmationMessage] = useState(void 0); const [redirectReturnUrl, setRedirectReturnUrl] = useState(void 0); const trpc = useMemo(() => { return createTrpcClient(payApiUrl, sessionId); }, [payApiUrl, sessionId]); const [resize, onResize] = useState(0); useEffect(() => { setInWalletPaymentUrlFromApiUrl(payApiUrl); }, [payApiUrl]); const setOpen = useCallback( (open2, meta) => { setOpenState(open2); if (open2) { setLockPayParams(true); } if (!open2 && paymentCompleted && modalOptions?.resetOnSuccess) { setPaymentCompleted(false); setLockPayParams(false); paymentState.resetOrder(); } trpc.nav.mutate({ action: open2 ? "navOpenPay" : "navClosePay", orderId: pay.order?.id?.toString(), data: meta ?? {} }); if (open2) onOpenRef.current?.(); else onCloseRef.current?.(); }, // We don't have good caching on paymentState, so don't include it as a dep // eslint-disable-next-line react-hooks/exhaustive-deps [trpc, pay.order?.id, modalOptions?.resetOnSuccess, paymentCompleted] ); const onSuccess = useCallback(() => { if (modalOptions?.closeOnSuccess) { setTimeout(() => setOpen(false, { event: "wait-success" }), 1e3); } setPaymentCompleted(true); }, [modalOptions?.closeOnSuccess, setOpen, setPaymentCompleted]); const setRoute = useCallback( (route2, data) => { const action = route2.replace("daimoPay", ""); log(`[SET ROUTE] ${action} ${pay.order?.id} ${debugJson(data ?? {})}`); trpc.nav.mutate({ action, orderId: pay.order?.id?.toString(), data: data ?? {} }); setRouteState(route2); }, [trpc, pay.order?.id, log] ); useEffect(() => setTheme(theme), [theme]); useEffect(() => setLang(opts.language || "en-US"), [opts.language]); useEffect( () => setDisableMobileInjector(opts.disableMobileInjector ?? false), [opts.disableMobileInjector] ); useEffect(() => setErrorMessage(null), [route, open]); const paymentState = usePaymentState({ trpc, lockPayParams, setRoute, log, redirectReturnUrl }); const showPayment = async (modalOptions2) => { const id = pay.order?.id; log( `[PAY] showing modal ${debugJson({ id, modalOptions: modalOptions2, paymentFsmState: pay.paymentState })}` ); setModalOptions(modalOptions2); setOpen(true); if (modalOptions2.connectedWalletOnly) { paymentState.setTokenMode("all"); } if (pay.paymentState === "error") { setRoute(ROUTES.ERROR); } else if (pay.paymentState === "payment_started" || pay.paymentState === "payment_completed" || pay.paymentState === "payment_bounced") { setRoute(ROUTES.CONFIRMATION); } else if (modalOptions2.connectedWalletOnly) { setRoute(ROUTES.SELECT_TOKEN); } else { setRoute(ROUTES.SELECT_METHOD); } }; const isUnderpaid = pay.order?.mode === DaimoPayOrderMode.HYDRATED && pay.order.sourceStatus === DaimoPayOrderStatusSource.WAITING_PAYMENT && pay.order.sourceTokenAmount != null; useEffect(() => { if (pay.paymentState === "error") { setRoute(ROUTES.ERROR); } else if (pay.paymentState === "payment_started" || pay.paymentState === "payment_completed" || pay.paymentState === "payment_bounced") { setRoute(ROUTES.CONFIRMATION, { event: "payment-started" }); } else if (isUnderpaid) { paymentState.setSelectedDepositAddressOption(void 0); setRoute(ROUTES.WAITING_DEPOSIT_ADDRESS); } }, [pay.paymentState, setRoute, isUnderpaid]); const value = { theme: ckTheme, setTheme, mode: ckMode, setMode, customTheme, setCustomTheme, lang: ckLang, setLang, disableMobileInjector, setDisableMobileInjector, setOnOpen, setOnClose, open, setOpen, route, setRoute, // Daimo Pay context uniquePaymentMethodPage, setUniquePaymentMethodPage, pendingConnectorId, setPendingConnectorId, sessionId, solanaConnector, setSolanaConnector, onConnect, // Other configuration options: opts, errorMessage, onSuccess, showContactSupport, setShowContactSupport, confirmationMessage, setConfirmationMessage, redirectReturnUrl, setRedirectReturnUrl, debugMode, log, displayError: (message, code) => { setErrorMessage(message); console.log("---------DAIMOPAY DEBUG---------"); console.log(message); if (code) console.table(code); console.log("---------/DAIMOPAY DEBUG---------"); }, resize, triggerResize: () => onResize((prev) => prev + 1), // Above: generic ConnectKit context // Below: Daimo Pay context showPayment, paymentState, trpc }; return createElement( PayContext.Provider, { value }, /* @__PURE__ */ jsx(Web3ContextProvider, { children: /* @__PURE__ */ jsxs(ThemeProvider, { theme: defaultTheme, children: [ children, /* @__PURE__ */ jsx( DaimoPayModal, { lang: ckLang, theme: ckTheme, mode, customTheme: ckCustomTheme, disableMobileInjector } ) ] }) }) ); }; const DaimoPayProvider = (props) => { const payApiUrl = props.payApiUrl ?? "https://pay-api.daimo.xyz/"; const log = useMemo( () => props.debugMode ? (...args) => console.log(...args) : () => { }, [props.debugMode] ); return /* @__PURE__ */ jsx( PaymentProvider, { payApiUrl, log, debugMode: props.debugMode, children: /* @__PURE__ */ jsx(SolanaContextProvider, { solanaRpcUrl: props.solanaRpcUrl, children: /* @__PURE__ */ jsx(DaimoPayUIProvider, { ...props, payApiUrl, log }) }) } ); }; export { DaimoPayProvider }; //# sourceMappingURL=DaimoPayProvider.js.map