A marketplace sells other people's goods. When a shopping agent buys a basket containing items from three sellers, one card payment comes in and three sellers each expect to be paid their share, minus the platform's commission and fees, at the right time. Refunds and disputes can arrive weeks after the sellers were paid. Settlement is the process that turns one captured payment into correct, auditable money movements to everyone involved.
The Agent Payments Protocol (AP2) is relevant because it changes what evidence you hold about each purchase: signed mandates that prove the user authorised this checkout and this payment. But AP2 does not move money. This article separates what the protocol gives you from what you must build, then designs the settlement side: ledger, fee allocation, holds and reserves, payouts, post-payout refunds and reconciliation, with a worked example and code. The roles and mandates themselves are covered in AP2 agent payments architecture.
What AP2 covers and what it leaves to you
The AP2 v0.2 specification defines five roles. The Shopping Agent discovers products, builds the checkout and executes the purchase. The Credential Provider supplies payment credentials and verifies agent authorisation. The Merchant provides and completes the checkout. The Merchant Payment Processor processes the payment. The Trusted Surface is a non-agentic UI that obtains the user's informed consent before a user-signed mandate is created.
It defines two mandate types. A Checkout Mandate binds to the merchant-signed checkout JWT through a hash (checkout_hash) and proves the agent was authorised to buy that assembled checkout. A Payment Mandate binds to the same checkout and proves authorisation to pay for it. Each has a receipt. Both come in an open form, which carries constraints and the agent's public key in a cnf claim and is used when the human is not present, and a closed form that pins one specific checkout.
What the specification explicitly does not cover is just as important: fund movement, settlement, marketplace scenarios and multi-merchant handling. So everything below the mandate layer in this article is your platform's design, not protocol. Field names such as seller_payable or reserve_bps are illustrative. Some older pages on this site use the earlier Intent and Cart mandate names. The v0.2 terms are used here.
The marketplace is the Merchant
In AP2's model a single Merchant signs the checkout. In a marketplace that is almost always the platform, which acts as merchant of record. It signs one checkout JWT that lists line items from several sellers, receives the payment through its processor, and owes each seller a share. Sellers are counterparties in your ledger, not AP2 parties. Two consequences follow.
First, the platform carries the payment liability. Chargebacks are filed against the platform's merchant account, whichever seller shipped the goods. Your settlement design must therefore be able to take money back from sellers.
Second, the mandate evidence belongs to the whole checkout, not to a seller. Store checkout_hash, the mandate identifiers and the receipt references on the order, and copy the order ID and hash onto every ledger entry and payable that results from it. When a dispute arrives months later about one seller's item, you can go from the chargeback to the order, to the mandates proving the user authorised that exact basket, and to the seller balance that has to absorb the loss. The split payment article covers the alternative, where the processor splits funds at capture time. The ledger reasoning below still applies, because you still need to know who owes whom after refunds.
A settlement ledger, by example
Use a double-entry ledger with integer minor units and one currency per entry. Every movement is a balanced set of debits and credits, posted once under an idempotency key. The core accounts are: processor clearing (money the processor owes you), one payable account per seller, commission revenue, processor fee expense and recovered fees, a reserve per seller, and cash at bank.
Worked example. An agent buys, in one checkout, a lamp from seller A for €80.00 and two books from seller B for €40.00, a total of €120.00. Commission is 12% for A and 15% for B. The processor charges €2.10 on the capture, and the platform passes processing fees through to sellers in proportion to their gross.
| Item | Seller A | Seller B | Platform |
|---|---|---|---|
| Gross | 8,000 | 4,000 | - |
| Commission | -960 (12%) | -600 (15%) | +1,560 |
| Processor fee share (210 split 2:1) | -140 | -70 | +210 recovered, -210 expense |
| Net payable (minor units) | 6,900 | 3,330 | 1,560 revenue |
Check the balance: 6,900 + 3,330 + 1,560 + 210 (fees recovered from sellers) = 12,000, the captured amount. The posting is: debit processor clearing 12,000; credit seller A payable 6,900; credit seller B payable 3,330; credit commission revenue 1,560; credit fees recovered 210; and separately debit fee expense 210 against processor clearing, because the processor deducts its fee before paying you. When the processor's settlement arrives, debit bank 11,790 and credit processor clearing 11,790, which brings clearing to zero. A clearing account that does not return to zero is your first reconciliation signal.
Fee splits rarely divide evenly. Use a largest-remainder allocation so shares always add up to the original amount, and choose one rounding rule (here banker's rounding for commission) and write it down:
from decimal import Decimal, ROUND_DOWN
def allocate(total_minor, weights):
"""Split an integer amount (minor units) by weights; remainders go to the largest fractions."""
total_w = sum(weights)
raw = [Decimal(total_minor) * w / total_w for w in weights]
floors = [int(r.to_integral_value(rounding=ROUND_DOWN)) for r in raw]
left = total_minor - sum(floors)
order = sorted(range(len(raw)), key=lambda i: raw[i] - floors[i], reverse=True)
for i in order[:left]:
floors[i] += 1
return floors # always sums exactly to total_minor
def settle_capture(order, capture):
lines_by_seller = group(order.lines, key="seller_id")
gross = [sum(l.amount_minor for l in ls) for ls in lines_by_seller.values()]
fee_share = allocate(capture.processor_fee_minor, gross)
entries = [Entry("processor_clearing", debit=capture.amount_minor),
Entry("processor_fees_expense", debit=capture.processor_fee_minor),
Entry("processor_clearing", credit=capture.processor_fee_minor)]
for (seller, ls), g, fee in zip(lines_by_seller.items(), gross, fee_share):
commission = sum(round_half_even(l.amount_minor * l.commission_bps, 10_000) for l in ls)
entries += [Entry(f"seller_payable:{seller}", credit=g - commission - fee),
Entry("commission_revenue", credit=commission),
Entry("fees_recovered", credit=fee)]
post(entries, idempotency_key=f"capture:{capture.id}",
refs={"order_id": order.id, "checkout_hash": order.checkout_hash})For the general ledger design see double-entry ledgers for agent payments.
When a seller's money becomes payable
Crediting a seller's payable does not mean paying it now. A payable moves through states: pending (captured, not yet eligible), available (eligible for the next payout), paid, and possibly reversed. The rules that move it are business policy. Common ones:
- Funds received. Do not pay out money the processor has not yet settled to you, unless you choose to fund the gap from your own balance sheet.
- Fulfilment. Release after shipment or delivery confirmation. Delivery-based release costs sellers cash flow but sharply reduces losses from non-delivery claims. Where release depends on an external signal, the design questions are those of escrow: who attests delivery, and what happens when the attestation never arrives.
- Return window. Some marketplaces hold the full payable through the return period for categories with high return rates.
- Rolling reserve. Hold a percentage of each payable, for example 10% for 90 days, for new or risky sellers, so there is money to absorb later refunds and chargebacks.
AP2's two modes give you one more input. A purchase made in human-not-present mode was approved through an open mandate with constraints, rather than by the user approving the exact basket. Whether that changes dispute rates for your traffic is something to measure, not assume. Record the mode on the order so your risk team can set hold and reserve rules per mode if the data justifies it.
Payouts
A payout run selects available balances above a minimum, creates one payout per seller and currency, and posts debit seller payable / credit bank-in-transit, moving it to cash when the bank confirms. General payout mechanics are covered in designing a payment system. The marketplace-specific points are:
- Idempotency. Key each payout on seller, currency and run ID, and pass the key to your payout provider, so a retried run cannot pay twice.
- Eligibility at payout time. Seller verification complete, account not frozen, bank details not just changed. A bank-detail change followed by a payout request is a classic account-takeover pattern.
- Returns and currency. A returned transfer reverses back to the seller's available balance and flags the account. Cross-currency payouts post the conversion as separate entries, so FX gains and losses are visible.
Refunds and chargebacks after payout
The difficult case is money flowing backwards after the seller has been paid. Suppose the user returns one book from seller B (€20.00) after B's payout. The refund to the buyer is 2,000. Decide in policy, before it happens, how the components unwind: B's payable is debited the item's net (2,000 minus 15% commission = 1,700), the platform returns its commission of 300 or keeps part of it, and processor fees on refunds are usually not returned by the processor, so decide who absorbs them.
Since B's payable is now zero, the debit drives it negative: B owes the platform 1,700. Recover in order: net against B's next captures, then draw on B's reserve, then invoice or debit B's account under your seller agreement, and finally write off. A negative-balance report with ageing is a core operational screen.
Chargebacks follow the same accounting with two differences. The processor debits the disputed amount plus a fee from your settlement, and you may win it back. Post the chargeback as a debit to the seller under your liability policy, and post the reversal if you win. Your evidence is the mandate chain: the Checkout Mandate tied to the signed checkout by checkout_hash, the linked Payment Mandate, the receipts, and the fulfilment record for the seller's line. Retrieve them through the order ID stored on every ledger entry.
Reconciliation
Run three comparisons every day. They are covered in more depth in AP2 payment reconciliation.
- Processor versus ledger. Match each capture, refund, chargeback and fee in the processor's settlement report to a ledger posting by processor reference. Unmatched lines on either side become exceptions.
- Bank versus processor. The deposit for each settlement batch must equal the batch's net. The clearing account should return to zero for every settled batch.
- Payouts versus bank. Every payout sent must be confirmed or returned. Anything still in transit after the expected window is an exception.
Add one marketplace-specific invariant: the sum of all seller payables, reserves and platform revenue must equal cash plus clearing balances minus anything in transit. When this total drifts, you have a posting bug, and it is far cheaper to find it in one day's data than in a quarter's.
Failure modes and trade-offs
- Double posting on retry. A capture webhook arrives twice and both copies are posted. Every posting needs a unique idempotency key, enforced by the database.
- Rounding drift. Shares computed with floating point or independent rounding do not sum to the total. Use integer minor units and largest-remainder allocation.
- Paying out unsettled funds. A processor delay turns into a cash shortfall. Tie availability to the settlement being received.
- Lost evidence link. Ledger entries without the order ID and
checkout_hashmake chargebacks unwinnable even when the mandates exist. - Seller insolvency. Negative balances can become unrecoverable. Reserves trade seller cash flow against platform losses.
- Mutable history. Editing a posted entry destroys the audit trail. Correct mistakes with reversing entries.
The central trade-off is the speed of seller payouts against the platform's exposure. Fast payouts attract sellers, while holds and reserves protect you. Make it a per-seller, per-category policy driven by measured refund and dispute rates, not a single global number.
What to do next
- Confirm with your processor and counsel whether you act as merchant of record, and write down your liability policy for refunds and chargebacks per seller.
- Store
checkout_hash, the mandate identifiers, receipts and the AP2 mode on every order, and carry the order ID onto every ledger entry. - Model the ledger in integer minor units with one payable and one reserve account per seller, and post each capture with an idempotency key.
- Implement largest-remainder allocation for fees and test it with property tests that check shares always sum to the total.
- Define payable states and release rules (funds received, fulfilment, reserve) and start with conservative holds for new sellers.
- Build the three daily reconciliations and a negative-balance ageing report before you scale payouts.