Quickstart
What you are building
You will build a complete payment flow using @pepay/x402flex:
- Create a
PaymentIntent - Execute a router payment
- Verify settlement
Prerequisites
- Node.js 18+
- A funded signer for your target network
- Router and merchant addresses
Install
npm install @pepay/x402flex ethers
Initialize the SDK client
What this does: configures one client that can use API endpoints and direct contracts.
import { createClient } from '@pepay/x402flex';
const sdk = createClient({
mode: 'hybrid',
preset: 'bnbpay-testnets',
api: { baseUrl: 'https://api.bnbpay.org' },
contracts: {
defaultNetwork: 'eip155:97',
},
});
Expected result: sdk exposes intents, payments, x402, sessions, subscriptions, and relay namespaces.
Common errors and fixes:
MISSING_API_CLIENT: addapi.baseUrlwhen usingapiorhybridfeatures.UNSUPPORTED_MODE: usecontractsorhybridfor direct router calls.
Create a PaymentIntent
What this does: derives deterministic paymentId, resourceId, referenceHash, and nonce.
import { createFlexIntent } from '@pepay/x402flex';
import { ethers } from 'ethers';
const MERCHANT_ADDRESS = '0x1111111111111111111111111111111111111111';
const REFERENCE_ID = 'order-1001';
const intentBundle = createFlexIntent({
merchant: MERCHANT_ADDRESS,
token: ethers.ZeroAddress,
amount: ethers.parseEther('0.05'),
chainId: 97,
referenceId: REFERENCE_ID,
scheme: 'push:evm:direct',
});
Expected result: intentBundle contains:
intent(router payload)witness(scheme + intent hash)paymentId,resourceId,referenceId
Common errors and fixes:
- Invalid merchant/token address: ensure checksummed
0x...addresses. - Wrong chain ID: use network-specific chain ID from your configured environment.
Execute a payment
What this does: submits a router transaction with the generated intent/witness.
import { sendRouterPayment, RpcTransport } from '@pepay/x402flex';
import { ethers } from 'ethers';
const ROUTER_ADDRESS = '0xf14f56A54E0540768b7bC9877BDa7a3FB9e66E91';
const provider = new ethers.JsonRpcProvider(process.env.BNB_RPC_URL);
const signer = new ethers.Wallet(process.env.PAYER_PRIVATE_KEY!, provider);
const txResult = await sendRouterPayment({
transport: new RpcTransport(signer),
routerAddress: ROUTER_ADDRESS,
intent: intentBundle.intent,
witness: intentBundle.witness,
reference: intentBundle.referenceId,
});
console.log('txHash', txResult.txHash);
Expected result: a confirmed transaction hash and router settlement attempt.
Common errors and fixes:
reference is required: pass a non-empty reference string.- Insufficient funds: fund native gas token and payment amount.
- Wrong router: verify router address for the target network.
Verify settlement
What this does: reads indexed settlement status from bnbpay-api.
import { createApiClient } from '@pepay/x402flex';
const api = createApiClient({ baseUrl: 'https://api.bnbpay.org' });
const payment = await api.payments.get(intentBundle.paymentId);
console.log({
paymentId: payment.paymentId,
resourceId: payment.resourceId,
amount: payment.amount,
token: payment.token,
});
Expected result: payment record keyed by paymentId and resourceId.
Common errors and fixes:
- Not found immediately after send: wait for confirmations/indexer lag.
- Wrong environment: ensure chain/network matches your payment execution.
Optional: invoice-backed payment
What this does: creates an invoice first, then builds canonical payment intent via API.
const invoice = await api.invoices.create({
title: 'Order #1002',
merchantId: 'merchant-123',
amount: '25.00',
currencyToken: 'USDT',
network: 'bnbTestnet',
reference: 'order-1002',
});
const built = await api.payments.buildIntent({
mode: 'minimal',
network: 'bnbTestnet',
merchant: MERCHANT_ADDRESS,
token: '0x55d398326f99059fF775485246999027B3197955',
amount: '25.00',
scheme: 'permit2',
invoiceId: invoice.invoiceId,
});
console.log(built.derived.paymentId);
Expected result: buildIntent returns canonical intent + signing metadata aligned with API records.
Core invariants (must understand)
paymentIdis deterministic and bound to:token,amount,deadline,resourceId,referenceHash, andnonce.noncemust be persisted per payment. If you lose it, you cannot reliably recompute the samepaymentId.referenceHashmust be derived from the finalreferenceDatastring (including session tags when used).
Common errors and fixes
- Payment executes but verification fails: mismatch between reference string and
referenceHash. - Session flow reverts: session-tagged reference missing or stale
SessionSpendAuthvalues. - Relay flow rejects EIP-3009:
authNoncemust be intent-derived.