More Tutorials

3DS supported Stripe.js Integration with Chargebee APIs

Stripe
3D Secure
Product Catalog 1.0

Tutorial Scope

We will only address the flow specifics of processing your payment through 3DS using Stripe Elements, integrated through Stripe.js and Chargebee APIs. We have also included the example code and Github links to it. This way, you can try out the tutorial with our mock checkout.

Stripe.js: Overview

Stripe.js is a JavaScript library, which is accessible via APIs to tokenize customer information by collecting sensitive card data using customizable Stripe Elements. For further details on Stripe.js, take a look at Stripe's documentation.

Steps: Overview

Here's a detailed set of steps on how the entire 3DS checkout flow works:

Subscription Entitlements
Subscription Entitlements
  1. Create Payment Method
    • User enters their card details inside Stripe Elements.
    • On submit, Stripe.js passes the card information to the Stripe server using Create PaymentMethod request.
    • Stripe verifies the card and returns a PaymentMethod ID.
  2. Create Payment Intent
    • The PaymentMethod ID along with other checkout details are submitted to your server.
    • On your server, using Estimate API, get the estimated amount for this checkout from Chargebee.
    • Create a customer in Stripe and pass the customer identifier during the PaymentIntent creation. This step is mandatory for merchants who are using Stripe's RBI e-mandate flow to collect recurring payments in India.
    • Using that amount, call Stripe API to Create a PaymentIntent on your server side.
  3. Authenticate
    • Stripe.js will show authentication window only if additional authentication is required.
    • On successful authentication, payment intent status will change to "requires capture"
  4. Create Subscription
    • Payment Intent along with other checkout details are submitted to your server.
    • These details are passed on to Chargebee's create subscription API.
    • Chargebee captures the payment using the intent and creates the subscription.

Honey Comics - Demo Application

Honeycomics is the demo application of Chargebee for which you can download the code and create the application to test out the flow mentioned in this tutorial.

Prerequisites

Before trying out this tutorial, you would need to setup the following:


Stripe.js + Chargebee API Integration

This will be a walkthrough of the working implementation of Stripe.js and Chargebee API integration for creating a subscription in Chargebee.

Integrate Stripe Elements

If you haven’t implemented Stripe Elements on your checkout page, please do so. The steps mentioned here require you to have a Stripe.js integration. You can follow the steps mentioned here to integrate Stripe Elements in your checkout using Stripe.js.

stripe_js_3ds/checkout.htmlView full code
<script src="https://js.stripe.com/v3/"></script>
stripe_js_3ds/checkout.htmlView full code
var stripe = Stripe('pk_test_KnsIwFNro5WaA41h1qO4kNbK87N9KXBLxR');
stripe_js_3ds/checkout.htmlView full code
<div class="row">
   <div id="card-element"></div>
</div>
stripe_js_3ds/checkout.htmlView full code
var elements = stripe.elements();
var cardElement = elements.create('card');
cardElement.mount('#card-element');

Server-side Stripe API Integration

Stripe API Integration on your Server-side is much needed to go forward. You can choose the client library that you prefer and integrate it in your backend. More information on the integration can be found under Stripe API integration.

Create Client-side Object: PaymentMethod

Now, you need to write JavaScript, in a way that it should trigger up as soon as the customer submits the form and,

  • Pass the card details to Stripe
  • Handle the response from Stripe

Use _stripe.createPaymentMethod('card', cardElement, {})_ to pass the card details via cardElement to Stripe. The parameter cardElement should be the Stripe element used for obtaining card information.

stripe_js_3ds/checkout.htmlView full code
stripe.createPaymentMethod('card', cardElement, {})

Handle PaymentMethod Response

If successful, the JSON response will have PaymentMethod object. In case the card validation fails, the JSON object will contain the error information.

From the PaymentMethod object, extract its ID and pass it on to your server for creating the payment intent.

stripe_js_3ds/checkout.htmlView full code
fetch('/stripe_js_3ds/confirm_payment', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
        payment_method_id: result.paymentMethod.id,
        addr: $('[name="addr"]').val(),
        extended_addr: $('[name="extended_addr"]').val(),
        city: $('[name="city"]').val(),
        state: $('[name="state"]').val(),
        zip_code: $('[name="zip_code"]').val(),
    })
}).then(function (result) {
    // Handle server response
    result.json().then(function (json) {
        handleServerResponse(json);
    })
});

Now let's switch to the server side implementation

Setup Chargebee’s Client library

Download and import the client library of your choice. Then, configure the client library with Chargebee's Test site and its full-access API Key.

