
Payout Split Management
- 73 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Manage marketplace payout splits with seller disbursements, commission calculation, tax withholding, and 1099 reporting.
About
Handles complex marketplace payout splits including seller disbursements, commission calculation, tax withholding, and 1099 reporting. A developer uses it to correctly pay and report to sellers on a platform.
- Seller disbursements and commission calculation
- Tax withholding and 1099 reporting
Payout Split Management by the numbers
- 73 all-time installs (skills.sh)
- Ranked #552 of 1,106 Finance & Trading skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill payout-split-managementAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 73 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Manage marketplace payout splits with seller disbursements, commission calculation, tax withholding, and 1099 reporting.
Files
Payout Split Management
Overview
A marketplace or multi-seller platform must split every payment between the platform (commission) and one or more sellers (net payout), withhold taxes where required, manage rolling reserves for refund protection, and disburse funds on a schedule. Stripe Connect is the industry standard for handling this — it legally routes funds to connected seller accounts, handles tax withholding and 1099 reporting, and eliminates the compliance risk of holding seller funds in a pooled bank account.
For Shopify, WooCommerce, and BigCommerce stores that operate as multi-vendor marketplaces, dedicated marketplace apps (like Dokan for WooCommerce) handle the payout split logic on top of Stripe Connect.
When to Use This Skill
- When building a marketplace with independent sellers who need earnings disbursements
- When your platform charges a percentage or flat commission on transactions
- When you need to comply with IRS 1099-K reporting requirements for sellers
- When managing rolling reserves for high-risk or new sellers
- When sellers are requesting faster payouts (daily/weekly vs. monthly)
Core Instructions
Step 1: Choose the right marketplace and payout architecture
| Platform | Recommended Approach | Notes |
|---|---|---|
| Shopify | Shopify Marketplace Kit + Stripe Connect, or Multi-Vendor Marketplace app | Shopify does not natively support multi-seller payouts; marketplace apps handle seller accounts and split payouts |
| WooCommerce | Dokan Multivendor Marketplace or WCFM Marketplace + Stripe Connect | Dokan is the most widely used WooCommerce marketplace plugin; has native Stripe Connect integration for split payouts |
| BigCommerce | Multi-Seller Marketplace app + Stripe Connect | Third-party marketplace apps on BigCommerce App Marketplace |
| Custom / Headless | Stripe Connect (Express or Custom accounts) | Build the full split logic; Stripe handles compliance, tax forms, and fund routing |
Step 2: Set up Stripe Connect for seller payouts
Stripe Connect is required for any architecture where you want to route funds to sellers automatically (rather than collecting everything in your account and manually wiring sellers).
Choose the right Connect account type:
- Express accounts: Sellers onboard via a Stripe-hosted form; Stripe handles identity verification (KYC); best for most marketplaces
- Custom accounts: You control the onboarding UX entirely; more complex; for platforms with specific UX requirements
- Standard accounts: Sellers connect an existing Stripe account; they see your platform in their Stripe dashboard
For most marketplaces, Express accounts are the right choice.
Set up Stripe Connect in the Stripe Dashboard: 1. Go to Stripe Dashboard → Connect → Overview and configure your platform 2. Enable Express accounts under Connect → Settings → Account types 3. Configure your platform's branding (name, logo, colors) for the hosted onboarding flow 4. Set the commission structure in your platform settings (not Stripe — Stripe routes funds based on the amounts you specify per transaction)
---
WooCommerce (Dokan)
1. Install Dokan Multivendor Marketplace (free base + paid extensions from dokan.wedevs.com) 2. Go to Dokan → Settings → Selling Options and configure:
- Commission type: percentage (e.g., 15% of each sale goes to the platform), flat fee, or combined
- Commission rate: the percentage or flat amount you keep as the marketplace
3. Go to Dokan → Settings → Withdrawal and configure payout schedules (weekly, bi-weekly, monthly) and minimum withdrawal amount 4. Enable Stripe Connect: go to Dokan → Settings → Payment → Stripe Connect and enter your Stripe Connect platform keys 5. Sellers connect their Stripe accounts by going to their vendor dashboard and clicking Payment Settings → Connect with Stripe 6. When an order is placed, Dokan automatically splits the payment: the seller's share is transferred to their connected Stripe account, the commission stays in your platform's Stripe balance
1099 reporting with Dokan: Dokan tracks cumulative payouts per seller. Export vendor earnings reports from Dokan → Reports → Vendor Wise Sales for 1099 preparation. For automatic 1099 generation, use TaxBandits or Track1099 — import the Dokan export and generate IRS-compliant 1099-K forms.
Shopify (Multi-Vendor Marketplace app)
1. Install Multi-Vendor Marketplace or Marketplace Kit from the Shopify App Store (various providers) 2. Configure vendor commission rates in the app's vendor settings 3. Connect Stripe Connect for payouts in the app's payment settings 4. Vendors are onboarded via a hosted form and can view their earnings and request payouts from a vendor portal
BigCommerce
1. Install a multi-seller marketplace app from the BigCommerce App Marketplace 2. Configure commission rates and payout schedules in the app settings 3. Connect Stripe Connect for automatic fund routing
---
Custom / Headless
For custom marketplace builds, implement the full split payout architecture using Stripe Connect:
Seller onboarding (Express accounts):
// Create an Express Connect account for a new seller
const account = await stripe.accounts.create({
type: 'express',
country: 'US',
email: sellerEmail,
capabilities: { transfers: { requested: true } },
metadata: { seller_id: sellerId },
});
// Generate onboarding link — redirect seller to complete Stripe's KYC form
const accountLink = await stripe.accountLinks.create({
account: account.id,
refresh_url: `${process.env.PLATFORM_URL}/sellers/onboarding/retry`,
return_url: `${process.env.PLATFORM_URL}/sellers/onboarding/complete`,
type: 'account_onboarding',
});
// Save account.id to your database and redirect seller to accountLink.url
await db.sellers.update({ where: { id: sellerId }, data: { stripeAccountId: account.id } });Splitting payment at checkout (Destination charge pattern):
// Create a payment intent that automatically routes commission to your platform
// and the seller's share to their connected account
const paymentIntent = await stripe.paymentIntents.create({
amount: orderTotalCents,
currency: 'usd',
payment_method_types: ['card'],
application_fee_amount: Math.round(orderTotalCents * commissionRate), // Platform commission
transfer_data: {
destination: seller.stripeAccountId, // Seller's Connect account
},
metadata: { order_id: orderId, seller_id: sellerId },
});Rolling reserve (withhold a % for refund protection):
// Instead of immediate transfer, create a manual transfer on a delay
const transfer = await stripe.transfers.create({
amount: Math.round(sellerNetEarningsCents * (1 - ROLLING_RESERVE_RATE)), // 95% transferred now
currency: 'usd',
destination: seller.stripeAccountId,
transfer_group: `order_${orderId}`,
metadata: { order_id: orderId, reserve_amount: Math.round(sellerNetEarningsCents * ROLLING_RESERVE_RATE) },
});
// Schedule the reserve release 90 days later via a cron job or job queue
await reserveQueue.add('release-reserve', {
sellerId: seller.id,
orderId,
reserveAmount: Math.round(sellerNetEarningsCents * ROLLING_RESERVE_RATE),
}, { delay: 90 * 24 * 60 * 60 * 1000 });1099-K tracking:
Track each seller's gross transaction volume in real-time. For 2024+, the IRS 1099-K threshold is $600.
// Update seller YTD earnings after each order
await db.sellers.update({
where: { id: sellerId },
data: { ytdGrossVolume: { increment: orderSubtotal }, ytdTransactions: { increment: 1 } },
});
// Alert when approaching $600 threshold — request W-9 before first payout
const seller = await db.sellers.findUnique({ where: { id: sellerId } });
if (seller.ytdGrossVolume >= 400 && !seller.w9Collected) {
await sendW9RequestEmail(seller);
}For 1099 form generation, use TaxBandits API or Track1099 API rather than building IRS form generation from scratch.
Step 3: Configure payout schedules and seller portal
Stripe Connect payout schedule: In the Stripe Dashboard under Connect → Settings → Payouts, configure the default payout schedule for connected accounts (daily, weekly, or monthly). Individual seller accounts can request changes to their schedule within your platform's settings.
Seller earnings dashboard: Sellers need visibility into their earnings, pending payouts, and reserves. Build or use a pre-built portal:
- Dokan/WCFM: includes a vendor dashboard with earnings, payout requests, and payment history
- Custom: use the Stripe Connect Account Balance API to show sellers their available balance in real-time
Best Practices
- Use Stripe Connect Express or Custom accounts — never hold seller funds in a pooled bank account; use Stripe Connect to ensure funds are legally owned by the platform until transferred
- Collect W-9 before first payout — without a W-9, you must apply 24% IRS backup withholding; make W-9 collection part of seller onboarding
- Calculate earnings at order capture, not payout time — earnings records should be immutable and linked to specific orders; the payout is a separate aggregation step
- Implement rolling reserves for new sellers — withhold 5–10% for 90 days to protect against refunds and chargebacks; release automatically on schedule
- Store commission rates as snapshots — commission rates change over time; never recompute historical earnings with the current rate; record the rate at the time of each sale
Common Pitfalls
| Problem | Solution |
|---|---|
| Stripe Connect transfer fails silently | Listen for the transfer.failed webhook and notify sellers and your ops team immediately |
| Rolling reserve not released after maturity | Build a daily cron job that checks release dates; test with a short reserve period in staging |
| 1099-K gross amount does not match seller records | 1099-K must report gross payment volume before platform fees; use gross order amount, not net seller earnings |
| Backup withholding not applied to sellers without W-9 | Set a flag in your database when onboarding if W-9 is not collected; apply 24% withholding to all payouts for that seller until it is received |
| Negative payout when refunds exceed sales in a period | Carry negative balances forward to the next payout period; never request clawbacks from seller bank accounts |
| Dokan commission not splitting correctly | Verify the commission rate is set at the vendor level (not just global default); check Dokan's logs under Dokan → Logs for transfer errors |
Related Skills
- @stripe-integration
- @payment-reconciliation-automation
- @accounts-receivable-automation
- @tax-compliance-automation
- @invoice-generation-automation
{
"context": "Tests whether the agent correctly implements 1099-K generation with the 2024 $600 threshold, reports gross_order_amount (not net earnings), uses upsert for idempotency, triggers W-9 requests at $400, applies 24% backup withholding for sellers without W-9, implements the daily reserve release job with a double-ledger entry, and stores only last 4 TIN digits in the app.",
"type": "weighted_checklist",
"checklist": [
{
"name": "1099-K threshold $600",
"max_score": 10,
"description": "The 1099-K eligibility threshold is set to $600 (not the old $20,000 threshold), and the code or notes attribute this to the 2024 IRS rule change"
},
{
"name": "Gross amount reported",
"max_score": 10,
"description": "The 1099-K gross_amount is populated from the gross order amount (before platform fees), NOT from the net seller earnings after deductions"
},
{
"name": "Upsert for 1099 records",
"max_score": 8,
"description": "The 1099 generation uses upsert (not plain insert) so that re-running the job updates existing draft records rather than creating duplicates"
},
{
"name": "W-9 collection trigger at $400",
"max_score": 9,
"description": "The notes or code specify that W-9 collection requests should be triggered when a seller's YTD earnings reach $400 (to collect the form before the $600 filing threshold)"
},
{
"name": "Backup withholding rate",
"max_score": 8,
"description": "Tax notes state that the IRS backup withholding rate is 24%, applied to sellers who have not submitted a W-9"
},
{
"name": "Reserve release checks today",
"max_score": 9,
"description": "The reserve release job queries for records where release_date is less than or equal to today (not strictly less than), and filters for status='held' and entry_type='reserve'"
},
{
"name": "Double-ledger reserve release",
"max_score": 9,
"description": "When releasing a reserve, the job both updates the existing 'reserve' entry to status='released' AND creates a new ledger entry with entry_type='release'"
},
{
"name": "Reserve release atomic",
"max_score": 8,
"description": "The reserve release operations (update ledger, create release entry, zero out rolling_reserve on order_earnings) are performed in a single database transaction"
},
{
"name": "SQL status column logic",
"max_score": 9,
"description": "The SQL query includes a computed status column that distinguishes sellers who: require a 1099 (>=600, W-9 collected), require W-9 collection (>=600, no W-9), or are below threshold"
},
{
"name": "TIN storage note",
"max_score": 8,
"description": "The notes or code mention that only the last 4 digits of the recipient TIN should be stored in the application database, with the full TIN kept in a secure vault"
},
{
"name": "YTD earnings used for threshold",
"max_score": 7,
"description": "The 1099 eligibility check uses the seller's ytd_earnings field (or an equivalent YTD aggregate) rather than recalculating from scratch each time"
},
{
"name": "form_type is 1099-K",
"max_score": 5,
"description": "The generated form records use form_type '1099-K' (not '1099-NEC' or other variants)"
}
]
}
Year-End Tax Reporting and Reserve Management
Problem/Feature Description
ArtisanHub is a marketplace for independent craftspeople that has been running for two years. As January approaches, their finance team has two urgent problems. First, they need to file 1099 forms for qualifying sellers — but no one on the team is sure exactly who qualifies under the rules that changed recently, whether the form should report gross sales or just what the seller was paid after fees, and how to handle sellers who never submitted their tax paperwork. Second, the rolling reserve funds held back from early sellers are maturing and need to be released automatically — the current manual process is error-prone and sellers are complaining about delayed access to their money.
The engineering team needs: (1) a JavaScript service that generates 1099 records for the correct set of sellers using the current IRS threshold, and (2) a daily job that finds and releases matured reserve funds. The finance team also wants a monitoring query they can run to quickly see which active sellers are approaching the filing threshold and whether their W-9 status creates any compliance risk.
Output Specification
Produce the following files:
tax-reporting.js— the 1099 generation servicereserve-release-job.js— the daily reserve release jobtax-compliance-notes.md— explains the threshold used, what amount is reported on the 1099, how W-9 status affects withholding, and the recommended W-9 collection trigger pointseller-1099-status.sql— a SQL query that shows active sellers with their YTD earnings and a status column indicating whether they require a 1099, still need a W-9, or are below threshold
{
"context": "Tests whether the agent correctly implements the earnings calculation formula with all deductions: commission (supporting percentage/flat/tiered types), Stripe processing fee pass-through, rolling reserve withholding, backup tax withholding, and net earnings clamping. Also verifies immutable commission rate snapshots and T+2 settlement timing.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Commission type branching",
"max_score": 10,
"description": "Code branches on commission_type, handling at least 'percentage', 'flat', and 'tiered' cases separately"
},
{
"name": "Stripe processing fee formula",
"max_score": 10,
"description": "Processing fee is calculated as 2.9% of order amount PLUS a fixed $0.30 (i.e., amount * 0.029 + 0.30)"
},
{
"name": "Rolling reserve deduction",
"max_score": 8,
"description": "A rolling reserve amount is withheld from net earnings, computed from a per-seller reserve percentage applied to the order subtotal"
},
{
"name": "Reserve release date",
"max_score": 7,
"description": "Reserve release date is set to the current date plus the seller's rolling_reserve_days (e.g. 90 days)"
},
{
"name": "Backup withholding rate",
"max_score": 10,
"description": "When backup withholding applies, the withheld amount is calculated at 24% of (subtotal minus commission), not 24% of the full order amount"
},
{
"name": "Net earnings formula",
"max_score": 10,
"description": "Net seller earnings is computed as: subtotal - commission - processing_fee - rolling_reserve - tax_withheld"
},
{
"name": "Non-negative earnings guard",
"max_score": 8,
"description": "Net earnings are clamped to a minimum of 0 (using Math.max(0, ...) or equivalent) to prevent negative values"
},
{
"name": "T+2 settlement timing",
"max_score": 7,
"description": "The available_date for funds is set to 2 days after the current date (T+2 ACH settlement)"
},
{
"name": "Commission rate snapshot",
"max_score": 10,
"description": "The commission_rate at the time of the order is stored on the earnings record (not re-read later); the notes or code explicitly states that historical rates must not be recomputed"
},
{
"name": "YTD totals updated",
"max_score": 10,
"description": "After creating the earnings record, seller ytd_earnings and ytd_transactions are incremented (using the gross order amount for ytd_earnings)"
},
{
"name": "Earnings at order time",
"max_score": 10,
"description": "The notes document states that earnings are calculated at order capture time, not at payout/disbursement time"
}
]
}
Marketplace Earnings Calculator
Problem/Feature Description
Craft & Co is a handmade goods marketplace launching next quarter. Their product team has finalized the business model: the platform takes a commission on every sale, Stripe handles card processing, and new sellers are placed on a reserve program for their first 90 days to protect against chargebacks. Some sellers — particularly those who signed up without completing their tax paperwork — will have income tax withheld directly from their earnings.
The engineering team needs a JavaScript module that calculates the exact amount a seller earns for a given order, breaking down every deduction so that earnings records are transparent and auditable. The module must handle multiple commission structures (the marketplace offers different rates to different seller tiers) and must produce a database record for every order — calculation happens at the moment the order is placed, not at payout time. Seller year-to-date totals must also be updated as part of this process, because the finance team uses them for end-of-year tax reporting.
Output Specification
Produce a file earnings-calculator.js containing the earnings calculation logic as a JavaScript module. The file should be self-contained and include inline comments explaining any non-obvious numeric constants or business rules. Also produce a calculation-notes.md that documents the calculation formula used, what each deduction represents, and any edge cases handled. The calculation-notes document should be detailed enough for a finance auditor to verify the logic.
{
"context": "Tests whether the agent correctly implements Stripe Connect disbursements including idempotency (create DB record first), stripe_account_status validation, cents conversion, transfer_group metadata, atomic DB transaction on success, failure recording, negative balance handling, and using stripe.transfers.create().",
"type": "weighted_checklist",
"checklist": [
{
"name": "Stripe Connect transfers used",
"max_score": 10,
"description": "Uses stripe.transfers.create() (or equivalent Stripe Transfers API) to route funds to sellers, not direct bank wires or Stripe Payouts API to the platform account"
},
{
"name": "Account status check",
"max_score": 9,
"description": "Code checks that stripe_account_status equals 'enabled' before attempting a transfer, and skips or throws for non-enabled accounts"
},
{
"name": "Disbursement record created first",
"max_score": 10,
"description": "A disbursement record is inserted into the database BEFORE calling the Stripe API, with status 'processing', to ensure idempotency"
},
{
"name": "Amount in cents",
"max_score": 9,
"description": "The Stripe transfer amount is converted to integer cents using Math.round(amount * 100) or equivalent before being passed to the API"
},
{
"name": "transfer_group set",
"max_score": 7,
"description": "The Stripe transfer includes a transfer_group parameter identifying the disbursement (e.g. 'payout_{disbursement_id}')"
},
{
"name": "Transfer metadata included",
"max_score": 7,
"description": "The Stripe transfer includes a metadata object with at least disbursement_id and seller_id fields"
},
{
"name": "Atomic success transaction",
"max_score": 10,
"description": "On successful transfer: earnings records are marked 'disbursed' AND the disbursement record is updated (status 'paid', stripe_transfer_id set) in a single atomic database transaction"
},
{
"name": "Failure recorded",
"max_score": 8,
"description": "On Stripe API failure, the disbursement record is updated to status 'failed' with the error message stored in failure_reason"
},
{
"name": "Negative/zero balance skip",
"max_score": 8,
"description": "If net amount is zero or negative, the disbursement is skipped without triggering a clawback or throwing an error; balance is carried forward"
},
{
"name": "Design doc: idempotency explained",
"max_score": 8,
"description": "The design document explicitly describes the idempotency strategy (create DB record first so a retry won't double-pay)"
},
{
"name": "Design doc: failure handling described",
"max_score": 7,
"description": "The design document describes how Stripe transfer failures are recorded and how the ops team is alerted (e.g., via webhook handling or monitoring)"
},
{
"name": "transfer.failed webhook mentioned",
"max_score": 7,
"description": "The design document or code comments mention handling the Stripe 'transfer.failed' webhook event to catch silent failures"
}
]
}
Seller Disbursement Processor
Problem/Feature Description
GigMarket is a freelance services marketplace that has grown to 800 active sellers. Every week their finance team manually exports a spreadsheet of sellers owed money and wires funds one by one — a process that takes two days and has caused several payment errors. The CTO wants a fully automated batch disbursement system that runs on a schedule and reliably transfers the correct net amounts to each seller's bank account.
Sellers have already completed Stripe Connect onboarding as part of signup. Not all accounts are fully activated yet — some are still restricted or pending review — so the system needs to skip ineligible sellers gracefully rather than failing the entire batch. When a transfer succeeds, all the underlying earnings records need to be marked as disbursed in the same atomic operation so the books never fall out of sync with what Stripe actually paid out. When a transfer fails, the failure must be recorded with the reason so the finance team can investigate. The system should also handle the edge case where a seller's net amount is zero or negative after deductions, and instead of triggering a clawback, carry that balance forward.
Output Specification
Produce a file disbursement-processor.js implementing the batch disbursement logic as a JavaScript module. Include a disbursement-design.md document that describes the data flow, idempotency strategy, and how the system handles failure cases — detailed enough for a code reviewer to evaluate the correctness of the approach. Clean up any test artifacts before finishing.
{
"name": "finsi/payout-split-management",
"version": "0.1.0",
"summary": "Manage complex payout splits for marketplaces and platforms with seller disbursements, commission calculation, tax withholding, and 1099 reporting",
"skills": {
"payout-split-management": {
"path": "SKILL.md"
}
}
}