3D Secure Card Payments with Sources Preview

    Use Sources to accept card payments verified using 3D Secure, an additional layer of authentication that protects you from liability for fraudulent card payments. If you need help after reading this, check out our answers to common questions or chat live with other developers in #stripe on freenode.

    Stripe users in the following countries can process card payments that require authentication with 3D Secure using Sources—a single integration path for creating payments using any supported method:

    • United States
    • Canada
    • Europe
    • Australia (for AUD, EUR, and USD payments only)

    The process for 3D Secure card payments begins in the same way as regular card payments as your integration first creates a Source object that represents the card information. Instead of using this source to make a charge request, it’s used to create a 3D Secure Source object and your customer is then redirected to their card issuer’s website to verify their identity using 3D Secure. Once completed, your integration uses the 3D Secure source to make a charge request and complete the payment.

    Within the scope of Sources, 3D Secure card payments are are a pull-based, synchronous method of payment. This means that your integration takes action to debit the amount from the customer’s card and there is immediate confirmation about the success or failure of a payment.

    Considerations for using 3D Secure

    3D Secure (also known as Mastercard SecureCode and Verified by Visa) provides a layer of protection against fraudulent payments that’s supported by most card-issuing banks. Unlike regular card payments, 3D Secure card payments require cardholders to complete an additional verification step with the issuer. Merchants are not liable for any fraud related to payments that have been authenticated with 3D Secure—the card issuer assumes full responsibility.

    While 3D Secure protects you from fraud, it requires your customers to complete additional steps during the payment process that could impact their checkout experience. For instance, if a customer does not know their 3D Secure information, they might not be able to complete the payment.

    When considering the use of 3D Secure, you might find the right balance is to use it only in situations where there is an increased risk of fraud, or if the customer’s card would be declined without it.

    Handling card information

    Card information is sensitive by nature. While sources for other payment methods are created server-side, card sources must be created client-side using Stripe.js. This ensures that no sensitive card data passes through your server so your integration can operate in a PCI compliant way.

    When your customer submits their card information using your payment form, it is sent directly to Stripe, and a representative Source object is returned for you to use. The process is similar to the creation of tokens. If you’re already using Stripe.js to tokenize card information, switching to sources is only a small change.

    Step 1: Create a card Source object

    To create a source with Stripe.js, first include the library within your payment page and set your publishable API key. Once included, use the following source.create method (instead of card.createToken) to create a source when a customer submits their card information in your payment form:



    Stripe.source.create({
      type: 'card',
      card: {
        number: $('.card-number').val(),
        cvc: $('.card-cvc').val(),
        exp_month: $('.card-expiry-month').val(),
        exp_year: $('.card-expiry-year').val(),
      },
      owner: {
        address: {
          postal_code: $('.address_zip').val()
        }
      }
    }, stripeResponseHandler);
    

    When your script receives the response from Stripe’s servers, the following stripeResponseHandler function is called. Similar to using Stripe.js to create card tokens, this function does the following:

    • If the card information entered by the user returned an error, the error is displayed on the page
    • If no errors were returned then a source was created successfully. Add the returned source ID to the form and submit the form to your server.
    function stripeResponseHandler(status, response) {
    
      // Grab the form:
      var $form = $('#payment-form');
    
      if (response.error) { // Problem!
    
        // Show the errors on the form
        $form.find('.payment-errors').text(response.error.message);
        $form.find('button').prop('disabled', false); // Re-enable submission
    
      } else { // Source was created!
    
        // Get the source ID:
        var source = response.id;
    
        // Insert the source into the form so it gets submitted to the server:
        $form.append($('<input type="hidden" name="source" />').val(source));
    
        // Submit the form:
        $form.get(0).submit();
    
      }
    }
    

    Once the source has been created, its status is immediately set to chargeable. No additional customer action is needed so the source can be used straight away.

    {
      "id": "src_19YP2AAHEMiOZZp1Di4rt1K6",
      "object": "source",
      "amount": null,
      "client_secret": "src_client_secret_jMvnQ7PQ2HCIiWtkfmU3QNEC",
      "created": 1483575790,
      "currency": null,
      "flow": "none",
      "livemode": false,
      "metadata": {},
      "owner": {
        "address": null,
        "email": null,
        "name": null,
        "phone": null,
        "verified_address": null,
        "verified_email": null,
        "verified_name": null,
        "verified_phone": null
      },
      "status": "chargeable",
      "type": "card",
      "usage": "reusable",
      "card": {
        "exp_month": 12,
        "exp_year": 2018,
        "brand": "Visa",
        "country": "US",
        "cvc_check": "unchecked",
        "funding": "credit",
        "last4": "3063",
        "three_d_secure": "required",
        "address_line1_check": null,
        "address_zip_check": null
      }
    }

    At this stage, you can either proceed with a regular card payment and make a charge request, or continue with the 3D Secure process.

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

    Determining if 3D Secure is required

    Some issuing banks mandate that 3D Secure is required. If 3D Secure verification is not performed for cards that require it, any charge you attempt would be declined. Use the card.three_d_secure attribute of the card source to determine whether 3D Secure is required. The possible values are:

    • required: 3D Secure is required
    • optional: 3D Secure is optional
    • not_supported: 3D Secure is not supported on this card

    Step 2: Create a 3D Secure Source object

    Using the card source that has been created using Stripe.js, a 3D Secure Source object is then created. this can also be created client-side using Stripe.js or server-side using the API.

    Parameter Value
    type three_d_secure
    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 The currency the payment is being created in (e.g., eur).
    redirect[return_url] The URL the customer should be redirected to after they have successfully verified the payment.
    three_d_secure[card] The ID of the card source.
    Stripe.source.create({
      type: 'three_d_secure',
      amount: 1099,
      currency: "eur",
      three_d_secure: {
        card: src_19YP2AAHEMiOZZp1Di4rt1K6
        },
      redirect: {
        return_url: "https://shop.foo.com/crtA6B28E1"
        }
    }, stripeResponseHandler);
    

    Server-side source creation

    The use of Stripe.js to create this type of 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 any sensitive information that passes through your servers.

    curl https://api.stripe.com/v1/sources \
       -u sk_test_BQokikJOvBiI2HlWgH4olfQ2: \
       -d amount=1099 \
       -d currency=eur \
       -d type=three_d_secure \
       -d redirect[return_url]="https://shop.foo.com/crtA6B28E1" \
       -d three_d_secure[card]=src_19YP2AAHEMiOZZp1Di4rt1K6
    
    # 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({
      amount: 1099,
      currency: 'eur',
      type: 'three_d_secure',
      three_d_secure: {card: 'src_19YP2AAHEMiOZZp1Di4rt1K6'},
      redirect: {return_url: 'https://shop.foo.com/crtA6B28E1'},
    })
    
    # 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(
      amount=1099,
      currency='eur',
      type='three_d_secure',
      three_d_secure={'card': 'src_19YP2AAHEMiOZZp1Di4rt1K6'},
      redirect={'return_url': 'https://shop.foo.com/crtA6B28E1'},
    )
    
    // 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(
      "amount" => 1099,
      "currency" => "eur",
      "type" => "three_d_secure",
      "three_d_secure" => array("card" => "src_19YP2AAHEMiOZZp1Di4rt1K6"),
      "redirect" => array("return_url" => "https://shop.foo.com/crtA6B28E1"),
    ));
    
    // 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> sourceParams = new HashMap<String, Object>();
    sourceParams.put("amount", 1099);
    sourceParams.put("currency", "eur");
    sourceParams.put("type", "three_d_secure");
    
    Map<String, Object> redirectParams = new HashMap<String, Object>();
    redirectParams.put("return_url", "https://shop.foo.com/crtA6B28E1");
    sourceParams.put("redirect", redirectParams);
    Map<String, Object> threeDSecureParams = new HashMap<String, Object>();
    threeDSecureParams.put("card", "src_19YP2AAHEMiOZZp1Di4rt1K6");
    sourceParams.put("three_d_secure", threeDSecureParams);
    
    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({
      amount: 1099,
      currency: "eur",
      type: "three_d_secure",
      three_d_secure: {card: "src_19YP2AAHEMiOZZp1Di4rt1K6"},
      redirect: {return_url: "https://shop.foo.com/crtA6B28E1"},
    }, function(err, source) {
      // asynchronously called
    });
    

    This results in the following response:

    {
      "id": "src_19YlvWAHEMiOZZp1QQlOD79v",
      "object": "source",
      "amount": 1099,
      "client_secret": "src_client_secret_kBwCSm6Xz5MQETiJ43hUH8qv",
      "created": 1483663790,
      "currency": "eur",
      "flow": "redirect",
      "livemode": false,
      "metadata": {},
      "owner": {
        "address": null,
        "email": null,
        "name": null,
        "phone": null,
        "verified_address": null,
        "verified_email": null,
        "verified_name": null,
        "verified_phone": null
      },
      "redirect": {
        "return_url": "https://shop.foo.com/crtA6B28E1",
        "status": "pending",
        "url": "https://hooks.stripe.com/redirect/authenticate/src_19YlvWAHEMiOZZp1QQlOD79v?client_secret=src_client_secret_kBwCSm6Xz5MQETiJ43hUH8qv"
      },
      "status": "pending",
      "type": "three_d_secure",
      "usage": "single_use",
      "three_d_secure": {
        "card": "src_19YP2AAHEMiOZZp1Di4rt1K6",
        "customer": null,
        "authenticated": false
      }
    }
    

    Error codes

    Source creation for 3D Secure card 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.

    Testing 3D Secure payments

    Most of our test cards return redirect.status as succeeded without going through the full 3D Secure flow. Use the following card information to fully test the 3D Secure process:

    • 4000000000003055: all transactions on this card succeed
    • 4000000000003063: only successful 3D Secure transactions on this card will succeed

    Step 3: Have the customer authorize the payment

    When creating a 3D Secure source, it’s status is most commonly first set to pending and cannot yet be used to create a charge. Your customer must successfully verify their identity with their card issuer to make the source chargeable. To allow your customer to verify their identity using 3D Secure, redirect them to the URL provided within theredirect[url] attribute of the Source object. We recommend using a 302 HTTP status code when performing this redirect if the source has been created as part of a form submission or using client-side Javascript using AJAX.

    After the customer has authorized the payment, the Source object’s status is updated to chargeable and it is ready to use in a charge request. Additionally, the three_d_secure.authenticated attribute is set to true. Stripe populates the redirect[return_url] with the following GET parameters when your customer returns 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.

    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].

    IFRAME redirect

    The redirect for 3D Secure verification can also take place within an IFRAME, allowing your customer to remain on your website during the process. To perform verification within an IFRAME:

    1. Set the return_url to a static holding page when creating a 3D Secure source
    2. Create an IFRAME from the redirect[url] using JavaScript
    3. Start client-side polling of the source to determine updates to its status
    4. Once the source’s status transitions to chargeable, close the IFRAME
    5. Continue polling the source and display a payment confirmation message once the source’s status transitions to consumed

    Although return_url still needs to be set when creating a 3D Secure source, your customer is not redirected away from your site once verification is completed—the IFRAME is simply closed instead. The static page should contain a message informing the customer that the verification process is complete and that the window will close shortly.

    Mobile applications

    Certain banks support 3D Secure verification within a natively installed banking app. If you are integrating 3D Secure within a mobile application and your customers are redirected, the redirect URL must be opened using the phone’s native browser (e.g., Safari on iOS). The use of in-app web views and containers can prevent your customer’s installed banking app from launching to complete authentication—resulting in a lower conversion rate.

    You can use your application URI scheme when providing a value for redirect[return_url] so that your customers are returned to your app after they have completed authentication.

    No verification required

    In some cases, a 3D Secure source’s status can be immediately set to chargeable. This can happen if the customer’s card has not yet been enrolled in 3D Secure. Should this occur, the redirect.status value is set to succeeded and three_d_secure.authenticated set to false.

    If the customer’s card is enrolled in 3D Secure but the issuing bank determines the risk of fraud is low, the source’s three_d_secure.authenticated attribute is set to true.

    Step 4: Confirm that the source is ready to use

    Once the customer has authenticated the payment, the source’s status transitions to chargeable and it can be used to make a charge request. You can determine whether authentication was successful using client-side source polling with Stripe.js, or webhooks.

    Client-side source polling

    You can use Stripe.js to poll the Source object, client-side. The JavaScript handler is called as soon as the Source object is retrieved, client-side, and every time its status is updated. In particular, it is called when the status changes from pending to either failed or chargeable, and from chargeable to consumed once your server makes the associated charge request.

    Stripe.source.poll(
      "src_16xhynE8WzK49JbAs9M21jaR",
      "src_client_secret_UfwvW2WHpZ0s3QEn9g5x7waU",
      function(status, source) {
        // `source`: is the source object.
        // `status`: is the HTTP status. if non 200, an error occured
        //          and the poll is canceled.
    
        // This handler is called as soon as the source is retrieved and subsequently
        // anytime the source's status (`source.status`) is updated.
      })
    

    Webhooks

    The following webhook events are also 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.canceled A Source object expired and cannot be used to create a charge.
    source.consumed A Source object that was single-use has already been charged.
    source.failed A Source object failed to become chargeable as your customer declined to authenticate the payment.

    Handling customers that don’t return after completing authentication

    In some rare instances, customers may assume that the order process is complete once they have completed the 3DS authentication flow. This can result in the customers closing their browser instead of completing the redirection back to your app or website. When accepting 3D Secure card-based payments, use the source.chargeable event to confirm when a source is chargeable, even if you make use of client-side polling. This allows you to confirm that the customer has verified their identity and the source is can be used in a charge request, even if they have closed their browser.

    Source 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.

    As card 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 card debited and you receive the funds.

    Step 5: Make a charge request using the source

    Once the source is chargeable, 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_BQokikJOvBiI2HlWgH4olfQ2: \
       -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_BQokikJOvBiI2HlWgH4olfQ2"
    
    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_BQokikJOvBiI2HlWgH4olfQ2"
    
    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_BQokikJOvBiI2HlWgH4olfQ2");
    
    $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_BQokikJOvBiI2HlWgH4olfQ2";
    
    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_BQokikJOvBiI2HlWgH4olfQ2");
    
    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_BQokikJOvBiI2HlWgH4olfQ2"
    
    chargeParams := &stripe.ChargeParams{
      Amount: 1099,
      Currency: "eur",
    }
    chargeParams.SetSource("src_18eYalAHEMiOZZp1l9ZTjSU0")
    ch, err := charge.New(chargeParams)
    

    Recurring payments using 3D Secure-based cards

    A 3D Secure source cannot be used for recurring payments. Instead, you can save the original card source to a Customer object once a successful charge request using the 3D Secure source has been made. This ensures that your customer’s initial payment is verified using 3D Secure. The ID of the card source can be retrieved from the 3D Secure source as the value of its three_d_secure.card attribute.

    curl https://api.stripe.com/v1/customers \
       -u sk_test_BQokikJOvBiI2HlWgH4olfQ2: \
       -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({
      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(
      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(
      "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("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({
      source: "src_18eYalAHEMiOZZp1l9ZTjSU0",
    }, function(err, customer) {
      // asynchronously called
    });
    

    When signing up a customer to a subscription using a 3D Secure-based card, the first payment must be processed independently so the 3D Secure process can be completed. As a result, you may want to make use of trial periods to ensure that future payments start from the next billing period and not immediately. This prevents your customer from being billed at the start of the subscription, since they have already made a first payment during the 3D Secure process.

    Step 6: Confirm that the charge has succeeded and the payment is complete

    Since 3D Secure-based card payments is a synchronous payment method, the Charge object’s status immediately reflects whether or not it has been successful. Either of 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.

    Disputed payments

    Card networks provide a process for cardholders to dispute payments made with their card. A dispute can be filed by the cardholder any time after a payment has been successful. When accepting 3D Secure-based card payments, the customer’s bank is liable if any payment is disputed as fraudulent or unrecognized. These types of disputes are handled internally, do not appear in the Dashboard, and do not result in funds being withdrawn from your Stripe account.

    Should a customer dispute a payment for any other reason (e.g. product not received), then the standard dispute process applies. As such, you should make the appropriate decisions regarding your business and how you manage disputes, if they occur, and how to avoid them completely.

    Next steps

    Congrats! You've learned about processing 3D Secure-based card payments using Sources. Some documentation you might want to read next: