reference
Unified Payment Intents
Unified Payment Intents
Overview
Daxchain uses a Unified Intent & Transaction Model to handle all forms of cryptocurrency reception (Invoices, API Checkouts, and Pay-To links). This architecture separates the "Intent" (what we expect to happen) from the "Transaction" (what actually happened on-chain).
The payment_intents Table
The payment_intents table is the single source of truth for all checkout-style requests. It replaces the legacy invoices, api_orders, and payment_orders tables.
Key Fields
kind: Identifies the source domain —invoice,order, orpay_to. (The enum value isorder, notapi_order.)external_id: The user-visible reference (e.g.,INV-2024-001).deposit_address: The unique on-chain address (Fireblocks sub-vault) allocated for this specific intent.fireblocks_vault_id: The allocated sub-vault id (0039_payment_intents_fireblocks_vault_id).crypto_amount_minor: The gross amount the payer is expected to send. Nullable forpay_to— amountless pay-to intents are supported (0041_payment_intents_pay_to_amountless).status: The unified state of the intent.
Status State Machine
draft: (Invoices only) Prepared but not yet "opened" or visible to the payer.pending: Awaiting payment. The deposit address is active.detected: A transaction has been seen on-chain but hasn't reached the required confirmation threshold.confirmed: Exact payment received (within 5 BPS tolerance). Funds credited to merchant.underpaid: Payment received, but less than expected. Actual amount credited; merchant notified for manual reconciliation.overpaid: Payment received, more than expected. Actual amount credited; merchant notified.due: Invoice past its due date without full settlement.expired: Intent TTL reached before payment was confirmed.void: Manually cancelled by the merchant or admin.
The full payment_intent_status enum is: draft, pending, detected, confirmed, underpaid, overpaid, due, expired, void.
Reconciliation & Monitoring
Daxchain runs a generic Deposit Indexer that operates across all intents.
- Inbound Event: A provider callback lands on
/api/webhooks/fireblocks, is persisted to the durableprovider_webhook_eventsinbox, and is processed off the request path by theprovider.webhook.processpg-boss queue, which inserts intoobserved_deposits. Local dev enqueues the same shape viadev-only.provider.webhook.dispatch, so emulation exercises the production ingestion path. - Lookup: The system finds the
payment_intentsrow wheredeposit_address == observed.to_address. - Amount Match:
- Fixed-amount (
invoice,order): Comparesobserved.amountagainstintent.crypto_amount_minor. - Open-ended (
pay_to): Always transitions toconfirmed.
- Fixed-amount (
- Accounting:
- A row is written to
transactions(type:deposit), linked viaintent_id. ledger_entriesare written to credit the merchant's balance.
- A row is written to
- Webhooks: A
webhook_eventis enqueued, linked viaintent_id, alerting the merchant's system.
Generic Metadata
Domain-specific data is stored in the metadata JSONB column:
- Invoice:
clientEmail,fromSnapshot,toSnapshot. - API Order:
callbackUrl,returnUrl. - Pay-To:
payerEmail,payerName.
This allows the core logic (searching, pagination, status tracking) to remain generic while supporting diverse feature sets.