Skip to main content
PayoutModuleService reads and writes payout records, and it’s the only code that calls the payout provider. Resolve it from the container in subscribers, jobs, steps, and routes. Prefer the workflows when you also need links, events, or a rollback.

Resolve the service

The service is registered under MercurModules.PAYOUT, whose value is "payout":

Generated methods

The service extends MedusaService({ Onboarding, Payout, PayoutAccount, PayoutReversal }) and implements IPayoutModuleService from @mercurjs/types, so each model gets Medusa’s standard methods. Resolve it with that interface: container.resolve<IPayoutModuleService>(MercurModules.PAYOUT). The generated create and update methods write rows only. They never call the provider.

Provider-backed methods

These methods call the payout provider as part of the write.

createPayoutAccount

Creates the row, calls the provider’s createAccount with data.account_id and metadata.account_id set to the new id, then stores external_id, the flags, the derived status and kyc_status, and the provider’s data. Deletes the row if the provider throws. input takes optional data, context, and seller (used when onboarding.prefill is on).

syncPayoutAccount

Re-reads the account with the provider’s retrieveAccount and stores the same fields as createPayoutAccount, plus last_synced_at. Clears onboarding_token once details are submitted. The account status and kyc_status are derived here from the provider’s flags, requirements and disabled_reason.

deletePayoutAccount

Deletes the account at the provider with its optional deleteAccount, then the row. createPayoutAccountWorkflow calls it when it rolls back, so the provider isn’t left with an account no seller is linked to. A provider without deleteAccount keeps its account.

createOnboarding

Publishes the link the seller follows next and stores it on the account’s onboarding record:
  • Before the seller submits their details, it calls the provider’s createOnboarding with refresh_url and return_url from input.data or the module’s onboarding options, and stores the link with kind: "onboarding". The refresh URL carries the account’s onboarding_token. A new token is issued when the account has none, when the current one is older than onboarding.tokenTtlSeconds, or when input.rotate_token is set, and onboarding_token_issued_at records when.
  • Once details_submitted is set, it asks the provider’s createLoginLink for a dashboard link and stores it with kind: "login". A provider without createLoginLink gets a new onboarding link instead.
input takes account_id and optional data, context and rotate_token.

processPayout

Sends a pending or failed payout with the provider’s createPayout, passing the order id as group, the payment in data.source_payment, and { payout_id, order_id } as metadata. Like Medusa’s capturePayment, it locks the row and marks it processing in a transaction, then calls the provider outside it, so the lock is never held across the provider call. Stores the provider’s status and id. A provider error doesn’t throw: the row gets status_reason: "provider_error", the message in data.status_detail, and the status failed, or aborted when the error is a PayoutProviderError with code final. Any other status is returned unchanged.

processPayoutReversal

Sends a pending or failed reversal with the provider’s reversePayout, against the provider id kept in the payout’s data.provider.id. Locks and records errors the same way as processPayout.

getWebhookActionAndData

Passes { data, rawData, headers } to the provider and returns its action and the provider’s object id. It doesn’t change any record.

getOptions

Returns the resolved module options, onboarding.returnUrl, onboarding.refreshBaseUrl, onboarding.prefill, onboarding.defaultCountry, and onboarding.tokenTtlSeconds (24 hours by default), with defaults applied. Scheduling options belong to the integration that runs the payouts, such as the Stripe connector plugin.

Idempotency keys

Each provider-backed method sets context.idempotency_key before it calls the provider: The key in use is stored in the row’s data.idempotency_key and the attempt count in data.attempts. What happens to the key depends on the error the provider throws: A payout.failed webhook also clears the key, because the provider failed the payout it made under it. Stripe Connect forwards the key to Stripe on account, transfer, and reversal creation.