# `sepaDirectDebit()`

Enables customers to authorise direct debits from their EUR-denominated bank accounts via a SEPA Direct Debit mandate, collected through a popup widget on your checkout page.

**Key features:**

- SEPA Direct Debit Core scheme support for personal and business accounts
- In-widget mandate collection - customers enter IBAN and consent in the widget
- Merchant-initiated transactions (MIT) enabled by default - payment methods are saved for the merchant
- Optional billing address pre-fill
- Asynchronous settlement (up to 5 business days)

:::info
For a complete implementation guide with examples, see: [Accept payments via SEPA Direct Debit - Web](/docs/guides/merchant/accept-payments/online-payments/sepa-direct-debit/web)
:::

## Prerequisites

This payment method requires payments module initialisation. See: [Payments module initialisation](/docs/sdks/merchant-web-sdk/initialisation/payments-module)

:::warning
SEPA Direct Debit is **not enabled by default**. Contact your account executive to have it enabled on your merchant account. You will also need a valid SEPA Creditor ID.
:::

## Type signature

```typescript
PaymentsInstance.sepaDirectDebit(
  options: PaymentsModuleSepaDirectDebitOptions
): SepaDirectDebitInstance

interface PaymentsModuleSepaDirectDebitOptions {
  createOrder: () => Promise<{ publicId: string }>
  billingAddress?: Address
  onSuccess?: (payload: { orderId: string }) => void
  onError?: (payload: {
    error: RevolutCheckoutError
    orderId: string
  }) => void
  onCancel?: (payload: { orderId: string | undefined }) => void
  isRecurringPayment?: boolean // defaults to true - saves payment method for merchant-initiated transactions
}

interface SepaDirectDebitInstance {
  show: () => void
  destroy: () => void
}
```

## Parameters

