A tutorial on deploying an in-app checkout page using Stripe.js Stripe.js tokenization feature to create subscriptions in Chargebee and store and process payments on Stripe. Stripe.js makes it easy to use any form page as a checkout page and also reduces your PCI DSS scope. It does this by taking care of all the sensitive card information for you.
Hosted Pages: The simplest way to setup your checkout process with Chargebee is by using the hosted payment pages or the Hosted Pages + API integration method. Chargebee’s hosted pages are built using the Bootstrap themes.
Stripe's Embedded checkout form: The other option is to use the embedded checkout form provided by stripe. See this tutorial for explanation on how to use it.
Overview:
What is Stripe JS?
Well, Stripe.js is a JavaScript library which you can wire into your checkout form to handle the credit card information. When a user signs up using the checkout form, it sends the credit card information directly from the user's browser to Stripe's servers. Meaning, credit card information never hits your server and thus reducing your PCI compliance scope. For further details, have a look at Stripe's documentation.
How exactly does this work?
Here's a detailed set of steps on how the entire checkout flow works:
- Using your signup form user enters their card and personal details.
- On form submit, Stripe.js passes just the card information to the Stripe server.
- Stripe verifies the card, stores it if successful and returns a temporary token.
- The temporary token and the form details are submitted to your server.
- And using the create subscription API call, the subscription information along with the temporary token is passed to Chargebee.
And, Voila, a subscription is created! Your users never left your website and neither did your servers handle any sensitive information.
Is this secure?
With Stripe.js, your server does not handle any sensitive credit card data, which reduces the PCI compliance burden. But we strongly recommend you to use Chargebee's hosted payment pages.
Honey Comics - Demo Application
'Honey Comics', our demo application, is a fictitious online comic book store providing a subscription service for comics. We send comic books every week to subscribers. Users can sign up for a subscription from the website by providing the account,payment and shipping address information.
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.
- A plan in Chargebee for your customers to subscribe to.You can setup the plan for the demo using the "Setup Configuration" option in the index page if you have downloaded code and started the tutorials locally.
- A Stripe account (test mode for now, live mode when you go into production).
- Your Stripe account integrated into your Chargebee account. Here is how you do it.
- Your Stripe test publishable key (don't forget to replace it with the live publishable key when in production).
- Your Chargebee API key for your test site.
Build the checkout form
We will start with the client side implementation. Let's start by building a form, for our users to sign up with. Now, only the card related information is passed to Stripe while remaining information such as account and shipping are passed to our server and then onto Chargebee. Our demo app's checkout page is an example of a simple form collecting basic account information (such as name, email, phone) and card information such as card number, expiry date, cvv. We also collect the shipping address.
Below form snippet shows the card number field alone.
<div class="form-group">
<label for="card_no">Credit Card Number</label>
<div class="row">
<div class="col-sm-6">
<input type="text" class="card-number form-control" id="card_no"
required data-msg-required="cannot be blank">
</div>
<div class="col-sm-6">
<span class="cb-cards hidden-xs">
<span class="visa"></span>
<span class="mastercard"></span>
<span class="american_express"></span>
<span class="discover"></span>
</span>
</div>
</div>
<small for="card_no" class="text-danger"></small>
</div>
Warning
We should not include a 'name' attribute to the card data fields in the form. This will ensure that the sensitive card data is not passed to our server when submitted.
The below form snippet shows an account field. In this case the name attribute is set as it needs to be passed on to our demo app server.
<div class="col-sm-6">
<div class="form-group">
<label for="customer[email]">Email</label>
<input id="email" type="text" class="form-control" name="customer[email]" maxlength=50
data-rule-required="true" data-rule-email="true"
data-msg-required="Please enter your email address"
data-msg-email="Please enter a valid email address">
<small for="customer[email]" class="text-danger"></small>
</div>
</div>
Wire up Stripe.js
Now that the form is built, let's wire up Stripe.js into our form. For Stripe.js to come into action, we add it to checkout page's header tag using script tag.
<script type="text/javascript" src="https://js.stripe.com/v2/"></script>
Set the Stripe Publishable Key
After the JavaScript library has been embedded, we set the Stripe publishable key. This is for Stripe to identify our account.
<!-- Setting the stripe publishable key.-->
<script>Stripe.setPublishableKey("pk_test_acfVSJh9Oo9QIGTAQpUvG5Ig");
</script>
Replace the sample key given above with your test publishable key. Also, don't forget to replace the test keys with live keys when going into production.
Send the card details to Stripe
So now that Stripe.js is all set in our web page, now let's write up some javascript for our form. The javascript should trigger up once a user submits the form and pass the card details to Stripe and also handle the response from Stripe. In our code, we used a Stripe's Stripe.createToken(params, stripeResponseHandler) to pass the card details to Stripe. The parameter params should be a JSON object containing the card information and stripeResponseHandler is a callback function to be executed once the call is complete. The Stripe call is asynchronous and stripe will call the callback function with a json object once it has stored the card details in its server(s)
$("#subscribe-form").on('submit', function(e) {
// form validation
formValidationCheck(this);
if(!$(this).valid()){
return false;
}
// Disable the submit button to prevent repeated clicks and form submit
$('.submit-button').attr("disabled", "disabled");
// createToken returns immediately - the supplied callback
// submits the form if there are no errors
Stripe.createToken({
number: $('.card-number').val(),
cvc: $('.card-cvc').val(),
exp_month: $('.card-expiry-month').val(),
exp_year: $('.card-expiry-year').val()
}, stripeResponseHandler);
return false; // submit from callback
});
Handle Stripe's response
The json object passed to the callback function will contain a temporary token and additional information. Incase the card validation fails then the json object will contain the error information.The sample responses are given below
- Success response.
{ "card": "Object", "created": 1384236766, "id": "tok_2vRInkdCyn8mTi", "livemode": false, "object": "token", "type": "card", "used": false }
- Error response.
{ "error": "Object", "code": "invalid_expiry_month", "message": "Your card's expiration month is invalid.", "param": "exp_month", "type": "card_error" }
In our demo app's callback function (viz,stripeResponseHandler) we do the following:
- We identify whether the response from Stripe is an success json or error json.
- If it's an error, we re-enable the submit button and call the error handler function(as shown below).
function stripeErrorDisplayHandler(response) { //Card field map - the keys are taken from error param values sent from stripe // and the values are error class name in the form. var errorMap = { number: 'card-number', cvc: 'card-cvc', exp_month: 'card-expiry-month', exp_year: 'card-expiry-year', } //Check if param exist in error if (response.error.param) { var paramClassName = errorMap[response.error.param] if (paramClassName) { //Display error in found class $('.' + paramClassName) .parents('.form-group') .find('.text-danger') .text(response.error.message) .show() } else { $('.alert-danger').show().text(response.error.message) } } else { $('.alert-danger').show().text(response.error.message) } }
- If it is success, we extract the response, append it to the form and submit it to our demo app server.
// Call back function for stripe response.
function stripeResponseHandler(status, response) {
if (response.error) {
// Re-enable the submit button
$('.submit-button').removeAttr("disabled");
// Show the errors on the form
stripeErrorDisplayHandler(response);
$('.subscribe_process').hide();
} else {
var form = $("#subscribe-form");
// Getting token from the response json.
var token = response['id'];
// insert the token into the form so it gets submitted to the server
if ($("input[name='stripeToken']").length == 1) {
$("input[name='stripeToken']").val(token);
} else {
form.append("<input type='hidden' name='stripeToken' value='" + token + "' />");
}
var options = {
// post-submit callback when error returns
error: subscribeErrorHandler,
// post-submit callback when success returns
success: subscribeResponseHandler,
complete: function() {
$('.subscribe_process').hide()
},
contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
dataType: 'json'
};
// Doing AJAX form submit to your server.
form.ajaxSubmit(options);
return false;
}
}
Now lets switch to the server side implementation
Setup the Chargebee client library
You have to download and import the client library of our choice. Then, configure the client library with your test site and its 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");
Create the subscription
We fetch the Stripe token and other information from the POST parameters submitted by our form and use the Create Subscription API to create the subscription in Chargebee.
/*
* 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.
*/
Result result = Subscription.create()
.planId(planId)
.customerEmail(req.getParameter("customer[email]"))
.customerFirstName(req.getParameter("customer[first_name]"))
.customerLastName(req.getParameter("customer[last_name]"))
.customerPhone(req.getParameter("customer[phone]"))
.cardTmpToken(req.getParameter("stripeToken")).request();
# 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 => _params["customer"],
:card => {
:tmp_token => _params['stripeToken']
}
})
/*
* Constructing a parameter array for create subscription api.
* It will have account information, the temporary token got from Stripe and
* plan details.
* For demo purpose a plan with id 'annual' is hard coded.
* Other params are obtained from request object.
* 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.
*
*/
$createSubscriptionParams = array(
"planId" => "basic",
"customer" => $_POST['customer'],
"card" => array(
"tmp_token" => $_POST['stripeToken']
));
/*
* 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);
Redirecting to a thank you page
So, what happens when a subscription is created successfully? Well, Chargebee returns a success response(as shown below) in json format which is wrapped as a 'result' class by the client library. In case of any error , Chargebee returns a error response(as shown below) which is wrapped and thrown as an exception by the client library.
- Success Response
{ "customer": { "auto_collection": "on", "billing_address": { "city": "Walnut", "country": "US", "line1": "340 S LEMON AVE #1537", "object": "billing_address", "state": "CA", "zip": "91789" }, "card_status": "no_card", "created_at": 1382683911, "email": "john@user.com", "first_name": "John", "id": "8avSkOLGCmUZJ", "last_name": "Wayne", "object": "customer", "phone": "+1-949-305-6900" }, "subscription": { "created_at": 1382683911, "due_invoices_count": 0, "id": "8avSkOLGCmUZJ", "object": "subscription", "plan_id": "basic", "plan_quantity": 1, "started_at": 1382683911, "status": "in_trial", "trial_end": 1385362311, "trial_start": 1382683911 } }
- Error Response
{ "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" }
In case of successful checkout we 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/thankyou.html\"}");
# Forwarding to thank you page after successful create subscription.
render json: {
:forward => "thankyou.html"
}
/*
* Forwarding to success page after successful create subscription in ChargeBee.
*/
$queryParameters = "name=" . urlencode($result->customer()->firstName) .
"&planId=" . urlencode($result->subscription()->planId);
$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.
- Stripe Tokenization Errors: Stripe returns error JSON for tokenization errors. We used the stripeErrorDisplayHandler(as shown below) to identify and display the error that occurred.
function stripeErrorDisplayHandler(response) { //Card field map - the keys are taken from error param values sent from stripe // and the values are error class name in the form. var errorMap = { number: 'card-number', cvc: 'card-cvc', exp_month: 'card-expiry-month', exp_year: 'card-expiry-year', } //Check if param exist in error if (response.error.param) { var paramClassName = errorMap[response.error.param] if (paramClassName) { //Display error in found class $('.' + paramClassName) .parents('.form-group') .find('.text-danger') .text(response.error.message) .show() } else { $('.alert-danger').show().text(response.error.message) } } else { $('.alert-danger').show().text(response.error.message) } }
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
Now that you're all set, why don't you test your integration with some test transactions. Here are some credit card numbers that you can use to test your application.
Visa | 4242 4242 4242 4242 |
Discover | 6011 1111 1111 1117 |
JCB | 3530 1113 3330 0000 |
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