@coin-voyage/paykit
Version:
Seamless crypto payments. Onboard users from any chain, any coin into your app with one click.
199 lines • 8.19 kB
JavaScript
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 { 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",
hideTooltips: false,
hideQuestionMarkCTA: false,
hideNoWalletCTA: false,
walletConnectCTA: "link",
hideRecentBadge: false,
embedGoogleFonts: false,
walletConnectName: undefined,
disclaimer: null,
bufferPolyfill: true,
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" && opts.bufferPolyfill) {
// Buffer Polyfill, needed for bundlers that don't provide Node polyfills (e.g CRA, Vite, etc.)
window.Buffer = window.Buffer ?? Buffer;
}
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]);
const { account } = useAccount();
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 has started, poll for updates. Do this regardless
// of whether the modal is still being displayed for usePayStatus().
useEffect(() => {
if (!payOrder?.status)
return;
let intervalMs = 0;
if (payOrder.status === PayOrderStatus.AWAITING_PAYMENT) {
intervalMs = 5000;
}
else if ([PayOrderStatus.AWAITING_CONFIRMATION, PayOrderStatus.OPTIMISTIC_CONFIRMED].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;
}
const timeout = setTimeout(paymentState.refreshOrder, intervalMs);
return () => {
clearTimeout(timeout);
};
}, [payOrder]);
const showPayment = (modalOptions) => {
setModalOptions(modalOptions);
setOpen(true);
const isConnected = account.isConnected;
if (isConnected) {
paymentState.setConnectorChainType(account.chainType);
}
const payOrder = paymentState.payOrder;
if (payOrder?.status === PayOrderStatus.COMPLETED) {
setRoute(ROUTES.CONFIRMATION);
}
else if (payOrder?.status === PayOrderStatus.AWAITING_PAYMENT && payOrder.payment?.src) {
paymentState.setSelectedTokenOption(payOrder.payment.src);
setRoute(ROUTES.PAY_WITH_TOKEN);
}
else {
setRoute(isConnected ? ROUTES.SELECT_TOKEN : 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