Marketplace splits
Share a charge across recipients — Asaas splits by percent or fixed amount, Woovi subaccounts keyed by Pix key — and the integer arithmetic that keeps a computed split summing to the total.
When the money does not all belong to you — a marketplace, a platform taking a cut, a booking site paying a venue — the gateway can divide the charge at settlement instead of you moving money afterwards.
Two drivers implement split on the charge: Asaas and Pagar.me. Woovi does it differently, with
subaccounts created ahead of time. Four more — Adyen, Mollie, Razorpay and Square — accept the field
in the type and throw a named error rather than approximating it, because their own split models
are not the shape split describes:
[payments] Square does not split a payment across recipients. `app_fee_money` takes a single
application fee for an OAuth-connected seller, which is not the same thing; pass it as
`metadata.appFeeAmount` if that is what you want.That is the honest failure. A split silently dropped is a marketplace that keeps the seller's
money.
Showing Asaas — pick your gateway in the sidebar to see yours.
Pass split on the charge. Each entry names a wallet and its share, by percent, by fixed amount, or
both:
const payment = await getPayments().driver('pix').charge({
customerId: buyer.billingCustomerId!,
amount: 10_000, // R$ 100,00
description: `Pedido ${order.id}`,
externalReference: order.id,
split: [
{ walletId: seller.asaasWalletId, percentualValue: 90 }, // 90% to the seller
{ walletId: partner.asaasWalletId, fixedValue: 500 }, // R$ 5,00 to the partner
],
})percentualValue is a percent (0–100). fixedValue is cents, like every other amount in this
library — the driver converts it to the decimal Asaas expects, so you never write 5.00.
Whatever is not split stays with the account that owns the API key. The split applies to the charge, so it flows through to a subscription's generated charges too.
Computing a split yourself
When the shares come from your own pricing rather than a flat percentage, do the arithmetic in integers and give the rounding remainder to one recipient:
import type { Money } from '@adonis-agora/payments'
export function splitAmount(total: Money, shares: number[]): Money[] {
const parts = shares.map((share) => Math.floor(total * share))
const remainder = total - parts.reduce((sum, part) => sum + part, 0)
parts[0] += remainder // the remainder has to land somewhere — pick deliberately
return parts
}
splitAmount(1000, [0.333, 0.333, 0.334]) // [333, 333, 334] — sums to exactly 1000
splitAmount(1000, [1 / 3, 1 / 3, 1 / 3]) // [334, 333, 333] — the remainder lands on the firstNever round each share independently
Math.round(total * share) per recipient can sum to more or less than the total, and the gateway
will reject the split — or worse, accept it and settle a cent you did not have. Floor everything, then
assign the remainder once. See Money.
Reconciling after the fact
Splits settle at the gateway, so your billing_payments row records the full charge — the split
is in payment.payload, not in the normalized fields. If you need per-recipient reporting, write your
own rows when you create the charge:
await SplitLine.createMany(
split.map((entry) => ({
orderId: order.id,
walletId: entry.walletId,
amount: entry.fixedValue ?? Math.floor(order.total * (entry.percentualValue! / 100)),
})),
)Then confirm them on payment.succeeded, the same way you confirm the order itself — see
Reacting to payments.
Metered billing
Record consumption as it happens, roll it up per meter for a period, price it against per-meter rates with an included allowance, and charge the overage exactly once.
Recovering
When a payment does not go through — dunning a failed subscription charge, refunding where the gateway supports it, and reconciling the billing tables after an outage.