Multi-vendor payouts¶
multi-vendor-payouts
Split a marketplace order by vendor, record commission as ledger entries, and pay vendors only settled amounts. Use when adding vendor catalogs, commissions, refunds that cross vendors, or payout batches.
Version 0.3.2 · MIT · Source · Rule
Install this skill
Copy the skill and its rule into your project:
The skill goes to .agents/skills/ and the rule to .agents/rules/. See Command line for other directories.
Use this skill when more than one vendor sells on one order. Catalog shape is the product-catalog skill. Charging the shopper is the payment skill. This skill records who is owed what after that charge.
Do not name a vendor plugin's internal functions unless that plugin is in the project and you have read them. The ledger below is the model to implement, including when the store uses an existing multi-vendor extension.
Procedure¶
- Put
vendor_idon every sellable line. A line without a vendor cannot be paid out. - On payment, write ledger entries. Do not edit the line price to "take commission". Commission is its own entry.
- Decide, in data, who is merchant of record for the goods, the tax, and the shipping. Do not assume the vendor is.
- Hold entries until the refund window or the delivery rule you stored. A payout batch sums only entries that are payable.
- Send the payout with an idempotency key. A second run of the same batch does not pay twice.
- A refund writes reversing entries. It does not delete the original lines.
The entry types are in the ledger notes. Worked examples and checks are in evals/tasks.json.
Ledger entry¶
Each entry has: id, order_id, vendor_id or platform, type, amount in integer minor units, currency, available_at, and payout_id once paid.
Amounts are signed. A sale credit to the vendor is positive. Commission the platform keeps is negative on the vendor and positive on the platform, or two explicit entries that sum to the same result. Pick one representation and use it everywhere.
amount uses the order currency. Do not convert to a payout currency inside the entry. Conversion, if it exists, is a later payout step with its own rate and timestamp.
When money becomes payable¶
- Authorization is not a sale. Write no payable entry until the payment skill marks the order paid.
available_atis at least the refund window, or the delivery confirmation, whichever the marketplace rule uses.- Pending entries can be displayed as upcoming. They are excluded from the payout sum.
Payout batch¶
- Select entries where
available_atis in the past,payout_idis null, and the vendor account is allowed to receive funds. - Group by vendor and currency.
- Persist the batch and stamp
payout_idin the same transaction as the decision to pay. - Call the payout provider with
payout_{batchId}. If the call times out, retry with that key and do not create a second batch. - A provider failure clears nothing silently. The batch stays
processingor moves tofailedwith the entries still reserved, so a retry cannot select them into a new batch.
Refunds¶
- Refund the shopper through the payment skill for the amount they paid.
- Reverse the vendor net and the commission in proportion to the refunded lines, including tax if the vendor was credited for tax.
- If the payout already left, the reversal is a negative balance on the next batch, not a deleted history.
Gotchas¶
- One order can contain two vendors. A single commission percent on the order total assigns one vendor's shipping to the other.
- Paying the gross and also forgetting to retain commission overpays the vendor.
- Summing order totals again at payout time double-counts if the ledger is also summed.
- Blocking a vendor after entries are
processingmust not release those entries back into the selectable pool.
Verification¶
- A paid two-vendor order produces entries whose vendor nets plus platform commission equal the goods total you intended to split.
- An unpaid order produces no payable entries.
- Running the payout job twice creates one provider payout per vendor.
- A refund after payout reduces the next batch and leaves the original entries in place.
Evaluation¶
Worked examples and the checks they must satisfy are in evals/tasks.json.
References¶
Marketplace ledger¶
Entry types¶
| Type | Who is credited | When it is written |
|---|---|---|
goods |
Vendor | Order is paid, for that vendor's line totals after line discounts |
commission |
Platform | Same moment, as the platform's share of those lines |
shipping |
Vendor or platform | Whoever the marketplace rule says keeps the shipping the shopper paid |
tax |
Vendor or platform | Whoever is responsible for remitting that tax |
refund_goods |
Reverses goods |
Refund is confirmed |
refund_commission |
Reverses commission |
Refund is confirmed |
adjustment |
Either | A person records a correction with a reason |
goods plus commission for a line equals the line total the shopper paid for those goods, before the shipping and tax entries. If commission is stored as a negative vendor entry instead of a platform entry, the vendor net is goods + commission and the platform total is the absolute commission. Do not store it both ways.
Worked numbers¶
Shopper pays 100.00 for vendor goods. Commission is 10%. Vendor shipping is 0. The currency has two minor units.
- Vendor
goods:10000 - Vendor
commission:-1000if commission is a negative vendor entry - Vendor payable after the hold:
9000
Tax and shipping stay out of that 100.00 until their own entries exist. Do not subtract commission from the tax entry unless the contract says the commission base includes tax.
Payout record¶
id, vendor_id, currency, amount (sum of reserved entries), status (processing, paid, failed), idempotency_key, provider_payout_id.
paid requires the provider id. failed keeps idempotency_key so the retry is the same payout.
Merchant of record¶
Store merchant_of_record on the order or the line as platform or vendor. This is a business and legal choice the marketplace has already made. The ledger follows that flag. It does not decide it.
Evaluation¶
Fixtures from evals/tasks.json. See evaluation fixtures for how the checks work.
split-paid-order¶
Record vendor goods and platform commission for a paid marketplace order without editing the line price.
Must include: ledger, commission
Must not include: edit the line price
Worked example
Write ledger entries when the shopper payment is confirmed.
The vendor goods entry and the platform commission entry split the line total. The catalog line price stays what the shopper paid.
retry-payout¶
Run a vendor payout batch twice without sending the money twice.
Must include: payout_id, idempotency
Must not include: sum the order totals again
Worked example
Stamp payout_id on the selected entries in the same transaction as the batch.
Retry the provider with that batch idempotency key. Do not select those entries into a second batch.