packages/core/src/workflows/payout. Import them, and their steps, from @mercurjs/core/workflows. The events they emit are listed in Events. Core doesn’t schedule any of them; see The payout pipeline.
createPayoutAccountWorkflow
Creates a payout account for a seller through the provider, links it to the seller and publishes the onboarding link. Workflow idcreate-payout-account-workflow.
Steps, in order:
use-query-graph-step(payout-account-seller-query): loads the seller’s profile and current accountvalidate-seller-without-payout-account: fails with409when the seller already has an accountcreate-payout-account: calls the service’screatePayoutAccountwith the seller’s profile; on rollback, deletes the account withdeletePayoutAccount, at the provider too when it supportsdeleteAccountcreate-remote-links: links the seller and the accountcreate-onboarding: publishes the onboarding link
PayoutAccountDTO. Exposes no hooks.
createOrRefreshSellerPayoutAccountWorkflow
Makes sure one seller has an account and, until they submit their details, a current onboarding link. Workflow idcreate-or-refresh-seller-payout-account. Safe to run on every seller change.
It loads the seller, runs
createPayoutAccountWorkflow when no account exists, and otherwise publishes a fresh onboarding link while the account still needs onboarding, because provider links expire. Returns { account_id, onboarding_published }.
linkPayoutAccountWorkflow
Attaches an account that already exists at the provider to a seller, behindPOST /admin/payout-accounts. Workflow id link-payout-account.
It validates that the seller has no account, stores the account without calling the provider (
create-payout-accounts, deleted on rollback), links it, then runs syncPayoutAccountWorkflow. Returns the synced PayoutAccountDTO.
createOnboardingWorkflow
Publishes the next link for a payout account. Workflow idcreate-onboarding-workflow, exported as createOnboardingWorkflowId.
It runs one step,
create-onboarding, and returns the OnboardingDTO. The service picks an onboarding link or, once details are submitted, a dashboard login link.
refreshPayoutAccountLinkWorkflow
Publishes a new link for an account, behindPOST /admin/payout-accounts/:id/refresh-link. Workflow id refresh-payout-account-link. Input { id }; it runs create-onboarding and returns the OnboardingDTO.
refreshOnboardingWorkflow
Exchanges an expired onboarding link for a fresh one, behind the publicGET /payout/onboarding/refresh?token=. Workflow id refresh-onboarding. Input { token }; it finds the account by onboarding_token and fails (validate-onboarding-token) with 404 when none matches, or with 400 when the token is older than the module’s onboarding.tokenTtlSeconds (24 hours by default). It then runs create-onboarding with rotate_token, so the used token stops working and the new link’s refresh URL carries a new one, and returns { url }.
syncPayoutAccountWorkflow
Re-reads an account from the provider. Workflow idsync-payout-account.
Steps, in order:
sync-payout-account-step: calls the service’ssyncPayoutAccount; restores the previous fields on rollbackcreate-onboarding: only whendetails_submittedistrue, to publish the dashboard login link- Emits
payout_account.updatedwith{ id }
PayoutAccountDTO.
createOrderPayoutsWorkflow
Pays each order’s seller their share. Workflow idcreate-order-payouts, exported as createOrderPayoutsWorkflowId.
use-query-graph-step(payout-orders-query,existing-payouts-query,payout-commission-lines-query): loads the orders with their fulfillments and payments, their existing payouts, and their commission linescreate-payouts: one new payout per order without one; deleted on rollback unless it was already sentupdate-payouts: re-decides payouts that areon_holdorfailed; restored on rollback unless it was already sentcreate-remote-links: links new payouts to the seller and the orderprocess-payouts: sends eachpendingpayout with the service’sprocessPayout- Emits
payout.failedwith{ id }for every payout the provider refused
{ counts, payouts }: the number of payouts per resulting status, such as { paid: 2, on_hold: 1 }, and the payouts sent in this run. Payouts that are already paid, processing, or aborted are skipped.
createPayoutReversalsWorkflow
Takes refunded shares back. Workflow idcreate-payout-reversals.
It loads the orders’ refunds, payouts and commission lines, then creates or re-decides one reversal per refund on an order whose payout exists, net of commission, skipping refunds already deducted from the payout (
create-payout-reversals-step, update-payout-reversals). It sends each pending reversal with process-payout-reversals and emits payout.reversal_failed for refusals. Returns { counts, reversals }.
processPayoutWorkflow
Sends existing payouts again. Workflow idprocess-payout.
It runs
process-payouts and emits payout.failed for refusals. Payouts that aren’t pending or failed are returned unchanged.
retryPayoutWorkflow
Retries one payout, behindPOST /admin/payouts/:id/retry. Workflow id retry-payout. Input { id }. It fails with 400 unless the payout is failed, on_hold or pending (validate-payout-retry). A failed or on_hold order payout runs createOrderPayoutsWorkflow for its order, so it is re-decided first. A pending payout, and any payout without an order, runs processPayoutWorkflow and is sent as is.
processPayoutReversalWorkflow
Sends existing reversals again. Workflow idprocess-payout-reversal. Input { reversal_ids: string[] }; it runs process-payout-reversals and emits payout.reversal_failed for refusals.
processPayoutForWebhookWorkflow
Applies a parsed provider webhook. Workflow idprocess-payout-for-webhook, exported as processPayoutForWebhookWorkflowId. Its input is a PayoutWebhookResult: an action and an optional data with id, the provider’s object id, and for payout events payout_id, Mercur’s payout id.
The status update runs
update-payouts. Returns nothing.
Steps
You can compose these steps into your own workflows:The process steps and
create-onboarding have no compensation. Money that reached the provider, or a link a seller already received, can’t be undone by a rollback; a payout’s status, status_reason, and data.attempts record what happened instead, and the next run picks up where it stopped. For the same reason, the create and update compensations skip a payout or reversal that is processing or paid, or was attempted after the step ran.