Skip to main content

Invoices

Invoice lifecycle

  1. Create invoice.
  2. Build canonical payment intent.
  3. Execute payment (direct or relay).
  4. Track status and stream updates.
  5. Cancel or confirm fallback if needed.

Create invoice

What this does: creates offchain invoice metadata and pricing context.

import { createApiClient } from '@pepay/x402flex';

const api = createApiClient({ baseUrl: 'https://api.bnbpay.org' });

const invoice = await api.invoices.create({
title: 'Order #4201',
merchantId: 'merchant-123',
merchantName: 'Runey',
amount: '25.00',
currencyToken: 'USDT',
network: 'bnbTestnet',
reference: 'order-4201',
});

Expected result: invoice.invoiceId to anchor the payment workflow.

Build payment intent

What this does: returns API-aligned intent + derived IDs + signing metadata.

const built = await api.payments.buildIntent({
mode: 'minimal',
network: 'bnbTestnet',
merchant: '0x1111111111111111111111111111111111111111',
token: '0x55d398326f99059fF775485246999027B3197955',
amount: '25.00',
scheme: 'permit2',
invoiceId: invoice.invoiceId,
});

console.log({
paymentId: built.derived.paymentId,
resourceId: built.derived.resourceId,
intentHash: built.derived.intentHash,
});

Expected result: canonical paymentId/resourceId tied to invoice reference.

Execute payment

What this does: submits a relay payment from built payloads.

const witness = {
schemeId: '0xc07338ac3a63d5933e6a5b2184602e306eb7c25700866301b635132c811546f1',
intentHash: built.derived.intentHash,
payer: '0x2222222222222222222222222222222222222222',
salt: built.input.salt,
};

const permit2 = {
permit: {
permitted: { token: built.input.token, amount: built.input.amountWei },
nonce: 1,
deadline: Math.floor(Date.now() / 1000) + 3600,
},
transferDetails: { to: '0xf14f56A54E0540768b7bC9877BDa7a3FB9e66E91', requestedAmount: built.input.amountWei },
signature: '0xPERMIT2_SIGNATURE',
};

await api.relay.payment({
network: 'bnbTestnet',
scheme: 'permit2',
intent: built.derived.intent,
witness,
witnessSignature: '0xWITNESS_SIGNATURE',
permit2,
reference: built.input.baseReference,
});

Expected result: relay returns transaction hash and payment ID.

Track status and stream events

What this does: observes invoice updates via polling and SSE/WS streams.

const status = await api.invoices.status(invoice.invoiceId);
const sseUrl = api.invoices.streamSseUrl(invoice.invoiceId);
const wsUrl = api.invoices.streamWsUrl(invoice.invoiceId);

console.log(status.status, sseUrl, wsUrl);

Expected result: realtime invoice status transitions (pending -> paid etc.).

Cancel and confirm fallback

What this does: handles operational exceptions.

await api.invoices.cancel(invoice.invoiceId);

await api.invoices.confirmPayment(invoice.invoiceId, {
txHash: '0x...',
paidBy: '0x2222222222222222222222222222222222222222',
paidAmount: '25.00',
paidToken: '0x55d398326f99059fF775485246999027B3197955',
});

Expected result: invoice reaches terminal state even when automatic linkage is delayed.

Production checklist

  • Persist invoice ID, payment ID, resource ID, and reference.
  • Persist nonce for deterministic payment reproduction.
  • Recompute reference hash from final tagged reference string.
  • Add retries for temporary indexer lag.