Goal
Understand (and verify) the money flow for a marketplace order: commission transfer to the vendor’s Stripe account, then payout from Stripe to the vendor’s bank, with both stages recorded on the WooCommerce order.
Before you start
- Initial Stripe setup and Stripe Connect vendor onboarding are done — vendors are
stripe_approved. - MAM WooCommerce Product Vendors is active with Stripe selected as the payout processor.
Stage 1 — Transfer (order → vendor’s Stripe balance)
When commissions for an order are paid out, MAM WooCommerce Product Vendors applies the mam_stripe_connect_transfer filter once per vendor with that vendor’s commission amount. The plugin then:
- Validates (all deny-by-default): order and vendor exist; the amount does not exceed the order total; the vendor actually has products on the order (filter
mam_stripe_transfer_vendor_on_order); the vendor has anacct_…id and isstripe_approved. - Skips duplicates: each vendor/order/amount/currency combination is transferred at most once (the key is stored on the order), so re-running a payout never double-pays.
- Creates the Stripe Transfer in the order’s currency, grouped as
order_{id}, sourced from the order’s charge when one is recorded, with metadatawp_order_idandvendor_id. - Emails the vendor admins the “Stripe – Seller Transfer Completed” notification (replacements:
order_id,amount,paid_amount,paid_date,paid_gateway).
Order meta written by a transfer
| Meta key | Contents |
|---|---|
_mam_stripe_transfer_ids |
Flat list of all transfer ids (tr_…) for the order |
_mam_stripe_transfer_keys |
The idempotency keys already used (v{vendor}_o{order}_a{cents}_c{currency}) |
_mam_stripe_transfer_ids_by_vendor |
Transfer ids grouped by vendor id |
_mam_stripe_transfers |
One record per transfer: transfer_id, payment_id (destination payment), vendor_id, amount (smallest currency unit), currency, created_at, and — empty until stage 2 — payout_id, paid_at |
Stage 2 — Payout reconciliation (vendor’s Stripe balance → bank)
Days later, Stripe batches the vendor’s balance into a payout to their bank. The connected account emits payout.created / payout.paid webhook events, which Stripe delivers to this site’s endpoint:
POST https://your-site/wp-json/mam-stripe/v1/webhook
For each verified payout event the plugin lists the payout’s balance transactions on the connected account, finds the transfers it knows about, and fills in payout_id and paid_at on the matching _mam_stripe_transfers record — so the order now shows not just “we sent the vendor their commission” but “Stripe paid it to their bank on this date, in this payout”. Already-linked transfers are never overwritten.
You don’t set this webhook up
The endpoint and its signing secret are self-provisioned (see Recipe: Initial Stripe setup, step 4). Every delivery is signature-verified; events other than payout.created/payout.paid, or events without a connected-account context, are acknowledged and ignored. If you ever created this webhook by hand in the Stripe dashboard, delete the old endpoint once — the auto-provisioned one replaces it.
Verifying / troubleshooting
| Check | How |
|---|---|
| Transfer happened | Order meta _mam_stripe_transfer_ids has a tr_… id; the transfer is visible in the Stripe dashboard under the order’s transfer group. |
| Vendor was notified | The “Stripe – Seller Transfer Completed” notification was sent to the vendor-admin users. |
| Payout linked | The order’s _mam_stripe_transfers record for that transfer has payout_id/paid_at filled in after Stripe pays the vendor (test-mode payouts can take a simulated day). |
| Webhook healthy | Stripe dashboard → Developers → Webhooks → the auto-provisioned endpoint shows 2xx deliveries. Signature failures usually mean a stale, hand-made endpoint still exists (delete it) — the plugin re-checks and heals its own endpoint automatically. |
| Transfer refused | The debug log (stripe_connect_transfer channel) records the exact reason: amount over order total, vendor not on order, vendor missing account id, or vendor not approved. |
Related articles
- Recipe: Initial Stripe setup
- Recipe: Stripe Connect vendor onboarding
- Hook:
mam_stripe_connect_transfer - Hooks: Stripe security filters
Metadata
| Field | Value |
|---|---|
| Article type | Recipe (Admin) |
| Plugin slug | mam-stripe-manager |
| Applies to plugin version | 26.19.1+ |
| Category | Building Your App |
| Audience | WordPress admin / store manager |
| Estimated time | 10 minutes (reading; the flow itself is automatic) |
| Last verified | 2026-06-10 |
