@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
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 { 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