Checkout Page
The SDK ships with a self-contained HTML payment page generated by generatePaymentPageHTML. It’s what the Express middleware serves at GET /aiot-pay/pay/:sessionId, and you can render it yourself if you’re building on a different framework.
What the page does
The rendered HTML has no external dependencies and performs the entire browser-side payment flow:
- Detects an injected wallet (
window.ethereum) and asks the user to connect it. - Switches the wallet to BSC Mainnet (
0x38) or BSC Testnet (0x61) based onsession.chainId. - Reads on-chain state directly via the wallet’s Ethereum provider — ERC-20 allowance to Permit2 and Permit2 nonce bitmap — to derive signing parameters locally. No server round-trip needed.
- If approval is needed, prompts a gasless EIP-7702 authorization or falls back to an on-chain ERC-20
approve()transaction. - Prompts the user to sign an EIP-712
PermitWitnessTransferFromtyped-data message. - Posts
{ sessionId, payment, eip7702Auth? }to/aiot-pay/completeand shows a countdown timer + status badge while the facilitator settles on-chain. - Redirects to
callbackUrl(if configured) on success, or shows the transaction hash and a BscScan link.
All of that happens in a single <script> tag inside the generated HTML — no SDK, no bundler, no runtime dependency beyond the user’s injected wallet.
Using it directly
import { generatePaymentPageHTML } from '@aiot/shopify-x402/checkout/payment-page'
app.get('/pay/:sessionId', async (req, res) => {
const session = await client.getCheckoutStatus(req.params.sessionId)
if (!session) return res.status(404).send('Session not found')
const html = generatePaymentPageHTML(
session,
{
title: 'Pay with Crypto',
callbackUrl: 'https://mystore.example/thanks',
testnet: session.chainId === 97
}
)
res.type('html').send(html)
})PaymentPageOptions
| Name | Type | Description |
|---|---|---|
title | string | Page title and heading.Default: 'Pay with Crypto' |
callbackUrl | string | Where to redirect after a successful payment. If omitted, the page just shows a success state. |
testnet | boolean | Force the "BSC Testnet" badge. Defaults to true for chainId 97, false otherwise. |
Security: HTML escaping
Every dynamic value injected into the HTML (title, session amounts, cart items, chain name, transaction hash) is routed through an internal escapeHtml helper before interpolation. This makes the page safe to host even when the title or cart item.title strings contain user-supplied data — there is no template engine and no direct string concatenation.
The page trusts the session data it’s handed. If you’re building a custom
route that reads the session from an untrusted store, validate
session.cartItems and session.fiatTotal before passing them to
generatePaymentPageHTML.
When to customize
The page is intentionally opinionated. For V1 you should only need to override the title and callbackUrl. If you need a completely different look — a white-label brand, localized copy, a multi-step modal — fork the template function and render your own HTML; all the state you need is on the CheckoutSession object.
Related
- Express middleware — auto-serves this page
- SDK client —
getCheckoutStatus+completePayment - API Reference → generatePaymentPageHTML