A payout is the actual money movement — the partner's wallet balance becoming a deposit in their bank account, PayPal account, or other payment method. The Affiliate module doesn't move money itself; it builds a payout request and hands it off to the Finance module, which routes through the configured payout provider.
Endpoints
/client/affiliate/me/earningsJWT/finance/payouts/requestJWT/finance/payoutsJWTEarnings
A partner reads their balance via the dashboard endpoint:
const dashboard = await fetch('/client/affiliate/me', {
headers: { orgid: 'my-org', Authorization: `Bearer ${jwt}` },
}).then(r => r.json());
// {
// programs: [
// {
// program: 'creator',
// affiliateId: '...',
// code: 'ALEX-2K9',
// stats: {
// totalReferrals, totalConversions,
// totalCommission, // lifetime earned
// pendingCommission, // held waiting on hold-window
// availableForPayout, // credited, not yet paid out
// paidCommission,
// }
// }
// ]
// }
availableForPayout is what the partner can actually withdraw. The detailed earnings list is at GET /client/affiliate/me/earnings?program=creator.
Requesting a payout
Payouts go through the Finance module — the Affiliate module's role is to verify the partner has the requested balance available, then create the payout request with the right metadata.
await fetch('/finance/payouts/request', {
method: 'POST',
headers: {
orgid: 'my-org',
Authorization: `Bearer ${jwt}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: 250,
currency: 'USD',
method: 'paypal', // 'paypal' | 'ach' | 'wire' | 'stripe-connect'
destination: { email: '[email protected]' },
source: {
type: 'affiliate',
affiliateId: 'alex-affiliate-id',
program: 'creator',
},
note: 'Q1 commission',
}),
});
The Finance module validates:
- The partner's
availableForPayoutcovers the request amount >= program.payout.minPayout- The destination payment method is verified
On success, it creates a payout record with status pending, debits the partner's balance, and queues the transfer. When the provider confirms the transfer (Stripe Connect transfer.created webhook, PayPal payout.success), the payout flips to completed and the corresponding referrals' commissionStatus flips to paid.
Auto-credit and auto-payout
Two related flags on the program:
payout.autoCredit: true— when the hold window passes, automatically movecommission_heldtoqualified(and credit the wallet). Without this, an admin must runPOST /affiliate/commissions/process-held.payout.minPayout— the minimum amount the partner can withdraw. Below this, the balance just accumulates.
There is no built-in "auto-payout" cron — partners must request a payout themselves. If you need scheduled auto-payouts, build it in the Automation module: trigger weekly, find affiliates with availableForPayout >= minPayout, call the payout request endpoint.
Payment method setup
Before a partner can request a payout they need a verified payment method on file. The flow lives under Finance:
POST /client/finance/payment-methods— add a method (PayPal, bank account, card)GET /client/finance/payment-methods— list verified methods- Method verification depends on type (micro-deposit for ACH, OAuth for PayPal, instant for Stripe Connect)
See Finance overview.
Listing payouts
// Partner — their own payouts
const mine = await fetch('/finance/payouts?source.affiliateId=' + myId, {
headers: { orgid: 'my-org', Authorization: `Bearer ${jwt}` },
}).then(r => r.json());
// Staff — all payouts
const all = await fetch('/finance/payouts?status=pending', {
headers: { orgid: 'my-org', Authorization: `Bearer ${jwt}` },
}).then(r => r.json());
Reversal handling
If a referred order is refunded after its commission was paid out, the partner's balance can go negative. The reverse logic in AffiliateTrackingService records the negative entry; it does not attempt to claw money back from the partner's external account. Negative balances are netted off the next payout request.
The customer-facing portal should call POST /finance/payouts/request with source.type: 'affiliate'. Do not expose admin payout endpoints (which can pay any source type) to partner JWTs.