| Parameter | Description | Type | Required |
| --------- | ----------- | ---- | -------- |
| `options` | Configuration object for SEPA Direct Debit | [`PaymentsModuleSepaDirectDebitOptions`](#paymentsmodulesepadirectdebitoptions-interface) | Yes |

### `PaymentsModuleSepaDirectDebitOptions` interface

| Parameter | Description | Type | Required |
| --------- | ----------- | ---- | -------- |
| `createOrder` | Async function that calls your backend to [create an order](/docs/api/merchant#create-order) and returns the order token | `() => Promise<{publicId: string}>` | Yes |
| `billingAddress` | Pre-fills the customer's billing address in the widget. If provided and valid (`countryCode`, `postcode`, `streetLine1`, `city`), the billing form is hidden. If omitted or incomplete, the customer enters it manually | [`Address`](/docs/sdks/merchant-web-sdk/types/address) | No |
| `onSuccess` | Callback triggered when the payment is **submitted** to the SEPA scheme. This does not indicate settlement - see [Callback events](#callback-events) | `(payload: {orderId: string}) => void` | No |
| `onError` | Callback triggered when the payment fails. Receives [`RevolutCheckoutError`](/docs/sdks/merchant-web-sdk/types/revolut-checkout-error) | `(payload: {error: RevolutCheckoutError, orderId: string}) => void` | No |
| `onCancel` | Callback triggered when user cancels the payment. `orderId` may be `undefined` if order creation failed | `(payload: {orderId?: string}) => void` | No |
| `isRecurringPayment` | When `true` (default), the payment method is saved for the merchant, enabling retries and recurring charges via the Merchant API (`POST /api/payments`). Set to `false` for one-time payments where the method should not be retained. | `boolean` (default: `true`) | No |

## Return value

```typescript
SepaDirectDebitInstance

interface SepaDirectDebitInstance {
  show: () => void
  destroy: () => void
}
```

The method returns a `SepaDirectDebitInstance` object containing:

| Method    | Description                                    | Type         |
| --------- | ---------------------------------------------- | ------------ |
| `show`    | Open the SEPA Direct Debit popup widget        | `() => void` |
| `destroy` | Close the widget and clean up resources        | `() => void` |

## Callback events

The SEPA Direct Debit widget provides callback functions for handling payment lifecycle events.

:::warning
Widget callbacks are not guaranteed to fire due to network issues, browser closures, or ad-blockers. Always use [webhooks](/docs/guides/merchant/monitor-and-observe/webhooks/using-webhooks) for critical backend operations like order fulfilment.
:::

:::note
In all callbacks, `orderId` refers to the order's public token (`order.token` from the API response), not the internal `order.id`. This is the public identifier used in your frontend code.
:::

:::warning [SEPA Direct Debit is a slow-rail payment method]
The `onSuccess` callback fires when the payment is **submitted** to the SEPA scheme, not when it settles. Settlement can take up to **5 business days**. 

Authorisation started does not guarantee payment completion - the debtor's bank can still return the payment within the refusal window. Fulfil orders only after receiving the `ORDER_COMPLETED` webhook event.
:::

### `onSuccess`

```typescript
(payload: { orderId: string }) => void
```

Triggered when the payment is submitted to the SEPA scheme. This signals that the customer has provided their IBAN and mandate consent, and the debit instruction has been queued - **not** that the payment has settled.

**Use cases:**

- Redirect to a "payment processing" page (not a confirmation page)
- Display a message informing the customer that the debit is being processed
- Update UI to reflect the pending payment state

**Example:**

```typescript
onSuccess: ({ orderId }) => {
  console.log('Payment submitted!', orderId)
  window.location.href = `/processing?orderId=${orderId}`
}
```

### `onError`

```typescript
(payload: { error: RevolutCheckoutError; orderId: string }) => void
```

Triggered when the payment fails. The `error` parameter is a [`RevolutCheckoutError`](/docs/sdks/merchant-web-sdk/types/revolut-checkout-error) object containing error details.

**Use cases:**

- Display error message to the customer
- Log error for debugging
- Re-enable checkout form
- Offer alternative payment methods

**Example:**

```typescript
onError: ({ error, orderId }) => {
  console.error('Payment failed:', error.message, orderId)
  alert(`Payment failed: ${error.message}`)
}
```

### `onCancel`

```typescript
(payload: { orderId: string | undefined }) => void
```

Triggered when the user cancels the payment. The `orderId` may be `undefined` if order creation failed or the user closed the widget before completing the flow.

**Use cases:**

- Display cancellation message
- Re-enable checkout form
- Track abandonment analytics

**Example:**

```typescript
onCancel: ({ orderId }) => {
  console.log('Payment cancelled', orderId)
  alert('Payment was cancelled. You can try again.')
}
```

## Usage examples

- ![With async/await]

  ```typescript
  import RevolutCheckout from '@revolut/checkout'

  // Initialise payments module
  const { sepaDirectDebit } = await RevolutCheckout.payments({
    publicToken: process.env.REVOLUT_PUBLIC_KEY,
    mode: 'prod',
  })

  // Create SEPA Direct Debit instance
  const instance = sepaDirectDebit({
    createOrder: async () => {
      const order = await yourServerSideCall()
      return { publicId: order.token }
    },

    billingAddress: {
      countryCode: 'DE',
      region: 'Berlin',
      city: 'Berlin',
      streetLine1: 'Musterstraße 1',
      streetLine2: '',
      postcode: '10115',
    },

    onSuccess: ({ orderId }) => {
      console.log('Payment submitted!', orderId)
      window.location.href = `/processing?orderId=${orderId}`
    },

    onError: ({ error, orderId }) => {
      console.error('Payment failed:', error.message, orderId)
      alert(`Payment failed: ${error.message}`)
    },

    onCancel: ({ orderId }) => {
      console.log('Payment cancelled', orderId)
      alert('Payment was cancelled.')
    },
  })

  // Show the widget when user clicks a button
  document
    .getElementById('sepa-direct-debit-button')
    .addEventListener('click', () => {
      instance.show()
    })
  ```

- ![Without async/await]

  ```typescript
  import RevolutCheckout from '@revolut/checkout'

  RevolutCheckout.payments({
    publicToken: process.env.REVOLUT_PUBLIC_KEY,
    mode: 'prod',
  }).then(({ sepaDirectDebit }) => {
    const instance = sepaDirectDebit({
      createOrder: () => {
        return yourServerSideCall()
          .then((order) => ({ publicId: order.token }))
      },

      billingAddress: {
        countryCode: 'DE',
        region: 'Berlin',
        city: 'Berlin',
        streetLine1: 'Musterstraße 1',
        streetLine2: '',
        postcode: '10115',
      },

      onSuccess: ({ orderId }) => {
        console.log('Payment submitted!', orderId)
        window.location.href = `/processing?orderId=${orderId}`
      },

      onError: ({ error, orderId }) => {
        console.error('Payment failed:', error.message, orderId)
        alert(`Payment failed: ${error.message}`)
      },

      onCancel: ({ orderId }) => {
        console.log('Payment cancelled', orderId)
        alert('Payment was cancelled.')
      },
    })

    document
      .getElementById('sepa-direct-debit-button')
      .addEventListener('click', () => {
        instance.show()
      })
  })
  ```

### Merchant-initiated transactions

`isRecurringPayment` defaults to `true`, meaning the payment method is saved for the merchant after a successful payment. This enables merchant-initiated transactions (MIT) - retries and recurring charges - via `POST /api/payments` without requiring the customer to re-enter their IBAN.

The example below shows the default behaviour. The `isRecurringPayment: true` is redundant but shown for clarity:

```typescript
const instance = sepaDirectDebit({
  createOrder: async () => {
    const order = await yourServerSideCall()
    return { publicId: order.token }
  },

  isRecurringPayment: true, // default - saved for MIT

  onSuccess: ({ orderId }) => {
    console.log('Recurring payment submitted!', orderId)
    window.location.href = `/processing?orderId=${orderId}`
  },

  onError: ({ error, orderId }) => {
    alert(`Payment failed: ${error.message}`)
  },
})

document.getElementById('sepa-dd-recurring-button').addEventListener('click', () => {
  instance.show()
})
```

To disable MIT for one-time payments, set `isRecurringPayment: false`. The payment method will not be saved after the payment completes.

## See also

- [Accept payments via SEPA Direct Debit - Web](/docs/guides/merchant/accept-payments/online-payments/sepa-direct-debit/web)
- [Payments module initialisation](/docs/sdks/merchant-web-sdk/initialisation/payments-module)
- [`Address` type reference](/docs/sdks/merchant-web-sdk/types/address)
- [`RevolutCheckoutError` type reference](/docs/sdks/merchant-web-sdk/types/revolut-checkout-error)
- [Use webhooks to keep track of the payment lifecycle](/docs/guides/merchant/monitor-and-observe/webhooks/using-webhooks)