Mooov Docs

merchant_not_charge_capable

A typed 422 Unprocessable Entity response returned by POST /v1/payment_intents when a tenant on your platform has not finished payment-provider onboarding. Carries a signed setup_url you can hand back to the tenant to send them straight to the Mooov screen that finishes their setup.

This page covers the response shape, the lifetime of the embedded setup link, the security properties of the link itself, and the recommended UX on your side. The broader /v1/payment_intents contract lives in the Connect protocol reference.

When you hit it

You call POST /v1/payment_intents on behalf of a tenant who has connected to your platform (a valid Mooov-Merchant header pointing at an active merchant_grants row) but the tenant has not finished PSP onboarding (no merchant_provider_accounts row with charges_enabled = true).

The most common path is:

  1. The tenant clicks Connect on your consent screen.
  2. The tenant runs through Mooov's signup wizard but skips the PSP step (it is deferrable by design — getting a treasurer in front of Stripe's KYC flow takes coordination).
  3. Your code mints a grant and tries to take a payment.
  4. Mooov returns 422 merchant_not_charge_capable with a setup_url you can use to send the tenant back to finish.

This is intentional. We minted the grant when the tenant consented so your integration doesn't get blocked on the slowest part of PSP onboarding. The trade-off is that the first charge after a deferred wizard returns this error.

Response shape

HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json

{
  "error": {
    "type": "merchant_not_charge_capable",
    "code": "merchant_not_charge_capable",
    "message": "This merchant has not completed payment provider onboarding. Send the merchant admin to setup_url to finish.",
    "merchant_id": "merch_lodge_001",
    "setup_url": "https://mooov3.mooov.money/signup/onboarding/providers?setup=eyJhbGciOi...",
    "setup_url_expires_at": "2026-05-20T13:00:00.000Z",
    "providers": [
      {"id": "prov_stripe", "display_name": "Stripe", "status": "not_connected", "source": "global"},
      {"id": "prov_mollie", "display_name": "Mollie", "status": "not_connected", "source": "global"}
    ],
    "docs_url": "https://docs.mooov.money/errors/merchant_not_charge_capable"
  }
}

Field reference:

Field Always present Notes
type Yes Stable string. Wire on this rather than the human message.
code Yes Equal to type for this error. Distinguish from infrastructure errors which set code separately from type.
message Yes Human-readable. Subject to copy iteration; do not parse.
merchant_id Yes The tenant the call was made on behalf of (echoes Mooov-Merchant).
setup_url Yes Signed, single-use link. When Mooov cannot identify the tenant's owner-role user (rare), this degrades to a non-signed variant that requires the tenant to sign in to Mooov first; see "The setup_url" below.
setup_url_expires_at Yes RFC3339 UTC with a Z suffix. Millisecond precision for signed links; second precision for the degraded variant.
providers Yes Catalog of payment providers available to the tenant, with their connection status. May be empty if the catalog is unavailable.
docs_url Yes Stable link to this page.

The envelope is intentionally additive: future fields will appear inside error.* without removing existing ones, so a forward- compatible client only needs to switch on error.type.

Provider entries

Each row in error.providers describes one PSP the tenant could connect on Mooov's side:

Field Notes
id Stable identifier (prov_stripe, prov_mollie, etc.).
display_name Marketing name for the PSP. Safe to render verbatim in your UI.
status One of not_connected, in_progress, connected, unavailable. unavailable means the PSP is not offered to this tenant today.
source Why the PSP is in the list. global (operator catalog) for live catalog rows; the degraded setup_url variant ships a single static entry with source platform_allowlist. A future release will introduce further per-platform allowlist values such as blocked_by_platform_allowlist.

You can ignore the providers list entirely and still recover from this error — the setup_url is sufficient. The list exists so you can render a richer "Mooov needs " hint in your dashboard without an extra round-trip.

The setup_url

setup_url is a single-use signed link to Mooov's onboarding wizard. Clicking it lets the tenant's owner-role user complete PSP setup without signing into Mooov first. Internally the URL embeds a JWT-style token, signed with a secret only Mooov holds, that:

The redemption endpoint is POST /v1/public/onboarding/exchange-setup-token. Your integration does NOT need to call this directly: Mooov's merchant SPA detects the ?setup= query parameter on /signup/onboarding/providers and exchanges it on the tenant's behalf. The tenant lands on Step 2 of the wizard with the PSP catalog already visible.

The degraded variant

In the rare case where Mooov cannot identify the tenant's owner-role user, setup_url carries ?merchant_id= instead of ?setup=. The link points at the same wizard step but is not signed and not single-use: a tenant user who is already signed in to Mooov lands directly on the PSP step, anyone else sees the Mooov login screen first. Treat both variants the same way on your side; the only contract difference is that the security properties above apply to the signed variant only.

Do not cache or share

Recommended UX on your side

The end-user journey we have in mind:

  1. Your tenant takes a guest's payment. Your API call to Mooov fails with this error.
  2. You surface a clear message to the user, ideally on the same screen they triggered the charge from: "Payments are not set up yet. Finish setup →
  3. The button opens setup_url in a new tab (or top-level navigation — both work). The lodge admin completes Stripe onboarding on Mooov.
  4. When the lodge admin returns to your dashboard, retry the charge. Successful charges no longer return this error.

If the click happens past setup_url_expires_at, the SPA shows a friendly "This setup link is no longer valid" panel. The tenant can ask you to generate a new link by attempting another charge that hits the same error path. Each new failed charge mints a fresh link.

Notes on related errors

Error When Setup link in body?
merchant_not_charge_capable (this) Tenant exists, grant exists, PSP onboarding incomplete. Yes (when an owner is known).
MERCHANT_NOT_CONNECTED (legacy) Same condition, but the request carried no resolvable merchant ID. Returned with the older simple body for backward compatibility. No.
GRANT_NOT_FOUND No active merchant_grants row. Tenant probably revoked you in their Mooov dashboard. N/A — you need a new grant first.
CONNECTED_ACCOUNT_LOOKUP_ERROR (500) Transient infrastructure error reading PSP rows. Safe to retry; not the merchant's onboarding state. N/A — this is not the same error.

merchant_not_charge_capable is the only one of these that indicates "the tenant can finish their own setup right now". Treat the others as platform-side / operational issues.