For the tutorial, we have configured the site and the credentials in a separate properties file. When the webapp is initialized, the client library gets configured.

EnvInitializer.javaView full code
/**
  * The credentials are stored in a properties file under WEB-INF
  * The live site api keys should be stored securely. It should preferably
  * be stored only in the production machine(s) and not hard coded
  * in code or checked into a version control system by mistake.
  */
Properties credentials = read("WEB-INF/ChargeBeeCredentials.properties");
Environment.configure(credentials.getProperty("site"), credentials.getProperty("api_key"));

Setup Stripe’s Client library

Stripe API Integration is necessary on your server-side to proceed. Download and import the client library of your choice. Then, configure the client library with Stripe's test secret key.

StripeJs3dsCheckout.javaView full code
Stripe.apiKey = "<your-stripe-api-key>";

Create Server-side Object: PaymentIntent

Pass the PaymentMethod ID to create a PaymentIntent object on the Server-side. For this, you need to send a request to Stripe APIs for creating PaymentIntent object on your Server-side.

Setting confirmation

This tutorial follows manual confirmation flow. Hence, set the confirmation*method as ‘manual’. **"confirmationmethod" => "manual"***

The request further requires three compulsory keys to be passed while creating the PaymentIntent object:

Retrieving amount

The invoice amount that needs to be charged can be obtained by calling the Estimate API from Chargebee. This can then be passed to the amount parameter in PaymentIntent.

StripeJs3dsCheckout.javaView full code
Result result = Estimate.createSubscription()
    .subscriptionPlanId("annual")
    .billingAddressLine1(jsonObject.get("addr").toString())
    .billingAddressLine2(jsonObject.get("extended_addr").toString())
    .billingAddressCity(jsonObject.get("city").toString())
    .billingAddressZip(jsonObject.get("zip_code").toString())
    .billingAddressCountry("US")
    .request();

Setting capture method

Make sure you change the capture_method key to "manual" from "automatic". This way, the authorization happens on your side, and the capture happens on the Chargebee side. Learn more.

Setting future usage

You need to set the setup_future_usage parameter as "off_session" in the PaymentIntent. This ensures that the PaymentIntent doesn't fail while Chargebee attempts to charge the card. Learn more.

StripeJs3dsCheckout.javaView full code
Map<String, Object> paymentIntentParams = new HashMap<>();
paymentIntentParams.put("amount", estimate.invoiceEstimate().total());
paymentIntentParams.put("currency", estimate.invoiceEstimate().currencyCode());
paymentIntentParams.put("capture_method", "manual");
paymentIntentParams.put("confirmation_method", "manual");
paymentIntentParams.put("setup_future_usage", "off_session");
paymentIntentParams.put("confirm", true);
paymentIntentParams.put("payment_method", jsonObject.get("payment_method_id"));

intent = PaymentIntent.create(paymentIntentParams);

PaymentIntent Response

If the PaymentIntent response contains either one of these statuses,

  • requires_action
  • requires_source_action

It essentially means client authentication is required, which is explained in the next section.

PaymentIntent will return a client_secret_key along with the requires_action response.

StripeJs3dsCheckout.javaView full code
protected JSONObject generatePaymentResponse(PaymentIntent intent) throws Exception {
    try {
        JSONObject respJson = new JSONObject();
        if (("requires_source_action".equals(intent.getStatus()) || "requires_action".equals(intent.getStatus())) &&
                "use_stripe_sdk".equals(intent.getNextAction().getType())) {

            respJson.put("requires_action", true);
            respJson.put("payment_intent_client_secret", intent.getClientSecret());
        } else if ("requires_capture".equals(intent.getStatus())) {
            respJson.put("success", true);
            respJson.put("payment_intent_id", intent.getId());
        } else {
            respJson.put("success", false);
            respJson.put("error", intent.getStatus());
        }
        return respJson;
    }
    catch (Exception e) {
        throw e;
    }
}

Now, let's switch to the client side implementation

User Authentication

Using client_secret

  • You will have to pass client_secret_key to Stripe.js' stripe.handleCardAction function.
  • Stripe.js will take care of opening the authentication window.

Confirmation

After the authentication is complete, PaymentIntent needs to be confirmed either manually or automatically depending on which of the either value was passed while creating PaymentIntent.

stripe_js_3ds/checkout.htmlView full code
function handleAction(response) {
    stripe.handleCardAction(
        response.payment_intent_client_secret
    ).then(function (result) {
        if (result.error) {
            // Show error in payment form
            console.log(JSON.stringify(result));
            $(".alert-danger").show().text(result.error.message);
        } else {
            // The card action has been handled
            // The PaymentIntent can be confirmed again on the server
            fetch('/stripe_js_3ds/confirm_payment', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    payment_intent_id: result.paymentIntent.id
                })
            }).then(response => response.json()).then(function (confirmResult) {
                console.log("payment_intent_id: ", confirmResult.payment_intent_id, "success: ", confirmResult.success);
                handleServerResponse(confirmResult);
            });
        }
    });
}

