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.
This 3DS payment process flow requires additional steps related to RBI mandates. Please contact Stripe to know the changes before integrating this flow.
Steps: Overview
Here's a detailed set of steps on how the entire 3DS checkout flow works:
- 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.
- 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.
- Authenticate
- Stripe.js will show authentication window only if additional authentication is required.
- On successful authentication, payment intent status will change to "requires capture"
- 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:
- A Chargebee account. Signup for a free trial if you don't have one.
- Plans configured in Chargebee.
- Stripe account in test mode, and integrated with your Chargebee user interface.
- Stripe Test mode's Publishable key.
- Stripe Test mode's Secret key.
- Your Chargebee API key for your test site.
- Stripe API integration on your Server-side.
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.
<script src="https://js.stripe.com/v3/"></script>
var stripe = Stripe('pk_test_KnsIwFNro5WaA41h1qO4kNbK87N9KXBLxR');
<div class="row">
<div id="card-element"></div>
</div>
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.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.
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.
/**
* 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"));
For the tutorial, we have configured the site credentials in config/environments/development.rb
ENV["CHARGEBEE_SITE"]="honeycomics-test"
ENV["CHARGEBEE_API_KEY"]="test_5LjFA6K6doB2EKRP7cufTd5TvT32a5BrT"
We setup the client library in config/initializers/chargebee.rb
ChargeBee.configure(:site => ENV["CHARGEBEE_SITE"], :api_key => ENV["CHARGEBEE_API_KEY"])
For the tutorial, we have configured the site credentials in Config.php
require_once(dirname(__FILE__) . "/lib/ChargeBee.php");
/*
* Sets the environment for calling the Chargebee API.
* You need to sign up at ChargeBee app to get this credential.
* It is better if you fetch configuration from the environment
* properties instead of hard coding it in code.
*/
ChargeBee_Environment::configure("honeycomics-test", "test_5LjFA6K6doB2EKRP7cufTd5TvT32a5BrT");
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.
Stripe.apiKey = "<your-stripe-api-key>";
ENV["STRIPE_API_KEY"]="<your-stripe-secret-key>"
\Stripe\Stripe::setApiKey("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"***
Chargebee APIs support both 'manual' and 'automatic' confirmation. Learn more
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.
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();
result = ChargeBee::Estimate.create_subscription({
:billing_address => {
:line1 => _params['addr'],
:line2 => _params['extended_addr'],
:city => _params['city'],
:stateCode => _params['state'],
:zip => _params['zip_code'],
:country => "US"
},
:subscription => {
:plan_id => "basic"
}
})
$result = ChargeBee_Estimate::createSubscription(array(
"billingAddress" => array(
"line1" => $body['addr'],
"line2" => $body['extended_addr'],
"city" => $body['city'],
"stateCode" => $body['state'],
"zip" => $body['zip_code'],
"country" => "US"
),
"subscription" => array(
"planId" => "basic"
)
));
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.
The capture_method key is set to automatic by default. If not changed to manual, authorization and capture happens on your side, and Chargebee cannot charge the card.
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.
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);
# Create the PaymentIntent
intent = Stripe::PaymentIntent.create(
payment_method: params['payment_method_id'],
amount: estimate.invoice_estimate.total,
currency: estimate.invoice_estimate.currency_code,
confirm: 'true',
confirmation_method: 'manual',
capture_method: 'manual',
setup_future_usage: 'off_session'
)
// Creating payment intent in Stripe
$intent = \Stripe\PaymentIntent::create([
"payment_method" => $body['payment_method_id'],
"amount" => $estimate->invoiceEstimate->total,
"currency" => $estimate->invoiceEstimate->currencyCode,
"confirm" => "true",
"confirmation_method" => "manual",
"capture_method" => "manual",
"setup_future_usage" => "off_session"
]);
Create a customer in Stripe and pass the value of the customer
parameter in the above code during PaymentIntent creation. This parameter is mandatory to pass for merchants who are using Stripe's RBI e-mandate flow to collect recurring payments in India.
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.
For PaymentIntents, API versions in Stripe before 2019-02-11 show requires_source_action instead of requires_action.
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;
}
}
def generate_payment_response(intent)
Stripe.api_key = ENV["STRIPE_API_KEY"]
if (intent.status == 'requires_source_action' || intent.status == 'requires_action') &&
intent.next_action.type == 'use_stripe_sdk'
# Inform the client to handle the action
render json: {
requires_action: true,
payment_intent_client_secret: intent.client_secret
}.to_json
elsif intent.status == 'requires_capture'
# The payment didn't need any additional actions it just needs to be captured
# Now can pass this on to chargebee for creating subscription
render json: { success: true, payment_intent_id: intent.id }.to_json
else
# Invalid status
return [500, { error: intent.status }.to_json]
end
end
function generatePaymentResponse($intent) {
if (($intent['status'] == 'requires_source_action' || $intent['status'] == 'requires_action') &&
$intent['next_action']['type'] == 'use_stripe_sdk') {
// Inform the client to handle the action
print json_encode(array(
'requires_action' => true,
'payment_intent_client_secret' => $intent['client_secret']
));
}
else if ($intent['status'] == 'requires_capture') {
// The payment didn't need any additional actions it just needs to be captured
// Now can pass this on to chargebee for creating subscription
print json_encode(array(
'success' => true,
'payment_intent_id' => $intent['id']
));
}
else {
// Invalid status
print json_encode(array(
'success' => false,
'error' => $intent['status']
));
}
}
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.
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.
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;
}
}
def create_subscription(_params)
# 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.
#
# Note : Here customer object received from client side is sent directly
# to ChargeBee.It is possible as the html form's input names are
# in the format customer[<attribute name>] eg: customer[first_name]
# and hence the $_POST["customer"] returns an associative array of the attributes.
result = ChargeBee::Subscription.create({
:plan_id => "basic",
:customer => {
:first_name => _params['customer']['first_name'],
:last_name => _params['customer']['last_name'],
:email => _params['customer']['email'],
:phone => _params['customer']['phone']
},
:payment_intent => {
:gw_token => _params['payment_intent_id'],
:gateway_account_id => "<your-gateway-account-id>"
}
})
return result
end
function createSubscription($body) {
/*
* Constructing a parameter array for create subscription api.
* It will have account information, the payment intent got from Stripe and
* plan details.
* For demo purpose a plan with id 'basic' is hard coded.
* Other params are obtained from request object.
* Note : Here customer object received from client side is sent directly
* to ChargeBee.
*
*/
$createSubscriptionParams = array(
"planId" => "basic",
"customer" => $body['customer'],
"payment_intent" => array(
"gw_token" => $body['payment_intent_id'],
"gateway_account_id" => "<stripe_gateway_account_id>"
)
);
/*
* Sending request to the chargebee server to create the subscription from
* the parameters received. The result will have customer,subscription and
* card attributes.
*/
$result = ChargeBee_Subscription::create($createSubscriptionParams);
return $result;
}
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.
/* Forwarding to thank you page after successful create subscription.
*/
//Writing json. Suggestion: Use proper json library
out.write("{\"forward\": \"/stripe_js_3ds/thankyou.html\"}");
# Forwarding to thank you page after successful create subscription.
render json: {
:forward => "thankyou.html"
}
$jsonResp = array();
/*
* Forwarding to success page after successful create subscription in ChargeBee.
*/
$jsonResp["forward"] = "thankyou.html";
echo json_encode($jsonResp, true);
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.
Reference Links
We're always happy to help you with any questions you might have!
support@chargebee.com