Skip to main content
The payout module handles the financial side of a marketplace — onboarding sellers to a payment provider, capturing authorized payments, and transferring funds to sellers after fulfillment.

Payout account

Every seller that wants to receive funds needs a PayoutAccount. This account links a seller to an external payment provider (e.g. Stripe Connect).

Account lifecycle

PENDING → ACTIVE

        RESTRICTED → ACTIVE (or REJECTED)
StatusDescription
PENDINGAccount created, awaiting provider onboarding
ACTIVEFully onboarded, can receive payouts
RESTRICTEDProvider has flagged the account (e.g. missing KYC)
REJECTEDProvider rejected the account

Onboarding

When a payout account is created, an Onboarding record is attached to it. This stores provider-specific data needed for identity verification or account setup (e.g. Stripe Connect onboarding links).

The payout pipeline

The payout flow is fully automated through two scheduled jobs and event-driven subscribers. No manual intervention is needed after configuration.

1. Capture check (every 15 minutes)

A scheduled job scans for orders that are ready for payment capture. For each order, it checks:
  • Payment is in authorized status
  • Seller has an ACTIVE payout account
  • Order meets the required fulfillment status (configurable, default: fulfilled)
  • No payout has been created yet
When the capture deadline approaches (authorization window minus safety buffer), the job emits an order.capture_requested event. If the authorization has already expired, it emits order.authorization_expired instead.

2. Payment capture (event-driven)

A subscriber listens for order.capture_requested events and runs the Medusa capturePaymentWorkflow to capture the authorized payment. On success, the order is marked as captured. On failure, the order is flagged so it won’t be retried.

3. Daily payouts (once per day)

A daily scheduled job (1 AM UTC) scans for captured orders that haven’t been paid out yet. For each eligible order, it emits a payout.requested event. An order qualifies for payout when:
  • Payment has been captured (metadata.captured = true)
  • No existing payout transfer
  • Seller has an ACTIVE payout account

4. Payout transfer (event-driven)

A subscriber listens for payout.requested events and runs the createPayoutWorkflow:
  1. Fetch order — Loads the order with its seller, payout account, and commission lines
  2. Calculate payout amount — Subtracts commission from the order total:
    payout_amount = order.total - total_commission
    
  3. Create payout — Calls the payment provider to initiate the transfer and creates a Payout record
  4. Link payout — Associates the payout with the seller

Payout statuses

StatusDescription
PENDINGPayout created, transfer initiated
PROCESSINGProvider is processing the transfer
PAIDFunds successfully transferred
FAILEDTransfer failed
CANCELEDTransfer was canceled
Payout statuses are updated via webhook events from the payment provider.

Webhook handling

A subscriber listens for payout.webhook_received events and delegates to the processPayoutForWebhookWorkflow. The provider parses the raw webhook payload and returns an action: Account events:
  • account.activated → Status set to ACTIVE
  • account.restricted → Status set to RESTRICTED
  • account.rejected → Status set to REJECTED
Payout events:
  • payout.processing → Status set to PROCESSING
  • payout.paid → Status set to PAID
  • payout.failed → Status set to FAILED
  • payout.canceled → Status set to CANCELED

Configuration

The payout module accepts the following options in medusa-config.ts:
OptionDefaultDescription
disabledfalseDisables both scheduled jobs
authorizationWindowMs7 daysHow long a payment authorization is valid
sellerActionWindowMs72 hoursTime for seller to fulfill the order
captureSafetyBufferMs24 hoursSafety margin before authorization expiry to trigger capture
requiredFulfillmentStatus"fulfilled"Minimum fulfillment status before capture is allowed

Data model overview

PayoutAccount
├── Onboarding (one-to-one)
└── Payout (one-to-many, per-order transfers)

Configuration example

// medusa-config.ts
{
  resolve: "./modules/payout",
  options: {
    // How long payment authorizations are valid
    authorizationWindowMs: 7 * 24 * 60 * 60 * 1000, // 7 days

    // Time for seller to fulfill before capture
    sellerActionWindowMs: 72 * 60 * 60 * 1000, // 72 hours

    // Safety margin before authorization expiry
    captureSafetyBufferMs: 24 * 60 * 60 * 1000, // 24 hours

    // Minimum fulfillment status before capture
    requiredFulfillmentStatus: "fulfilled",
  },
}

API examples

Get seller’s payout account (Vendor API)

curl http://localhost:9000/vendor/payout-accounts/<account-id> \
  -H "Authorization: Bearer <seller-token>"

Create payout onboarding (Vendor API)

Initiates provider onboarding (e.g. Stripe Connect account setup). The data and context fields are passed to the payout provider:
curl -X POST http://localhost:9000/vendor/payout-accounts/<account-id>/onboarding \
  -H "Authorization: Bearer <seller-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "context": {
      "return_url": "https://my-store.com/settings/payouts"
    }
  }'

List payouts (Admin API)

curl http://localhost:9000/admin/payouts \
  -H "Authorization: Bearer <admin-token>"

Provider abstraction

The payout module is provider-agnostic. A PayoutProviderService acts as a bridge to the external payment processor. It delegates all external operations — creating accounts, processing payouts, parsing webhooks — to a pluggable provider implementation. The module expects exactly one payout provider to be registered. Provider-specific data is stored in the data JSON fields on accounts, payouts, and onboarding records.