UNPKG

@daimo/pay

Version:

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

200 lines (197 loc) 8.23 kB
import { jsx } from 'react/jsx-runtime'; import { useEffect, useCallback, useRef } from 'react'; import { usePayContext } from '../../hooks/usePayContext.js'; import { TextContainer } from './styles.js'; import { getOrderSourceChainId, DaimoPayEventType, writeDaimoPayOrderID, getDaimoPayOrderView, getOrderDestChainId, assertNotNull } from '@daimo/pay-common'; import { AnimatePresence } from 'framer-motion'; import { useDaimoPay } from '../../hooks/useDaimoPay.js'; import { ResetContainer } from '../../styles/index.js'; import ThemedButton from '../Common/ThemedButton/index.js'; import { ThemeContainer } from '../Common/ThemedButton/styles.js'; /** * A button that shows the Daimo Pay checkout. Replaces the traditional * Connect Wallet » approve » execute sequence with a single action. */ function DaimoPayButton(props) { const { theme, mode, customTheme } = props; const context = usePayContext(); return (jsx(DaimoPayButtonCustom, { ...props, children: ({ show }) => (jsx(ResetContainer, { "$useTheme": theme ?? context.theme, "$useMode": mode ?? context.mode, "$customTheme": customTheme ?? context.customTheme, children: jsx(ThemeContainer, { onClick: props.disabled ? undefined : show, children: jsx(ThemedButton, { theme: theme ?? context.theme, mode: mode ?? context.mode, customTheme: customTheme ?? context.customTheme, children: jsx(DaimoPayButtonInner, {}) }) }) })) })); } /** Like DaimoPayButton, but with custom styling. */ function DaimoPayButtonCustom(props) { const context = usePayContext(); // Pre-load payment info in background. // Reload when any of the info changes. let payParams = "appId" in props ? { appId: props.appId, toChain: props.toChain, toAddress: props.toAddress, toToken: props.toToken, toUnits: props.toUnits, toCallData: props.toCallData, intent: props.intent, paymentOptions: props.paymentOptions, preferredChains: props.preferredChains, preferredTokens: props.preferredTokens, evmChains: props.evmChains, externalId: props.externalId, metadata: props.metadata, refundAddress: props.refundAddress, } : null; let payId = "payId" in props ? props.payId : null; const { paymentState, log } = context; const { order, paymentState: payState } = useDaimoPay(); // Set the payId or payParams useEffect(() => { if (payId != null) { paymentState.setPayId(payId); } else if (payParams != null) { paymentState.setPayParams(payParams); } paymentState.setButtonProps(props); // eslint-disable-next-line react-hooks/exhaustive-deps }, [payId, JSON.stringify(payParams || {})]); // Set the confirmation message const { setConfirmationMessage } = context; useEffect(() => { if (props.confirmationMessage) { setConfirmationMessage(props.confirmationMessage); } }, [props.confirmationMessage, setConfirmationMessage]); // Set the redirect return url const { setRedirectReturnUrl } = context; useEffect(() => { if (props.redirectReturnUrl) { setRedirectReturnUrl(props.redirectReturnUrl); } }, [props.redirectReturnUrl, setRedirectReturnUrl]); // Set the onOpen and onClose callbacks const { setOnOpen, setOnClose } = context; useEffect(() => { setOnOpen(props.onOpen); return () => setOnOpen(undefined); }, [props.onOpen, setOnOpen]); useEffect(() => { setOnClose(props.onClose); return () => setOnClose(undefined); }, [props.onClose, setOnClose]); // Payment events: call these three event handlers. const { onPaymentStarted, onPaymentCompleted, onPaymentBounced } = props; // Functions to show and hide the modal const { children, closeOnSuccess, resetOnSuccess, connectedWalletOnly } = props; const show = useCallback(() => { const modalOptions = { closeOnSuccess, resetOnSuccess, connectedWalletOnly, }; context.showPayment(modalOptions); }, [connectedWalletOnly, closeOnSuccess, resetOnSuccess, context]); const hide = useCallback(() => context.setOpen(false), [context]); // Emit onPaymentStart handler when payment state changes to payment_started const sentStart = useRef(false); useEffect(() => { if (sentStart.current) return; if (payState !== "payment_started") return; // TODO: Populate source payment details immediately when the user pays. // Use this hack because source chain id is not immediately populated when // payment_started const sourceChainId = getOrderSourceChainId(order); if (sourceChainId == null) return; sentStart.current = true; onPaymentStarted?.({ type: DaimoPayEventType.PaymentStarted, paymentId: writeDaimoPayOrderID(order.id), chainId: sourceChainId, txHash: order.sourceInitiateTxHash, payment: getDaimoPayOrderView(order), }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [order, payState]); // Emit onPaymentComplete or onPaymentBounced handler when payment state // changes to payment_completed or payment_bounced const sentComplete = useRef(false); useEffect(() => { if (sentComplete.current) return; if (payState !== "payment_completed" && payState !== "payment_bounced") return; sentComplete.current = true; const eventType = payState === "payment_completed" ? DaimoPayEventType.PaymentCompleted : DaimoPayEventType.PaymentBounced; const event = { type: eventType, paymentId: writeDaimoPayOrderID(order.id), chainId: getOrderDestChainId(order), txHash: assertNotNull(order.destFastFinishTxHash ?? order.destClaimTxHash, `[PAY BUTTON] dest tx hash null on order ${order.id} when intent status is ${order.intentStatus}`), payment: getDaimoPayOrderView(order), }; if (payState === "payment_completed") { onPaymentCompleted?.(event); } else if (payState === "payment_bounced") { onPaymentBounced?.(event); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [order, payState]); // Open the modal by default if the defaultOpen prop is true const hasAutoOpened = useRef(false); useEffect(() => { if (!props.defaultOpen || hasAutoOpened.current) return; if (order == null) return; show(); hasAutoOpened.current = true; // eslint-disable-next-line react-hooks/exhaustive-deps }, [order, props.defaultOpen, hasAutoOpened.current]); // Validation if ((payId == null) == (payParams == null)) { throw new Error("Must specify either payId or appId, not both"); } return children({ show, hide }); } DaimoPayButtonCustom.displayName = "DaimoPayButton.Custom"; DaimoPayButton.Custom = DaimoPayButtonCustom; const contentVariants = { initial: { zIndex: 2, opacity: 0, x: "-100%", }, animate: { opacity: 1, x: 0.1, transition: { duration: 0.4, ease: [0.25, 1, 0.5, 1], }, }, exit: { zIndex: 1, opacity: 0, x: "-100%", pointerEvents: "none", position: "absolute", transition: { duration: 0.4, ease: [0.25, 1, 0.5, 1], }, }, }; function DaimoPayButtonInner() { const { order } = useDaimoPay(); const label = order?.metadata?.intent ?? "Pay"; return (jsx(AnimatePresence, { initial: false, children: jsx(TextContainer, { initial: "initial", animate: "animate", exit: "exit", variants: contentVariants, style: { height: 40, }, children: label }) })); } export { DaimoPayButton, DaimoPayButtonInner }; //# sourceMappingURL=index.js.map