_Docs/
Get StartedModulesPlatformDeployCookbookChangelogReference
_Cookbook
  • Introduction
    • RideShare Tutorial
    • Omnibus Account Management
    • Payment Card Acceptance Processing
    • Card Issuing & Financial Host
    • Stablecoin On-Ramp & Off-Ramp Operations
  1. Examples
  2. Advanced Recipes
  3. Payment Card Acceptance Processing
Cookbook

Payment Card Acceptance Processing

This example shows how to implement card payment acceptance in Formance using a declarative ledger schema. Payment acceptance covers the full lifecycle of card transactions — from the moment a customer authorizes a payment, through acquirer settlement, to handling refunds and chargebacks. The schema models authorizations as assets (binding promises to pay) so that funds are available to end-users immediately, before settlement completes.

This is an illustrative example. Adapt the schema to your specific acquirer relationships, fee structures, and regulatory requirements.

The Complete Schema#

This is the full ledger schema for a card payment acceptance system. The sections below explain each part.

Ledger Schema4 transactions5 queries
├─ acquirers
│ └─ $acquirer_id^[a-zA-Z0-9_-]+$
│ └─ mainaccounttype=acquirer
├─ banks
│ └─ $bank_id^[a-zA-Z0-9_:-]+$
│ └─ mainaccounttype=nostro
├─ clients
│ └─ $client_id^[a-zA-Z0-9_-]+$
│ └─ mainaccounttype=wallet
└─ platform
└─ $platform_name
├─ feesaccount
├─ revenueaccount
└─ chargeback_feesaccount

Chart of Accounts#

The chart section defines four account groups:

Acquirers#

└─ acquirers
└─ $acquirer_id^[a-zA-Z0-9_-]+$
└─ mainaccounttype=acquirer

Normal debit accounts representing your relationship with payment acquirers (Stripe, Adyen, etc.). The balance on an acquirer account reflects unsettled authorizations — money promised but not yet received. Acquirer accounts go negative when authorizations are recorded before settlement, which is expected behavior.

Banks (Nostro Accounts)#

└─ banks
└─ $bank_id^[a-zA-Z0-9_:-]+$
└─ mainaccounttype=nostro

Normal debit accounts representing your assets held at partner banks. These are credited when the acquirer settles funds to your bank account.

Clients#

└─ clients
└─ $client_id^[a-zA-Z0-9_-]+$
└─ mainaccounttype=wallet

Normal credit accounts representing your liabilities to clients. When a card authorization tops up a wallet, the client balance increases immediately.

Platform#

└─ platform
└─ $platform_name
├─ feesaccount
├─ revenueaccount
└─ chargeback_feesaccount

Normal credit accounts used as operational accounts that track fees, revenue, and chargeback costs. These make the economics of each transaction explicit and auditable.

Balances & Settlement Timing#

Card payment acceptance creates a fundamental tension between real-time user experience and asynchronous settlement. Clients see funds credited to their wallets immediately upon authorization, but the acquirer does not actually settle those funds to your bank account until T+1 to T+3. The acquirer's negative balance represents exactly this time gap — the sum of all authorized-but-unsettled transactions.

Transaction Patterns#

Card Authorization (Gross Top-Up)#

CARD_AUTHORIZATION_GROSS_TOPUP records a card authorization and immediately credits the client's wallet with the full gross amount. The acquirer account goes negative — this is intentional.

CARD_AUTHORIZATION_GROSS_TOPUP
vars {
    asset $asset
    number $amount
    account $acquirer_id
    account $client_id
    account $platform_name
    string $authorization_id
}

send [$asset $amount] (
    source = @acquirers:$acquirer_id:main allowing unbounded overdraft
    destination = @clients:$client_id:main
)

set_tx_meta("authorization_id", $authorization_id)
set_tx_meta("type", "card_authorization_gross_topup")

Authorization as an asset: A card authorization is a binding promise from the issuing bank to pay. This schema treats that promise as an asset, allowing you to credit the client immediately rather than waiting days for settlement. The acquirer's negative balance represents unsettled promises.

Acquirer Settlement#

