Usage
Initialization
The SDK exposes the same OozooPayClient class regardless of how it was loaded.
npm (ES Module)
import { OozooPayClient } from '@team-oozoo/oozoo-pay';
const client = new OozooPayClient('pk_xxxxxxxxxxxxxxxx');
CDN (Standalone global)
<script src="https://cdn.oozoopay.com/latest/standalone.global.js"></script>
<script>
// Either await OozooPay.load(...) or new OozooPay.OozooPayClient(...)
const client = await OozooPay.load('pk_xxxxxxxxxxxxxxxx');
</script>
Both patterns produce the same client instance.
| Argument | Type | Required | Description |
|---|---|---|---|
clientKey | string | ✓ | Client Key (pk_xxx) |
Request a Payment
Call pay() to open the checkout overlay. When the user clicks "Pay", the SDK invokes your onCreateInvoice callback — your merchant server must create an invoice via the HMAC API and return the resulting invoiceId.
try {
await client.pay({
price: 100,
unit: 'usd',
successUrl: 'https://your-shop.com/payment/success',
failUrl: 'https://your-shop.com/payment/fail',
onCreateInvoice: async ({ price, unit, chainId, tokenAddress, sender }) => {
const res = await fetch('/api/create-invoice', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ price, unit, chainId, tokenAddress, sender }),
});
const { invoiceId } = await res.json();
return invoiceId;
},
});
} catch (err) {
// err.code === 'UNSUPPORTED_TOKEN' etc.
console.error(err);
}
pay() options
| Option | Type | Required | Description |
|---|---|---|---|
price | number | ✓ | Payment price |
unit | 'usd' | Currency unit (default: 'usd') | |
successUrl | string | ✓ | Redirect URL on payment success |
failUrl | string | Redirect URL on cancel/failure (omit to just close the modal) | |
onCreateInvoice | function | ✓ | Invoice creation callback (see below) |
chainId | string | Prefill — preselect a chain in the checkout (must be paired with tokenAddress) | |
tokenAddress | string | Prefill — preselect a token in the checkout (must be paired with chainId) |
onCreateInvoice callback
Invoked when the user clicks "Pay" after selecting a token + network. Your callback receives the user's selection and must return an invoiceId (UUID) created on your server.
| Parameter | Type | Description |
|---|---|---|
price | number | Payment price |
unit | 'usd' | Currency unit |
chainId | string | Selected blockchain network chain ID |
tokenAddress | string | Selected token contract address |
sender | string | Payer's wallet address |
See Server Integration for the server-side implementation.
Preselect a Token & Chain (Prefill)
Pass chainId + tokenAddress together to open the checkout with that token preselected. The user can still switch to another token before confirming.
await client.pay({
price: 100,
unit: 'usd',
successUrl: 'https://your-shop.com/payment/success',
failUrl: 'https://your-shop.com/payment/fail',
// Preselect: Ethereum Sepolia USDC
chainId: '11155111',
tokenAddress: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
onCreateInvoice: async ({ chainId, tokenAddress, sender }) => {
// ...
},
});
Behavior
| Case | Result |
|---|---|
Neither chainId nor tokenAddress provided | Default — user picks any available token in the checkout |
| Only one of them provided | Prefill is ignored; user picks as usual |
| Both provided and match a token the merchant activated | Token is preselected; user can still switch |
| Both provided but no match | pay() Promise rejects with code: 'UNSUPPORTED_TOKEN' (see below) |
Match rules:
chainIdis compared as a string, exactly.tokenAddressis compared case-insensitively.- For native coins (ETH, BNB, KAIA, etc.), use the EVM native sentinel
0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeEor the SVM native sentinelSo11111111111111111111111111111111111111111.
GET /api/tokensYour merchant server can call GET /api/tokens (HMAC-authenticated) to list the chain/token combinations enabled for this merchant. Pass that to your client to populate a dropdown — and you'll never hit UNSUPPORTED_TOKEN.
Error Handling
pay() and transfer() return a Promise<void>:
- resolves on success (after
successUrlredirect fires) and on user cancel (backdrop click / ESC / explicit close) - rejects with an
ApiErrorwhen the checkout signals an error
try {
await client.pay({ ... });
} catch (err) {
// err.name === 'ApiError'
// err.code === 'UNSUPPORTED_TOKEN' | 'PAYMENT_FAILED' | ...
// err.message — human-readable
}
Error codes
code | Cause |
|---|---|
UNSUPPORTED_TOKEN | Prefill (chainId + tokenAddress) didn't match any token enabled by this merchant. |
PAYMENT_FAILED | Blockchain transaction failed (signed but reverted or RPC error). |
TRANSFER_FAILED | Same as above, in a transfer() flow. |
If a failUrl is configured, the browser is also redirected there with ?code=...&message=... query params. The Promise still rejects, so a try/catch is the safest place to react.
Payment Flow
- Initialize the SDK with your Client Key.
- Call
pay()to open the checkout overlay (optionally prefill a token). - User confirms token & network in the UI (preselected if you prefilled).
onCreateInvoiceis invoked — your server creates an invoice via HMAC API.- Blockchain transaction — user signs and broadcasts from their wallet.
- Payment confirmed on-chain — user is redirected to
successUrl. - Webhook delivered — your server receives
invoice.confirmedand finalizes the order.
Do not finalize orders based solely on the successUrl redirect — users can navigate away or close the tab before redirect fires. Always wait for the invoice.confirmed webhook on your server.
Request a Withdrawal
transfer() opens the same overlay but in payout direction. It creates a withdrawal request — funds are not sent on-chain immediately. The request lands in the Merchant Admin Dashboard with status REQUESTED, and an admin must approve it (with 2FA) for the on-chain transfer to actually fire.
Use this for user-initiated withdrawals, off-flow refunds, or reward payouts where you want a human-in-the-loop approval step.
await client.transfer({
price: 100,
unit: 'usd',
successUrl: 'https://your-shop.com/withdrawal/success',
failUrl: 'https://your-shop.com/withdrawal/fail',
// Prefill is supported here too
chainId: '11155111',
tokenAddress: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
onCreateInvoice: async ({ price, unit, chainId, tokenAddress, receiver }) => {
const res = await fetch('/api/create-withdrawal', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ price, unit, chainId, tokenAddress, receiver }),
});
const { withdrawalId } = await res.json();
return withdrawalId;
},
});
transfer() options
Same shape as pay() — chainId/tokenAddress prefill works the same way. Only the callback parameter differs (receiver instead of sender).
onCreateInvoice callback (transfer)
| Parameter | Type | Description |
|---|---|---|
price | number | Withdrawal amount |
unit | 'usd' | Currency unit |
chainId | string | Selected blockchain network chain ID |
tokenAddress | string | Selected token contract address |
receiver | string | Recipient's wallet address (entered by the user in the UI) |
The server-side endpoint differs — it must call POST /api/invoices/withdrawals and return withdrawalId instead of invoiceId. See Server Integration → Create Withdrawal for details.