Payment

  • Following confirmation, PaymentIntent status changes to requires_capture.
  • Now the PaymentIntent ID can be passed with Chargebee APIs to perform the necessary operations.
    stripe_js_3ds/checkout.htmlView full code
    fetch('/stripe_js_3ds/checkout', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            customer: {
                first_name: $('[name="customer[first_name]"]').val(),
                last_name: $('[name="customer[last_name]"]').val(),
                email: $('[name="customer[email]"]').val(),
                phone: $('[name="customer[phone]"]').val()
            },
            addr: $('[name="addr"]').val(),
            extended_addr: $('[name="extended_addr"]').val(),
            city: $('[name="city"]').val(),
            state: $('[name="state"]').val(),
            zip_code: $('[name="zip_code"]').val(),
            payment_intent_id: response.payment_intent_id,
        })
    }).then(response => response.json()).then(function (responseJSON) {
        window.location.replace(responseJSON.forward);
    });
    

Create subscription

You can fetch the PaymentIntent ID and other information from the POST parameters submitted by your form and use the Create Subscription API to create a subscription in Chargebee.

StripeJs3dsCheckout.javaView full code
public Result createSubscription(JSONObject jsonObject)
    throws Exception {
      try {
          /*
          * planId is id of the plan present in the ChargeBee app.
          * For demo purpose a plan with id 'annual' is hard coded and should exist
          * at your ChargeBee site. It can be also be received from request by
          * making customer selecting the plan of their choice at client side.
          */
          String planId = "annual";

          /* Sends request to the ChargeBee server to create the subscription from
          * the parameters received. The result will have subscription attributes,
          * customer attributes and card attributes.
          */
          JSONObject customerObject = (JSONObject) jsonObject.get("customer");
          Result result = Subscription.create()
                  .planId(planId)
                  .customerEmail(customerObject.get("email").toString())
                  .customerFirstName(customerObject.get("first_name").toString())
                  .customerLastName(customerObject.get("last_name").toString())
                  .customerPhone(customerObject.get("phone").toString())
                  .paymentIntentGatewayAccountId("gw_HmgkoB8RWZ3usv68")
                  .paymentIntentGwToken(jsonObject.get("payment_intent_id").toString()).request();

          return result;
      }
      catch (Exception e) {
          throw e;
      }
  }

Redirecting to a Thankyou page

When a subscription is created successfully, Chargebee returns a success response in JSON format which is wrapped as a 'result' class by the client library.

In the event of a successful checkout you can redirect the user to a simple 'Thank You' page.

StripeJs3dsCheckout.javaView full code
/* Forwarding to thank you page after successful create subscription.
*/
//Writing json. Suggestion: Use proper json library
out.write("{\"forward\": \"/stripe_js_3ds/thankyou.html\"}");

Validation and Error Handling

Here's how we validate user inputs and handle API call errors in this demo:

  • Client Side Validation: Chargebee uses jQuery form validation plugin to check whether the user’s field inputs(email, zip code and phone number) are valid or not.
  • Server Side Validation: As this is a demo application we have skipped the server side validation of all input parameters. But we recommend you to perform the validation at your end.
  • Payment Errors: If a payment fails due to card verification or processing errors, Chargebee returns an error response(as shown below) which is thrown as a payment exception by the client library. We handle the exceptions in the demo application with appropriate error messages.
    {
       "api_error_code": "payment_method_verification_failed",
       "error_code": "add_card_error",
       "error_msg": "Problem while adding the card. Error message : (3009) Do not honour.",
       "http_status_code": 400,
       "message": "Problem while adding the card. Error message : (3009) Do not honour.",
       "type": "payment"
    }
    
  • General API Errors: Chargebee might return error responses due to various reasons such as invalid configuration, bad request etc. To identify specific reasons for all error responses you can check the API documentation. Also take a look at the error handler file to check how these errors can be handled.

Test cards

You can now test your integration with test transactions. Here are some credit card numbers that you can use to test your application.

3DS Required 4000 0000 0000 3220
3DS Supported 4000 0000 0000 3055
3DS Not Supported 3782 8224 6310 005

For more test cards for testing different scenarios click here.

Was this tutorial helpful ?
Need more help?

We're always happy to help you with any questions you might have!

support@chargebee.com