iDEAL Payments with Sources

    Use Sources to accept payments using iDEAL, the most popular payment method in the Netherlands.

    Stripe users in Europe and the United States can accept iDEAL payments from customers in the Netherlands using Sources—a single integration path for creating payments using any supported method.

    During the payment process, a Source object is created and your customer is redirected to their bank’s website or mobile application to authorize the payment. After completing this, your integration uses the source to make a charge request and complete the payment.

    iDEAL is a push-based, single-use and synchronous method of payment. This means your customer takes action to send the amount to you through a redirect and there is immediate confirmation about the success or failure of a payment.

    Step 1: Create a Source object

    To create a Source object client-side, follow the iDEAL Bank Element Quickstart. The iDEAL Bank Element lets your customers select their bank inline (rather than in an interstitial bank selection page) and check out faster. Once you’ve created a source object, you can proceed to customer authorization in the next step.

    <script src="https://js.stripe.com/v3/"></script>
    
    <form id="payment-form">
      <div class="form-row">
        <label for="name">
          Name
        </label>
        <input id="name" name="name" placeholder="Jenny Rosen" required>
      </div>
    
      <div class="form-row">
        <label for="ideal-bank-element">
          iDEAL Bank
        </label>
        <div id="ideal-bank-element">
          <!-- A Stripe Element will be inserted here. -->
        </div>
      </div>
    
      <button>Submit Payment</button>
    
      <!-- Used to display form errors. -->
        <div id="error-message" role="alert"></div>
    </form>
    /**
    * The CSS shown here will not be introduced in the Quickstart guide, but
    * shows how you can use CSS to style your Element's container.
    */
    input,
    .StripeElement {
      height: 40px;
    
      color: #32325d;
      background-color: white;
      border: 1px solid transparent;
      border-radius: 4px;
    
      box-shadow: 0 1px 3px 0 #e6ebf1;
      -webkit-transition: box-shadow 150ms ease;
      transition: box-shadow 150ms ease;
    }
    
    input {
      padding: 10px 12px;
    }
    
    input:focus,
    .StripeElement--focus {
      box-shadow: 0 1px 3px 0 #cfd7df;
    }
    
    .StripeElement--invalid {
      border-color: #fa755a;
    }
    See all 34 lines .StripeElement--webkit-autofill { background-color: #fefde5 !important; }
    // Create a Stripe client.
    // Note: this merchant has been set up for demo purposes.
    var stripe = Stripe('pk_test_6pRNASCoBOKtIshFeQd4XMUh');
    
    // Create an instance of Elements.
    var elements = stripe.elements();
    
    // Custom styling can be passed to options when creating an Element.
    // (Note that this demo uses a wider set of styles than the guide below.)
    var style = {
      base: {
        padding: '10px 12px',
        color: '#32325d',
        fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
        fontSmoothing: 'antialiased',
        fontSize: '16px',
        '::placeholder': {
          color: '#aab7c4'
        },
      },
      invalid: {
        color: '#fa755a',
      }
    };
    
    // Create an instance of the idealBank Element.
    var idealBank = elements.create('idealBank', {style: style});
    
    // Add an instance of the idealBank Element into the `ideal-bank-element` <div>.
    idealBank.mount('#ideal-bank-element');
    See all 67 lines var errorMessage = document.getElementById('error-message'); // Handle form submission. var form = document.getElementById('payment-form'); form.addEventListener('submit', function(event) { event.preventDefault(); showLoading(); var sourceData = { type: 'ideal', amount: 1099, currency: 'eur', owner: { name: document.querySelector('input[name="name"]').value, }, // Specify the URL to which the customer should be redirected // after paying. redirect: { return_url: 'https://shop.example.com/crtA6B28E1', }, }; // Call `stripe.createSource` with the idealBank Element and additional options. stripe.createSource(idealBank, sourceData).then(function(result) { if (result.error) { // Inform the customer that there was an error. errorMessage.textContent = result.error.message; errorMessage.classList.add('visible'); stopLoading(); } else { // Redirect the customer to the authorization URL. errorMessage.classList.remove('visible'); stripeSourceHandler(result.source); } }); });

    Custom client-side source creation

    If you choose to collect your customer’s bank yourself or not collect it all, create your own form and call stripe.createSource as described in the Stripe.js reference. When doing so, make sure to collect the following information from your customer:

    Parameter Value
    type ideal
    amount A positive integer in the smallest currency unit representing the amount to charge the customer (e.g., 1099 for a €10.99 payment).
    currency eur (iDEAL must always use Euros)
    ideal[bank]
    optional
    The customer’s bank.
    redirect[return_url] The URL the customer should be redirected to after the authorization process.
    statement_descriptor
    optional
    A custom statement descriptor for the payment.

    Server-side source creation

    A Source object can also be created server-side using the Source creation endpoint with the above parameters.

    Using either method, Stripe returns a Source object containing the relevant details for the method of payment used. Information specific to iDEAL is provided within the ideal subhash.

    {
      "id": "src_16xhynE8WzK49JbAs9M21jaR",
      "object": "source",
      "amount": 1099,
      "client_secret": "src_client_secret_UfwvW2WHpZ0s3QEn9g5x7waU",
      "created": 1445277809,
      "currency": "eur",
      "flow": "redirect",
      "livemode": true,
      "owner": {
    See all 35 lines "address": null, "email": null, "name": "Jenny Rosen", "phone": null, "verified_address": null, "verified_email": null, "verified_name": "Jenny Rosen", "verified_phone": null }, "redirect": { "return_url": "https://shop.example.com/crtA6B28E1", "status": "pending", "url": "https://hooks.stripe.com/redirect/src_16xhynE8WzK49JbAs9M21jaR?client_secret=src_client_secret_UfwvW2WHpZ0s3QEn9g5x7waU" }, "statement_descriptor": null, "status": "pending", "type": "ideal", "usage": "single_use", "ideal": { "bank": "ing", "bic": null, "iban_last4": null, "statement_descriptor": null } }

    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.

    Optional: Providing a custom statement descriptor

    iDEAL requires a statement descriptor before the customer is redirected to authenticate the payment. By default, your Stripe account’s statement descriptor is used (you can review this in the Dashboard). You can provide a custom descriptor by specifying statement_descriptor when creating a source.

    stripe.createSource({
      type: 'ideal',
      amount: 1099,
      currency: 'eur',
      statement_descriptor: 'ORDER AT11990',
      owner: {
        name: 'Jenny Rosen',
      },
      redirect: {
        return_url: 'https://shop.example.com/crtA6B28E1',
      },
    }).then(function(result) {
      // handle result.error or result.source
    });

    Providing a custom statement descriptor within a subsequent charge request has no effect.

    Optional: Specifying the customer's bank

    When your customer is redirected, they are first presented with a page to select which bank they use and complete authentication. If you already know which bank your customer uses for iDEAL payments, or request this during the checkout process, you can optionally provide the ideal[bank] parameter when creating a source and one of the following values. This allows your customer to be immediately redirected to their bank. The supported values for ideal[bank] are:

    Bank name Value
    ABN AMRO abn_amro
    ASN Bank asn_bank
    Bunq bunq
    ING ing
    Knab knab
    Moneyou moneyou
    Rabobank rabobank
    RegioBank regiobank
    SNS Bank (De Volksbank) sns_bank
    Triodos Bank triodos_bank
    Van Lanschot van_lanschot

    Error codes

    Source creation for iDEAL 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_ideal_bank The iDEAL bank parameter is invalid. It must be one of the values provided above.

    Step 2: Have the customer authorize the payment

    When creating a source, its status is initially set to pending and cannot yet be used to make a charge request. Your customer must authorize an iDEAL payment to make the source chargeable. To allow your customer to authorize the payment, redirect them to the URL provided within theredirect[url] attribute of the Source object.

    After the authorization process, your customer is redirected back to the URL provided as a value of redirect[return_url]. This happens regardless of whether authorization was successful or not. If the customer has authorized the payment, the Source object’s status will transition to chargeable when it is ready to be used in a charge request. If your customer declines the payment, the status will transition to failed.

    Stripe populates the redirect[return_url] with the following GET parameters when returning your customer to your website:

    • source: a string representing the original ID of the Source object
    • livemode: indicates if this is a live payment, either true or false
    • client_secret: used to confirm that the returning customer is the same one who triggered the creation of the source (source IDs are not considered secret)

    You may include any other GET parameters you may need when specifying redirect[return_url]. Do not use the above as parameter names yourself as these would be overridden with the values we populate.

    Mobile applications

    To integrate iDEAL within a mobile application, provide your application URI scheme as the redirect[return_url] value. By doing so, your customers are returned to your app after completing authorization. Refer to our Sources documentation for iOS or Android to learn more.

    If you are integrating without using our mobile SDKs, the redirect URL must be opened using the device’s native browser. The use of in-app web views and containers is prohibited by iDEAL and can prevent your customer from completing authentication—resulting in a lower conversion rate.

    Testing the redirect process

    When creating a Source object using your test API keys, you can follow the URL returned in the redirect[url] field. This leads to a Stripe page that displays information about the API request, and where you can either authorize or cancel the payment. Authorizing the payment redirects you to the URL specified in redirect[return_url].

    Step 3: Charge the Source

    Once the customer has authenticated the payment, the source’s status transitions to chargeable and it can be used to make a charge request. This transition happens asynchronously and may occur after the customer was redirected back to your website.

    Some customers using iDEAL assume that the order process is complete once they have authenticated the payment and received confirmation from their bank. This results in customers who close their browser instead of following the redirect and returning to your app or website.

    For these reasons it is essential that your integration rely on webhooks to determine when the source becomes chargeable in order to create a charge. Please refer to our best practices for more details on how to best integrate payment methods using webhooks.

    Webhooks

    The following webhook events are sent to notify you about changes to the source’s status:

    Event Description
    source.chargeable A Source object becomes chargeable after a customer has authenticated and verified a payment.
    source.failed A Source object failed to become chargeable as your customer declined to authenticate the payment.
    source.canceled A Source object expired and cannot be used to create a charge.

    Make a charge request using the source

    Once the source is chargeable, from your source.chargeable webhook handler, you can make a charge request using the source ID as the value for the source parameter to complete the payment.

    curl https://api.stripe.com/v1/charges \
       -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
       -d amount=1099 \
       -d currency=eur \
       -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_4eC39HqLyjWDarjtT1zdp7dc"
    
    charge = Stripe::Charge.create({
      amount: 1099,
      currency: 'eur',
      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_4eC39HqLyjWDarjtT1zdp7dc"
    
    charge = stripe.Charge.create(
      amount=1099,
      currency='eur',
      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_4eC39HqLyjWDarjtT1zdp7dc");
    
    $charge = \Stripe\Charge::create(array(
      "amount" => 1099,
      "currency" => "eur",
      "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_4eC39HqLyjWDarjtT1zdp7dc";
    
    Map<String, Object> chargeParams = new HashMap<String, Object>();
    chargeParams.put("amount", 1099);
    chargeParams.put("currency", "eur");
    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_4eC39HqLyjWDarjtT1zdp7dc");
    
    stripe.charges.create({
      amount: 1099,
      currency: "eur",
      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_4eC39HqLyjWDarjtT1zdp7dc"
    
    chargeParams := &stripe.ChargeParams{
      Amount: stripe.Int64(1099),
      Currency: stripe.String(string(stripe.CurrencyEUR)),
    }
    chargeParams.SetSource("src_18eYalAHEMiOZZp1l9ZTjSU0")
    ch, err := charge.New(chargeParams)
    

    iDEAL Sources are single-use and cannot be used for recurring or additional payments. For recurring use, you can use this source to create a reusable SEPA Direct Debit source.

    Refer to our Sources & Customers guide for more information on how single-use Sources interact with Customers.

    Step 4: Confirm that the charge has succeeded

    Since iDEAL is a synchronous payment method and the customer has already authenticated the payment as part of the redirect, unless there is an unexpected error, the Charge will immediately succeed.

    You will also receive the following webhook event as the charge is created:

    Event Description
    charge.succeeded The charge succeeded and the payment is complete.

    We recommend that you rely on the charge.succeeded webhook event to 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.

    Disputed payments

    The risk of fraud or unrecognized payments is extremely low with iDEAL as the customer must authenticate the payment with their bank. As such, there is no dispute process that can result in a chargeback and funds withdrawn from your Stripe account.

    Sources expiration

    A source must be used within six hours of becoming chargeable. If it is not, its status is automatically transitioned to canceled and your integration receives a source.canceled webhook event. Additionally, pending sources are canceled after one hour if they are not used to authenticate a payment.

    Once a source is canceled, the customer’s authenticated payment is refunded automatically—no money is moved into your account. For this reason, make sure the order is canceled on your end and the customer is notified once you receive the source.canceled event.

    Refunds

    Payments made with iDEAL can only be refunded within 180 days after the charge completes. After that refund window closes, it is no longer possible to refund the charge.

    Related resources

    Questions?

    We're always happy to help with code or other questions you might have! Search our documentation, contact support, or connect with our sales team. You can also chat live with other developers in #stripe on freenode.

    Was this page helpful? Yes No

    Send

    Thank you for helping improve Stripe's documentation. If you need help or have any questions, please consider contacting support.