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