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)
For a complete implementation guide with examples, see: Accept payments via SEPA Direct Debit - Web
Prerequisites
This payment method requires payments module initialisation. See: Payments module initialisation
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
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 | Yes |
PaymentsModuleSepaDirectDebitOptions interface
| Parameter | Description | Type | Required |
|---|---|---|---|
createOrder | Async function that calls your backend to create an 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 | No |
onSuccess | Callback triggered when the payment is submitted to the SEPA scheme. This does not indicate settlement - see Callback events | (payload: {orderId: string}) => void | No |
onError | Callback triggered when the payment fails. Receives RevolutCheckoutError | (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
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.
Widget callbacks are not guaranteed to fire due to network issues, browser closures, or ad-blockers. Always use webhooks for critical backend operations like order fulfilment.
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.
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
(payload: { orderId: string }) => voidTriggered 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:
onSuccess: ({ orderId }) => {
console.log('Payment submitted!', orderId)
window.location.href = `/processing?orderId=${orderId}`
}onError
(payload: { error: RevolutCheckoutError; orderId: string }) => voidTriggered when the payment fails. The error parameter is a RevolutCheckoutError 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:
onError: ({ error, orderId }) => {
console.error('Payment failed:', error.message, orderId)
alert(`Payment failed: ${error.message}`)
}onCancel
(payload: { orderId: string | undefined }) => voidTriggered 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:
onCancel: ({ orderId }) => {
console.log('Payment cancelled', orderId)
alert('Payment was cancelled. You can try again.')
}Usage examples
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()
})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:
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.