UNPKG

@coin-voyage/paykit

Version:

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

222 lines 9.39 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { useAccount, useConnectCallback } from "@coin-voyage/crypto/hooks"; import { PayOrderMode, PayOrderStatus } from "@coin-voyage/shared/common"; import { Buffer } from "buffer"; import { useCallback, useContext, useEffect, useRef, useState } from "react"; import { ThemeProvider as StyledThemeProvider } from "styled-components"; import { WagmiContext } from "wagmi"; import { PayContext } from "../components/contexts/pay/index"; import { Web3ContextProvider } from "../components/contexts/web3/index"; import { PayModal } from "../components/pay-modal/index"; import { useChainIsSupported } from "../hooks/useChainIsSupported"; import { useThemeFont } from "../hooks/useGoogleFont"; import { usePaymentState } from "../hooks/usePaymentState"; import defaultTheme from "../styles/defaultTheme"; import { ROUTES } from "../types/routes"; import { ApiProvider } from "./api-provider"; const defaultOptions = { language: "en-US", hideBalance: false, hideTooltips: false, hideQuestionMarkCTA: false, hideNoWalletCTA: false, walletConnectCTA: "link", hideRecentBadge: false, avoidLayoutShift: true, embedGoogleFonts: false, truncateLongENSAddress: true, walletConnectName: undefined, reducedMotion: false, disclaimer: null, bufferPolyfill: true, initialChainId: undefined, enforceSupportedChains: false, ethereumOnboardingUrl: undefined, walletOnboardingUrl: undefined, overlayBlur: undefined, }; export const PayKitProvider = ({ apiKey, environment = "production", ...props }) => { const wagmiContext = useContext(WagmiContext); if (!wagmiContext) { throw new Error("PayKitProvider must be used within a WagmiContext"); } return (_jsx(ApiProvider, { apiKey: apiKey, environment: environment, children: _jsx(PayKitProviderInternal, { ...props }) })); }; function PayKitProviderInternal({ theme = "auto", mode = "auto", customTheme, options, onConnect, onConnectValidation, onDisconnect, debugMode = false, children }) { const [allowedWallets, setAllowedWallets] = useState(!!onConnectValidation ? [] : null); useConnectCallback({ onConnect, onDisconnect, onConnectValidation, setAllowedWallets, }); const opts = Object.assign({}, defaultOptions, options); if (typeof window !== "undefined") { // Buffer Polyfill, needed for bundlers that don't provide Node polyfills (e.g CRA, Vite, etc.) if (opts.bufferPolyfill) { window.Buffer = window.Buffer ?? Buffer; } // Some bundlers may need `global` and `process.env` polyfills as well // Not implemented here to avoid unexpected behaviors, but leaving example here for future reference /* * window.global = window.global ?? window; * window.process = window.process ?? { env: {} }; */ } const [ckTheme, setTheme] = useState(theme); const [ckMode, setMode] = useState(mode); const [ckCustomTheme, setCustomTheme] = useState(customTheme ?? {}); const [ckLang, setLang] = useState("en-US"); const [open, setOpenState] = useState(false); const [lockPayParams, setLockPayParams] = useState(false); const [paymentCompleted, setPaymentCompleted] = useState(false); const [route, setRoute] = useState(ROUTES.SELECT_METHOD); const [modalOptions, setModalOptions] = useState(); const [errorMessage, setErrorMessage] = useState(""); const [resize, onResize] = useState(0); const onOpenRef = useRef(); const onCloseRef = useRef(); const setOnOpen = useCallback((fn) => { onOpenRef.current = fn; }, []); const setOnClose = useCallback((fn) => { onCloseRef.current = fn; }, []); // Include Google Font that is needed for a themes if (opts.embedGoogleFonts) { useThemeFont(theme); } const setOpen = useCallback((open) => { setOpenState(open); // Locking pay params the first time the modal is opened to // prevent the payOrder from changing while the modal is open. if (open) { setLockPayParams(true); } // Reset payment state on close if resetOnSuccess is true if (!open && paymentCompleted && modalOptions?.resetOnSuccess) { setPaymentCompleted(false); setLockPayParams(false); paymentState.resetPayOrder(); } // Run the onOpen and onClose callbacks if (open) 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 [modalOptions?.resetOnSuccess, paymentCompleted]); // Callback when a payOrder is successfully completed (regardless of whether // the final call succeeded or bounced) const onSuccess = useCallback(() => { if (modalOptions?.closeOnSuccess) { setTimeout(() => setOpen(false), 1000); } setPaymentCompleted(true); }, [modalOptions?.closeOnSuccess, setOpen, setPaymentCompleted]); // Other Configuration useEffect(() => setTheme(theme), [theme]); useEffect(() => setLang(opts.language || "en-US"), [opts.language]); useEffect(() => setErrorMessage(null), [route, open]); // Check if chain is supported, elsewise redirect to switches page const { account } = useAccount(); const isChainSupported = useChainIsSupported(account.chainType, account?.chainId); useEffect(() => { if (account.isConnected && opts.enforceSupportedChains && !isChainSupported) { setOpen(true); setRoute(ROUTES.SWITCHNETWORKS); } }, [account, isChainSupported, route, open]); const log = debugMode ? console.log : () => { }; // PaymentState is a second, inner context object containing a PayOrder // plus all associated status and callbacks. In order for useContext() and // downstream hooks like usePayStatus() to work correctly, we must set // set refresh context when payment status changes; done via setPayOrder. const [payOrder, setPayOrder] = useState(); const paymentState = usePaymentState({ payOrder, setPayOrder, setRoute, log }); // When a payment is in progress, poll for status updates. Do this regardless // of whether the modal is still being displayed for usePayStatus(). useEffect(() => { if (!payOrder?.status) return; let intervalMs = 0; if ([PayOrderStatus.AWAITING_PAYMENT, PayOrderStatus.AWAITING_CONFIRMATION].includes(payOrder.status)) { intervalMs = 2500; } else if (payOrder?.status === PayOrderStatus.EXECUTING_ORDER) { // Poll faster for deposits, sales completion is not/less relevant for users intervalMs = payOrder.mode === PayOrderMode.DEPOSIT ? 1000 : 2500; } else { return; } log(`[PAY] polling in ${intervalMs}ms`); setTimeout(paymentState.refreshOrder, intervalMs); }, [payOrder]); const showPayment = (modalOptions) => { setModalOptions(modalOptions); setOpen(true); const payOrder = paymentState.payOrder; if (payOrder?.status === PayOrderStatus.COMPLETED) { setRoute(ROUTES.CONFIRMATION); } else if (payOrder?.status === PayOrderStatus.AWAITING_PAYMENT && payOrder.source_amount && payOrder.source_currency) { paymentState.setSelectedTokenOption({ ...payOrder.source_currency, currency_amount: payOrder.source_amount, }); setRoute(ROUTES.PAY_WITH_TOKEN); // TODO: handle not connected // paymentState.setConnectorChainType(getChainTypeByChainId(payOrder.source_currency?.chain_id)) // const isConnected = account.chainId === payOrder.source_currency?.chain_id && account.isConnected // setRoute(isConnected ? ROUTES.PAY_WITH_TOKEN : ROUTES.CONNECTORS) } else { setRoute(ROUTES.SELECT_METHOD); } }; const value = { theme: ckTheme, setTheme, mode: ckMode, setMode, customTheme: ckCustomTheme, setCustomTheme, lang: ckLang, setLang, setOnOpen, setOnClose, open, setOpen, onSuccess, route, setRoute, // Other configuration options: opts, errorMessage, debugMode, log, displayError: (message, code) => { setErrorMessage(message); console.log("---------COINVOYAGE DEBUG---------"); if (code) console.table(code); console.log("---------/COINVOYAGE DEBUG---------"); }, resize, triggerResize: () => onResize((prev) => prev + 1), // Pay context showPayment, paymentState, allowedWallets, }; return (_jsx(PayContext.Provider, { value: value, children: _jsx(Web3ContextProvider, { enabled: open, children: _jsxs(StyledThemeProvider, { theme: defaultTheme, children: [children, _jsx(PayModal, {})] }) }) })); } //# sourceMappingURL=paykit-provider.js.map