Skip to main content

Quickstart

What you are building

You will build a complete payment flow using @pepay/x402flex:

  1. Create a PaymentIntent
  2. Execute a router payment
  3. 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: add api.baseUrl when using api or hybrid features.
  • UNSUPPORTED_MODE: use contracts or hybrid for 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)

  • paymentId is deterministic and bound to: token, amount, deadline, resourceId, referenceHash, and nonce.
  • nonce must be persisted per payment. If you lose it, you cannot reliably recompute the same paymentId.
  • referenceHash must be derived from the final referenceData string (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 SessionSpendAuth values.
  • Relay flow rejects EIP-3009: authNonce must be intent-derived.