zro-payment
v0.2.2
Published
Branded ZRO payment QR code component
Readme
zro-payment
Accept payments on your website from any ZRO wallet.
Drop in a button. Your customer pays on the same device, or by scanning a QR code with the ZRO app on their phone. Settlement is onchain, instant and feeless.
Documentation · Get an API key
npm install zro-paymentQuick start
import { ZROPayButton } from 'zro-payment';
export default function Checkout() {
return (
<ZROPayButton
apiKey="your_api_key"
amount={24.99}
returnUrl="https://yourstore.com/order/1234/thanks"
onSuccess={(txHash) => showThankYou(txHash)}
/>
);
}Create an API key at zroapp.xyz/docs/keys. Each key is scoped to one Base, and payments settle into that Base's wallet.
[!IMPORTANT]
onSuccessis a UI signal, not proof of payment. Confirm server-side before fulfilling an order — see Security.
How a payment works
- The customer clicks the button. The SDK creates a payment and receives a
payment_id. - The modal offers two ways to pay:
- Pay on ZRO — opens the hosted checkout, where the customer confirms from their ZRO wallet. This is the path that works when they are already on their phone and cannot scan their own screen.
- Scan the QR — for a customer at a desktop with the ZRO app on their phone.
- The SDK polls until the payment settles onchain, then calls
onSuccess(txHash).
API
<ZROPayButton>
| Prop | Type | Default | Description |
|---|---|---|---|
| apiKey | string | — | Required. Key for the Base being paid. |
| amount | number | — | Required. See Currency. |
| currency | 'USDC' \| 'CADC' | 'USDC' | Token the amount is denominated in. |
| returnUrl | string | — | Absolute http(s) URL to return the customer to after paying. |
| onSuccess | (txHash: string) => void | — | Fires when the payment settles. UI only. |
| apiUrl | string | ZRO API | Override for staging. |
| walletUrl | string | https://zroapp.xyz | Hosted checkout origin. Override for staging. |
Appearance
| Prop | Type | Default | Description |
|---|---|---|---|
| label | string | 'Pay with ZRO' | Button text. |
| accentColor | string | '#7C3AED' | Background colour of the trigger button. |
| textColor | string | '#fff' | Text colour on the accent. |
| radius | number \| string | 10 | Corner radius. 9999 for a pill, 0 for a square. |
| className | string | — | Your own class. Turns off the built-in styles — see below. |
| style | CSSProperties | — | Style overrides, merged last. |
Styling the button
Three levels, depending on how much control you want.
Theme it — keep the built-in layout, change the branding:
<ZROPayButton
apiKey="your_api_key"
amount={24.99}
label="Buy now"
accentColor="#ff5500"
radius={9999} // pill
/>These props style the trigger button on your page. The modal's QR code and its Pay on ZRO button keep ZRO's own branding — once the customer is being handed to ZRO, those elements should look like ZRO.
Tweak it — merge individual CSS properties over the defaults:
<ZROPayButton apiKey="..." amount={24.99} style={{ padding: '14px 32px', fontSize: 16 }} />Own it — pass a class and style it entirely yourself:
<ZROPayButton apiKey="..." amount={24.99} className="btn btn-primary" label="Checkout" />[!NOTE] Passing
classNameswitches the built-in inline styles off. Inline styles beat CSS, so if they stayed on, your stylesheet would be silently overridden. With a class present, only your CSS and any explicitstyleprop apply.
For total control over the whole flow — your own modal, your own QR placement —
use useZROPayment instead.
useZROPayment(options)
The same options, when you want to build the UI yourself.
const { isOpen, state, open, close, payUrl } = useZROPayment({
apiKey: 'your_api_key',
amount: 24.99,
});| Returns | Description |
|---|---|
| state.status | 'idle' · 'loading' · 'ready' · 'completed' · 'error' |
| state.qrPayload | Pass to <ZROQRCode> |
| state.paymentId | Use this to confirm the payment from your server |
| state.error | Human-readable message when status === 'error' |
| payUrl | Hosted checkout link for the same-device path |
| open() / close() | Control the flow |
<ZROQRCode>
<ZROQRCode payload={state.qrPayload} size={180} />Renders the branded QR code the ZRO app scans.
Security
onSuccess is forgeable. It is a client-side callback: a customer can invoke
it from the browser console, or stub the network request, and receive the goods
without paying. Use it to show a receipt or advance the checkout step — then
confirm the payment from your own server before releasing anything of value.
Treat apiKey as public. It ships in your site's JavaScript, so anyone can
read it. It cannot redirect funds — the destination wallet is set server-side by
the key's Base — but it can be used to create payments against your daily limit.
Set an allowed domain on the key to limit where it can be used from.
Currency
amount is denominated in whatever you pass as currency, and no conversion is
applied.
<ZROPayButton apiKey="..." amount={9.99} currency="CADC" />Charging 9.99 as CADC means you receive 9.99 CADC, not 9.99 USDC. Price in the
currency you are willing to accept, and reconcile the settled amount server-side
before fulfilling.
amount must be finite, greater than zero, no more than 1,000,000, and have
at most two decimal places.
Rate limits
Creating a payment counts against your key's daily limit. Status polling does not — you are billed for checkouts, not for how long a customer leaves the modal open.
The SDK polls every 2s for the first 30s, then every 10s, and stops after 5
minutes. It gives up immediately on 401, 403, 404 and 429 rather than
retrying. A checkout expires 15 minutes after it is created if no transaction
has been submitted for verification.
Requirements
React 17 or later. The package ships a "use client" banner, so it can be
imported directly from a Next.js App Router server component.
Support
Questions and issues: [email protected]
License
MIT
