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