Payments (Aliquot)
Stablecoin payments for platforms — invoices, hosted checkout, splits, refunds and payouts on Polygon, BNB Chain and Base.
Last updated Oct 27, 2026
Aliquot is the crypto-payments bounded context (apps/server/src/modules/crypto-payments/). A platform (the organization) sells on behalf of its sellers, takes stablecoin payments from buyers, splits each payment to the cent, and pays sellers and partners out on chain.
What it does
- Invoices in USD, paid in USDT or USDC on Polygon, BNB Chain (BSC) or Base. Each invoice gets its own deposit address, derived from an HD key (
m/44'/60'/0'/0/i, invoices start at index 1000). - Hosted checkout at
/pay/:id: the buyer picks a network × token pair (if the invoice did not fix one), sees an EIP-681 QR code, or pays in one click with a browser wallet. The page follows the invoice live (SSE, polling fallback) until the receipt and the printable invoice. - Payment links (
/l/:slug): a fixed-price product page; every "Pay" starts a fresh invoice. - Splits: a processing fee of 0.50 % (50 bps,
CRYPTO_PLATFORM_FEE_BPS), an optional platform markup and partner shares, computed in integer base units and booked in a double-entry ledger. - Refunds for every case where money arrived that is not owed to the seller (overpayment, expired partial payment, late payment, wrong token). Refunds are pulled by the buyer through a private claim link — see Refunds.
- Payouts of settled balances to an external wallet, protected by a TOTP step-up — see Payouts.
- Webhooks (
invoice.*,payout.*,refund.*) through the boilerplate's outgoing-webhooks module, HMAC-signed.
Networks and tokens
Four pairs exist per mode. Test mode uses testnets only; live mode uses mainnets only. The mode comes from the network (or from mode on an invoice where the buyer chooses).
| Pair | Live (chainId) | Test (chainId) | Confirmations |
|---|---|---|---|
| Polygon · USDT | Polygon PoS (137) | Polygon Amoy (80002) | 64 |
| Polygon · USDC | Polygon PoS (137) | Polygon Amoy (80002) | 64 |
| BNB Chain · USDT (18 decimals) | BSC (56) | BSC Testnet (97) | 15 |
| Base · USDC | Base (8453) | Base Sepolia (84532) | 30 |
Token contracts are the canonical ones (e.g. Polygon USDT 0xc2132D05…8e8F, Base USDC 0x833589fC…2913). Amoy has no canonical USDT; it exists only on the local chains, so it is offered only when every CRYPTO_RPC_POLYGON_AMOY URL is local (loopback). A network without a CRYPTO_RPC_<NETWORK> URL is listed by GET /api/v1/crypto/networks with enabled: false and cannot be selected.
Moving parts
| Process | Role |
|---|---|
| API server | Merchant API (/api/v1/crypto/*, session or API key), hosted-checkout API (/api/v1/public/*, no auth). Derives deposit addresses from the xpub only. |
Worker (bun run worker) | The BullMQ crypto queue: scan (every 2 s per network), expire (10 s), sweep (15 s), payouts (3 s), refunds (5 s). The only process that signs. |
| Postgres | Invoices, transfers, the ledger (append-only, balanced per transaction, balance >= 0), the outbound transaction journal. |
| Redis | Job schedules, per-job/per-network locks, nonce mutexes, step-up attempt counters. |
Without CRYPTO_MASTER_MNEMONIC the server boots and every crypto endpoint answers 503 CRYPTO_NOT_CONFIGURED. The full environment list is in Environment; module internals are in Bounded contexts.
Invoice lifecycle
AWAITING_SELECTION ──buyer picks a pair──▶ PENDING
PENDING ──first transfer──▶ DETECTED ──all confirmed──▶ PAID | OVERPAID | UNDERPAID
PENDING ──expiry──▶ EXPIRED (DETECTED never expires: the buyer paid in time)
UNDERPAID ──top-up──▶ DETECTED; UNDERPAID ──expiry──▶ EXPIRED_UNDERPAID (refund held)
EXPIRED_UNDERPAID ──refund paid──▶ REFUNDED; any open invoice ──void──▶ CANCELLEDPicking a pair restarts the payment window (default 30 minutes, expires_in_minutes 1–1440), because the address is only shown from then on.
Next
- Quickstart: API key → invoice → pay → webhook.
- Confirmations: when an invoice counts as paid, and why we say "confirmed", not "final".
- Local development: local chains, demo data and the acceptance suites.