@duckypay/react
UI-only bindings for the hosted checkout — an embedded iframe, a pay button, and a hook. No API key ever reaches the browser.
Install#
npm install @duckypay/reactReact 18 and 19 are supported. Import @duckypay/react/styles.css once if you use the default button styling.
The pattern#
Create the charge server-side, pass the invoiceId down, render a component:
// 1. Server: create the charge with @duckypay/node
const charge = await duckypay.charges.create({ chainId: 137, amount: '25000000' });
// 2. Return only the invoiceId to the browser — never your API key
return Response.json({ invoiceId: charge.invoiceId });
// 3. Client: hand it to any of the components above.<DuckyPayCheckout>#
Embeds the hosted checkout in an iframe that auto-sizes to its content.
import { DuckyPayCheckout } from '@duckypay/react';
import '@duckypay/react/styles.css';
export function Pay({ invoiceId }: { invoiceId: string }) {
return (
<DuckyPayCheckout
invoiceId={invoiceId}
onSuccess={({ txHash, chainId }) => router.push(`/thanks?tx=${txHash}`)}
onError={(e) => toast.error(e.message)}
onExpire={() => toast('This quote expired — refresh for a new one')}
/>
);
}The charge to render.
Checkout theme override.
Checkout language, e.g. "th".
Follow the iframe's content height (default true).
Typed lifecycle callbacks — see Events below.
<DuckyPayButton>#
A drop-in pay button that opens the checkout in a centered popup (or navigates, with mode="redirect"). If the popup is blocked, it falls back to redirect automatically.
import { DuckyPayButton } from '@duckypay/react';
<DuckyPayButton
invoiceId={charge.invoiceId}
mode="popup" // 'popup' (default) or 'redirect'
onSuccess={handlePaid}
>
Pay with crypto
</DuckyPayButton>useDuckyPayCheckout()#
Headless control for custom UI — same events, your markup.
import { useDuckyPayCheckout } from '@duckypay/react';
const checkout = useDuckyPayCheckout({
onSuccess: ({ invoiceId }) => refetchOrder(invoiceId),
onClose: () => track('checkout_dismissed'),
});
// Anywhere in your handlers:
checkout.open({ invoiceId }); // centered popup (falls back to redirect if blocked)
checkout.redirect({ invoiceId }); // full-page navigation
checkout.close();App-wide defaults (baseUrl, locale) can be set once with <DuckyPayProvider>.
Events & security#
onSuccessreceives{ invoiceId, txHash, chainId };onExpirefires when the quote deadline passes;onClosewhen the buyer dismisses the popup unpaid.- Messages from the checkout are origin-checked and shape-validated — a hostile page can’t forge a success event into your handlers.