Checkout & payments
One-click checkout with @hyperyai/sdk — BuyButton and useCheckout run login → charge → add-card → retry as a popup or redirect, for marketplace purchases and AI-credit top-ups.
@hyperyai/sdk ships a seamless auth + charge flow: a single action drives the
whole chain — press buy → log in (if needed) → charge → add a card (if needed) →
retry → done. An unauthenticated visitor can click Buy and be walked through
sign-in, card entry, and payment without you writing any of that glue.
It comes in two forms:
<BuyButton>— a drop-in "buy with Hypery" button for one-click marketplace purchases.useCheckout()— the hook underneath, for programmatic checkout (purchases and AI-credit top-ups) on your own UI.
Each interactive step (login, card entry) runs as a popup or a redirect,
chosen by interactionMode — so the flow completes in-page on desktop and stays
robust on mobile.
Setup
Checkout reads everything it needs from HyperyProvider — no extra provider. The
only new option is interactionMode:
// app/layout.tsx
import { HyperyProvider } from "@hyperyai/sdk";
export default function RootLayout({ children }) {
return (
<HyperyProvider
config={{
clientId: process.env.NEXT_PUBLIC_HYPERY_CLIENT_ID!,
redirectUri: "https://yourapp.com/callback",
gatewayUrl: "https://hypery.ai",
interactionMode: "auto", // 'auto' | 'popup' | 'redirect'
}}
>
{children}
</HyperyProvider>
);
}interactionMode defaults to auto: popup on desktop, redirect on mobile
or when a popup is blocked. For popup mode, redirectUri must be same-origin
as your app (the popup posts the result back to the opener).
BuyButton
Drop-in checkout for a marketplace purchase. On click it runs the full flow via
useCheckout and credits the sale to the seller's app (a Stripe Connect direct
charge; your platform fee is applied automatically).
import { BuyButton } from "@hyperyai/sdk";
<BuyButton
appId="app_123"
amountCents={499}
description="Pro upgrade"
onSuccess={(r) => console.log("paid", r.paymentIntentId)}
onError={(e) => toast.error(e.message)}
/>;By default the button is two-click — the first click arms it
(Confirm · $4.99 + Cancel), the second charges — because the card is charged
off-session and the buyer should give a clear "yes". Set requireConfirmation={false}
to charge on a single click.
onSuccess receives { ok, paymentIntentId, status, amountCents, applicationFeeCents }.
The button never throws — a redirecting (navigating away) or cancelled
(user closed a popup) result stays quiet, so no callback fires.
useCheckout
The hook behind BuyButton. Use it directly when you want checkout on your own
button, or for AI-credit top-ups (which BuyButton doesn't cover).
import { useCheckout } from "@hyperyai/sdk";
function AddCredits() {
const { checkout, status, isRunning } = useCheckout();
const buy = async () => {
const result = await checkout({ kind: "topup", usdAmount: 5 });
if (result.status === "success") {
// balance is funded
}
};
return (
<button onClick={buy} disabled={isRunning}>
{status === "charging" ? "charging…" : "add $5 of credits"}
</button>
);
}useCheckout() returns:
checkout(input) takes one of two shapes:
// marketplace purchase (Stripe Connect)
checkout({ kind: "purchase", appId: "app_123", amountCents: 499, description: "Pro" });
// buy AI credits for the signed-in user's balance
checkout({ kind: "topup", usdAmount: 5 });Purchases are idempotent: a stable idempotencyKey is generated (or pass your
own) so a resumed or retried charge is never double-billed.
Popup vs redirect
interactionMode controls how the login and add-card steps appear.
Each step opens a centered popup and the chain completes in-page — no
navigation (like PayPal / Firebase signInWithPopup). Best on desktop.
Requires a same-origin redirectUri.
Each step is a full-page redirect. The pending checkout is persisted to
localStorage and auto-resumes when the user returns — robust on mobile and
when popups are blocked.
The default. Uses popup on desktop and falls back to redirect on mobile or when a popup is blocked. Pick this unless you have a reason not to.
In redirect mode the flow can navigate away mid-checkout and resume on return —
so treat checkout() returning redirecting as "in progress", not "done".
Resume runs automatically once per page load.
Handling the result
checkout() resolves to a CheckoutResult:
const result = await checkout({ kind: "topup", usdAmount: 5 });
switch (result.status) {
case "success": /* result.data has the charge */ break;
case "error": /* result.error is a ParsedError */ break;
case "cancelled": /* user closed a popup — no-op */ break;
case "redirecting": /* navigating away; resumes on return */ break;
}Errors are already classified (ParsedError) — the same shape
RestrictionModal consumes — so a
payment decline or a spending-limit hit can drive a modal without extra parsing.