ACQUIRER_SETTLEMENT records the settlement from the acquirer to your bank account. Acquirers settle the net amount (gross minus fees), so this transaction has two movements:

  1. The net amount flows from the bank to the acquirer account (reducing the acquirer's negative balance)
  2. The fee amount flows from the platform fee account to the acquirer (zeroing out the remainder)

After settlement, the acquirer balance for those transactions should return to zero.

ACQUIRER_SETTLEMENT
vars {
    asset $asset
    number $net_amount
    number $fee_amount
    account $acquirer_id
    account $bank_id
    account $platform_name
    string $settlement_ref
}

send [$asset $net_amount] (
    source = @banks:$bank_id:main allowing unbounded overdraft
    destination = @acquirers:$acquirer_id:main
)

send [$asset $fee_amount] (
    source = @platform:$platform_name:fees allowing unbounded overdraft
    destination = @acquirers:$acquirer_id:main
)

set_tx_meta("settlement_ref", $settlement_ref)
set_tx_meta("type", "acquirer_settlement")

Card Refund#

CARD_REFUND reverses a previous authorization by debiting the client wallet and crediting the acquirer account. The acquirer then processes the refund back to the cardholder's issuing bank.

CARD_REFUND
vars {
    asset $asset
    number $amount
    account $acquirer_id
    account $client_id
    string $refund_id
    string $original_authorization_id
}

send [$asset $amount] (
    source = @clients:$client_id:main
    destination = @acquirers:$acquirer_id:main
)

set_tx_meta("refund_id", $refund_id)
set_tx_meta("original_authorization_id", $original_authorization_id)
set_tx_meta("type", "card_refund")

Chargeback#

CHARGEBACK handles a dispute initiated by the cardholder through their issuing bank. It debits the client wallet for the disputed amount and separately records the chargeback fee charged by the acquirer.

CHARGEBACK
vars {
    asset $asset
    number $amount
    number $chargeback_fee
    account $acquirer_id
    account $client_id
    account $platform_name
    string $chargeback_id
    string $original_authorization_id
}

send [$asset $amount] (
    source = @clients:$client_id:main allowing unbounded overdraft
    destination = @acquirers:$acquirer_id:main
)

send [$asset $chargeback_fee] (
    source = @platform:$platform_name:chargeback_fees allowing unbounded overdraft
    destination = @acquirers:$acquirer_id:main
)

set_tx_meta("chargeback_id", $chargeback_id)
set_tx_meta("original_authorization_id", $original_authorization_id)
set_tx_meta("type", "chargeback")

Chargebacks use allowing unbounded overdraft on the client account because a chargeback can occur even if the client has already spent the funds. The resulting negative balance represents a debt owed by the client to your platform.

Why Model Authorizations as Assets?#

In traditional accounting, you would wait for settlement before recording funds. This schema takes a different approach:

  • Immediate availability — clients can use funds as soon as the authorization succeeds, which is critical for wallet top-ups and marketplace payouts
  • Explicit fee tracking — settlement records the net amount and fees separately, giving you a clear picture of acquirer costs
  • Clean reconciliation — each acquirer account should trend toward zero after settlement; a persistent balance signals missing settlements

Key Differences from Omnibus Accounts#

If you have worked through the omnibus wallet example, the payment acceptance model will feel similar but differs in key ways:

  • Source of funds: In an omnibus model, funds are already at the bank (deposits received); in payment acceptance, funds are promised by the acquirer but not yet received.
  • Negative balances: Omnibus accounts use suspense accounts for unidentified deposits; payment acceptance expects acquirer accounts to go negative (overdraft) as a normal part of the authorization-before-settlement flow.
  • Settlement direction: Omnibus payouts are typically two-step (reserve then settle); payment acceptance settlement is acquirer-driven — the acquirer pushes net funds to your bank on its own schedule.
  • Fee tracking: Payment acceptance makes fees explicit at every stage — acquirer processing fees on settlement and chargeback fees on disputes — whereas omnibus models typically handle fees separately.

Queries#

The queries section defines reusable lookups:

  • CLIENT_BALANCE — get a specific client's wallet balance
  • ACQUIRER_BALANCE — get a specific acquirer's unsettled balance (useful for reconciliation)
  • UNSETTLED_AUTHORIZATIONS — all acquirer accounts with outstanding balances (risk monitoring)
  • PLATFORM_FEES — fee volumes across all platforms
  • PLATFORM_CHARGEBACK_FEES — chargeback fee volumes (dispute cost tracking)

These leverage the hierarchical account structure — filtering on acquirers::main matches all acquirer main accounts, and platform::fees matches all platform fee accounts.

Omnibus Account ManagementCard Issuing & Financial Host
On This Page
  • The Complete Schema
  • Chart of Accounts
  • Acquirers
  • Banks (Nostro Accounts)
  • Clients
  • Platform
  • Balances & Settlement Timing
  • Transaction Patterns
  • Card Authorization (Gross Top-Up)
  • Acquirer Settlement
  • Card Refund
  • Chargeback
  • Why Model Authorizations as Assets?
  • Key Differences from Omnibus Accounts
  • Queries