SEPA Direct Debit Payments with Sources

    Use Sources to accept payments using SEPA Direct Debit, a popular European banking payment method. If you need help after reading this, search our documentation or check out answers to common questions. You can even chat live with other developers in #stripe on freenode.

    Stripe users in Europe can use Sources—a single integration path for creating payments using any supported method—to accept SEPA Direct Debit payments from customers in countries within the Single Euro Payments Area.

    During the payment process, your integration collects your customer’s EUR-denominated IBAN bank account information. SEPA Direct Debits require the bank account holder to accept a mandate (debit authorization) that allows you to debit their account. A Source object is then created and your integration uses this to make a charge request and complete the payment.

    Within the scope of Sources, SEPA Direct Debit is a pull-based, reusable and asynchronous method of payment. This means that you take action to debit the amount from the customer’s account. It can take up to 14 business days to confirm the success or failure of a payment.

    Prerequisite: Collect mandate acceptance

    Before a source can be created, your customer must accept the SEPA Direct Debit mandate. Their acceptance authorizes you to collect payments for the specified amount from their bank account using SEPA Direct Debit.

    When your customer confirms the payment they are making, they are also accepting a mandate. Their acceptance authorizes you to collect payments for the specified amount from their bank account via SEPA Direct Debit. You must display the following standard authorization text (replacing Rocketship Inc with your company name) close to the payment confirmation button so that your customer can read and accept it.

    The details of the accepted mandate is generated as part of the Source object creation. A URL to view the mandate is returned as the value for sepa_debit[mandate_url]. Since this is the mandate that the customer has implicitly signed when accepting the terms suggested above, it must be communicated to them, either on the payment confirmation page or by email.

    Step 1: Create a Source object

    Bank account information is sensitive by nature. When collecting your customer’s IBAN details using a payment form, create a source, client-side, using Stripe.js. This prevents your customer’s bank account information from touching your server and reduces the amount of sensitive data that you need to handle securely.

    First, include the library within your payment page and set your publishable API key. Once you’ve included Stripe.js, use the stripe.createSource method to create a source, making sure to collecting the following information from your customer:

    Parameter Value
    type sepa_debit
    currency eur (bank accounts used for SEPA Direct Debit must always use Euros)
    sepa_debit[iban] The IBAN number for the bank account that you wish to debit.
    owner[name] The full name of the account holder.
    Stripe.createSource({
      type: 'sepa_debit',
      sepa_debit: {
        iban: 'DE89370400440532013000',
      },
      currency: 'eur',
      owner: {
        name: 'Jenny Rosen',
      },
    }).then(function(result) {
      // handle result.error or result.source
    });
    

    Server-side source creation

    The use of Stripe.js to create a SEPA Direct Debit source is optional, but highly recommended. If you forgo this step and pass the information directly to Stripe when creating a Source object, you must take appropriate steps to safeguard the sensitive bank information that passes through your servers.

    curl https://api.stripe.com/v1/sources \
       -u sk_test_BQokikJOvBiI2HlWgH4olfQ2: \
       -d type=sepa_debit \
       -d sepa_debit[iban]=DE89370400440532013000 \
       -d currency=eur \
       -d owner[name]="Jenny Rosen"
    
    # Set your secret key: remember to change this to your live secret key in production
    # See your keys here: https://dashboard.stripe.com/account/apikeys
    Stripe.api_key = "sk_test_BQokikJOvBiI2HlWgH4olfQ2"
    
    source = Stripe::Source.create({
      type: 'sepa_debit',
      sepa_debit: {iban: 'DE89370400440532013000'},
      currency: 'eur',
      owner: {
        name: 'Jenny Rosen',
      },
    })
    
    # Set your secret key: remember to change this to your live secret key in production
    # See your keys here: https://dashboard.stripe.com/account/apikeys
    stripe.api_key = "sk_test_BQokikJOvBiI2HlWgH4olfQ2"
    
    source = stripe.Source.create(
      type='sepa_debit',
      sepa_debit={'iban': 'DE89370400440532013000'},
      currency='eur',
      owner={
        'name': 'Jenny Rosen',
      },
    )
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    \Stripe\Stripe::setApiKey("sk_test_BQokikJOvBiI2HlWgH4olfQ2");
    
    $source = \Stripe\Source::create(array(
      "type" => "sepa_debit",
      "sepa_debit" => array("iban" => "DE89370400440532013000"),
      "currency" => "eur",
      "owner" => array(
        "name" => "Jenny Rosen",
      ),
    ));
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    Stripe.apiKey = "sk_test_BQokikJOvBiI2HlWgH4olfQ2";
    
    Map<String, Object> sepaParams = new HashMap<String, Object>();
    sepaParams.put("iban", "DE89370400440532013000");
    
    Map<String, Object> ownerParams = new HashMap<String, Object>();
    ownerParams.put("name", "Jenny Rosen");
    
    Map<String, Object> sourceParams = new HashMap<String, Object>();
    sourceParams.put("type", "sepa_debit");
    sourceParams.put("sepa_debit", sepaParams);
    sourceParams.put("currency", "eur");
    sourceParams.put("owner", ownerParams);
    
    Source source = Source.create(sourceParams);
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    var stripe = require("stripe")("sk_test_BQokikJOvBiI2HlWgH4olfQ2");
    
    var source = stripe.sources.create({
      type: "sepa_debit",
      sepa_debit: {iban: "DE89370400440532013000"},
      currency: "eur",
      owner: {
        name: "Jenny Rosen",
      },
    }, function(err, source) {
      // asynchronously called
    });
    

    Using either method, Stripe returns a Source object containing the relevant details for the specified method of payment.

    {
      "id": "src_18HgGjHNCLa1Vra6Y9TIP6tU",
      "object": "source",
      "amount": null,
      "client_secret": "src_client_secret_XcBmS94nTg5o0xc9MSliSlDW",
      "created": 1464803577,
      "currency": "eur",
      "flow": "none",
      "livemode": false,
      "owner": {
        "address": null,
        "email": null,
        "name": "Jenny Rosen",
        "phone": null,
        "verified_address": null,
        "verified_email": null,
        "verified_name": null,
        "verified_phone": null
      },
      "status": "chargeable",
      "type": "sepa_debit",
      "usage": "reusable",
      "sepa_debit": {
        "bank_code": "37040044",
        "country": "DE",
        "fingerprint": "NxdSyRegc9PsMkWy",
        "last4": "3001",
        "mandate_reference": "NXDSYREGC9PSMKWY",
        "mandate_url": "https://hooks.stripe.com/adapter/sepa_debit/file/src_18HgGjHNCLa1Vra6Y9TIP6tU/src_client_secret_XcBmS94nTg5o0xc9MSliSlDW"
      }
    }

    As SEPA Direct Debit payments are a pull-based payment method, there is no movement of funds during the creation of a source. Only when a successful charge request has been made is the customer’s debited and you eventually receive the funds.

    Source creation in mobile applications

    If you’re building an iOS or Android app, you can implement sources using our mobile SDKs. Refer to our sources documentation for iOS or Android to learn more.

    Error codes

    Source creation for SEPA Direct Debit payments may return any of the following errors:

    Error Description
    payment_method_not_available The payment method is currently not available. You should invite your customer to fallback to another payment method to proceed.
    processing_error An unexpected error occurred preventing us from creating the source. The source creation should be retried.
    invalid_bank_account_iban The IBAN provided appears to be invalid. Request the customer to check their information and try again.
    invalid_owner_name The owner name is invalid. It must be at least three characters in length.

    Step 2: Charge the Source

    Unlike most other payment methods, SEPA Direct Debit payments do not require any customer action after the source has been created. Once the customer has provided their IBAN details and accepted the mandate, no further action is needed and the resulting source is directly chargeable.

    Before creating a charge request to complete the payment, you should attach the Source to a Customer for later reuse.

    Attaching the Source to a Customer

    Attaching the Source to a Customer is required for you to reuse it for future payments. Please refer to our Sources & Customers guide for more details on how to attach Sources to new or existing Customers and how the two objects interact together. The following snippet attaches the Source to a new Customer:

    curl https://api.stripe.com/v1/customers \
       -u sk_test_BQokikJOvBiI2HlWgH4olfQ2: \
       -d email="paying.user@example.com" \
       -d source=src_18eYalAHEMiOZZp1l9ZTjSU0
    
    # Set your secret key: remember to change this to your live secret key in production
    # See your keys here: https://dashboard.stripe.com/account/apikeys
    Stripe.api_key = "sk_test_BQokikJOvBiI2HlWgH4olfQ2"
    
    customer = Stripe::Customer.create({
      email: 'paying.user@example.com',
      source: 'src_18eYalAHEMiOZZp1l9ZTjSU0',
    })
    
    # Set your secret key: remember to change this to your live secret key in production
    # See your keys here: https://dashboard.stripe.com/account/apikeys
    stripe.api_key = "sk_test_BQokikJOvBiI2HlWgH4olfQ2"
    
    customer = stripe.Customer.create(
      email='paying.user@example.com',
      source='src_18eYalAHEMiOZZp1l9ZTjSU0',
    )
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    \Stripe\Stripe::setApiKey("sk_test_BQokikJOvBiI2HlWgH4olfQ2");
    
    $customer = \Stripe\Customer::create(array(
      "email" => "paying.user@example.com",
      "source" => "src_18eYalAHEMiOZZp1l9ZTjSU0",
    ));
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    Stripe.apiKey = "sk_test_BQokikJOvBiI2HlWgH4olfQ2";
    
    Map<String, Object> customerParams = new HashMap<String, Object>();
    customerParams.put("email", "paying.user@example.com");
    customerParams.put("source", "src_18eYalAHEMiOZZp1l9ZTjSU0");
    
    Customer customer = Customer.create(customerParams);
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    var stripe = require("stripe")("sk_test_BQokikJOvBiI2HlWgH4olfQ2");
    
    stripe.customers.create({
      email: "paying.user@example.com",
      source: "src_18eYalAHEMiOZZp1l9ZTjSU0",
    }, function(err, customer) {
      // asynchronously called
    });
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    stripe.Key = "sk_test_BQokikJOvBiI2HlWgH4olfQ2"
    
    customerParams := &stripe.CustomerParams{
      Email: "paying.user@example.com",
    }
    customerParams.SetSource("src_18eYalAHEMiOZZp1l9ZTjSU0")
    c, err := customer.New(customerParams)
    

    Making a charge request

    Once attached, you can use the Source object’s ID along with the Customer object’s ID to perform a charge request and finalize the payment.

    curl https://api.stripe.com/v1/charges \
       -u sk_test_BQokikJOvBiI2HlWgH4olfQ2: \
       -d amount=1099 \
       -d currency=eur \
       -d customer=cus_AFGbOSiITuJVDs \
       -d source=src_18eYalAHEMiOZZp1l9ZTjSU0
    
    # Set your secret key: remember to change this to your live secret key in production
    # See your keys here: https://dashboard.stripe.com/account/apikeys
    Stripe.api_key = "sk_test_BQokikJOvBiI2HlWgH4olfQ2"
    
    charge = Stripe::Charge.create({
      amount: 1099,
      currency: 'eur',
      customer: 'cus_AFGbOSiITuJVDs',
      source: 'src_18eYalAHEMiOZZp1l9ZTjSU0',
    })
    
    # Set your secret key: remember to change this to your live secret key in production
    # See your keys here: https://dashboard.stripe.com/account/apikeys
    stripe.api_key = "sk_test_BQokikJOvBiI2HlWgH4olfQ2"
    
    charge = stripe.Charge.create(
      amount=1099,
      currency='eur',
      customer='cus_AFGbOSiITuJVDs',
      source='src_18eYalAHEMiOZZp1l9ZTjSU0',
    )
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    \Stripe\Stripe::setApiKey("sk_test_BQokikJOvBiI2HlWgH4olfQ2");
    
    $charge = \Stripe\Charge::create(array(
      "amount" => 1099,
      "currency" => "eur",
      "customer" => "cus_AFGbOSiITuJVDs",
      "source" => "src_18eYalAHEMiOZZp1l9ZTjSU0",
    ));
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    Stripe.apiKey = "sk_test_BQokikJOvBiI2HlWgH4olfQ2";
    
    Map<String, Object> chargeParams = new HashMap<String, Object>();
    chargeParams.put("amount", 1099);
    chargeParams.put("currency", "eur");
    chargeParams.put("customer", "cus_AFGbOSiITuJVDs");
    chargeParams.put("source", "src_18eYalAHEMiOZZp1l9ZTjSU0");
    
    Charge charge = Charge.create(chargeParams);
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    var stripe = require("stripe")("sk_test_BQokikJOvBiI2HlWgH4olfQ2");
    
    stripe.charges.create({
      amount: 1099,
      currency: "eur",
      customer: "cus_AFGbOSiITuJVDs",
      source: "src_18eYalAHEMiOZZp1l9ZTjSU0",
    }, function(err, charge) {
      // asynchronously called
    });
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    stripe.Key = "sk_test_BQokikJOvBiI2HlWgH4olfQ2"
    
    chargeParams := &stripe.ChargeParams{
      Amount: 1099,
      Currency: "eur",
      Customer: "cus_AFGbOSiITuJVDs",
    }
    chargeParams.SetSource("src_18eYalAHEMiOZZp1l9ZTjSU0")
    ch, err := charge.New(chargeParams)
    

    The resulting Charge object is created with a status of pending. At this stage, the funds are not yet guaranteed.

    Step 3: Confirm that the charge has succeeded

    SEPA Direct Debit payments are an asynchronous method, so funds are not immediately guaranteed. A charge created from a SEPA Direct Debit source can remain in a pending state for up to 14 business days from its creation, though the average time is around five business days. Once the charge is confirmed—and the funds guaranteed—its status is updated to succeeded.

    The following events are sent when the charge’s status is updated:

    Event Description
    charge.succeeded The charge succeeded and the payment is complete.
    charge.failed The charge has failed and the payment could not be completed.

    After confirming that the charge has succeeded, notify your customer that the payment process has been completed and their order is confirmed. Please refer to our best practices for more details on how to best integrate payment methods using webhooks.

    Failed charges

    If a charge is not confirmed, the charge’s status is automatically transitioned from pending to failed. Should a charge fail, notify your customer immediately upon receipt of the charge.failed event. When using SEPA Direct Debit, you may prefer not to fulfill orders until the charge.succeeded webhook has been received.

    Refunding a pending charge

    You can perform a refund against charges that are still pending and have not yet been confirmed. If you create a full or partial refund on a pending charge, the refund is performed once the charge’s status has transitioned to succeeded.

    In the event of the charge that transitioned to failed, full and partial refunds are marked as canceled, as the money never left the customer’s bank account.

    Testing charge success and failure

    You can mimic a successful or failed charge by first creating a test source with one of the following test IBAN account numbers. Use the resulting source in a charge request to create a test charge that is either successful or failed.

    • DE89370400440532013000: The charge status transitions from pending to succeeded
    • DE62370400440532013001: The charge status transitions from pending to failed

    When creating a test charge with this source, its status is initially set to pending before being automatically transitioned.

    Webhook events are triggered when using test sources and charges. The charge.pending event is first triggered, followed by either the charge.suceeeded or charge.failed event immediately after.

    Disputed payments

    SEPA Direct Debit provides a dispute process for bank account holders to dispute payments. As such, you should make the appropriate decisions regarding your business and how you approach SEPA Direct Debit payments.

    For a period of eight weeks after their account was debited, an account holder can dispute a payment and request a “no-questions-asked” chargeback through their bank. Any chargeback within the eight-week period are final and automatically honored. Unlike credit card disputes, there is no appeal process.

    Beyond the eight-week period after the creation of the payment, and for up to 13 months, a customer may only request a chargeback from their bank if they consider the debit had not been authorized. In this event, we automatically provide the customer’s bank with the mandate that the customer approved. This does not guarantee that the chargeback can be canceled as the customer’s bank can still decide that the debit was not authorized by the mandate—and that their customer is entitled to a refund.

    Should a chargeback occur, a dispute.created webhook event is sent, and Stripe deducts the amount of the chargeback from your Stripe balance. For more details on the SEPA Direct Debit dispute process, consult the SEPA Direct Debit rulebook.

    Related resources