Tokenizing a card in the browser
After your server has created an invoice for the customer's purchase, it must send back a payment page to let the customer choose how they want to pay. The page will generally contain a form for the customer to enter card details or select a card they have previously stored.
When the customer has submitted their card details, the API returns a card token string to your code. The token
uniquely identifies the set of details but it is essentially a "random" value that does not reveal any private information. Using a token to hide card details in this way is known as tokenizing the card. For security, Smart Router does not let you access the card details themselves, so you must use the token to identify the card and capture payments from it.
The code fragment below is a complete example that demonstrates several aspects of tokenizing a card, such as:
The sections that follow the example explain how this code is constructed, one aspect at a time.
<div class="container">
<form action="" method="POST" id="card-form">
<div class="block-group">
<input type="text" placeholder="John Smith" class="b75 block" id="cardholdername">
<input type="text" placeholder="10018" class="b25 block" id="cardholderzip">
</div>
<div class="block-group">
<div class="block" data-processout-input="cc-number"
data-processout-placeholder="4242 4242 4242 4242"></div>
<div class="block" data-processout-input="cc-exp"
data-processout-placeholder="MM / YY"></div>
<div class="block" data-processout-input="cc-cvc"
data-processout-placeholder="CVC"></div>
</div>
<div>
<input type="submit" id="paymentBtn">
</div>
<div id="errors"></div>
<div id="success"></div>
</form>
</div>
/*! PocketGrid 1.1.0
* Copyright 2013 Arnaud Leray
* MIT License
*/
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
/* Clearfix */
.block-group {
*zoom: 1;
}
.block-group:before, .block-group:after {
display: table;
content: "";
line-height: 0;
}
.block-group:after {
clear: both;
}
.block-group {
/* ul/li compatibility */
list-style-type: none;
padding: 0;
margin: 0;
}
/* Nested grid */
.block-group > .block-group {
clear: none;
float: left;
margin: 0 !important;
}
/* Default block */
.block {
float: left;
width: 100%;
}
.b75 {
width: 74%;
}
.b25 {
width: 25%;
margin-left: 1%;
}
html, body {
background: #ECEFF1;
font-size: 16px;
}
.container {
width: 100%;
max-width: 400px;
background: white;
margin: 3em auto;
padding: 1em;
border-radius: 4px;
box-shadow: 0 5px 7px rgba(50, 50, 93, 0.04), 0 1px 3px rgba(0, 0, 0, 0.03);
}
[data-processout-input], input {
border: 1px solid #ECEFF1;
border-radius: 4px;
box-shadow: 0 5px 7px rgba(50, 50, 93, 0.04), 0 1px 3px rgba(0, 0, 0, 0.03);
padding: 0.5em;
width: 100%;
margin-bottom: 1em;
font-size: 14px;
min-height: 2em;
}
::-webkit-input-placeholder { /* WebKit, Blink, Edge */
color: #ECEFF1;
}
:-moz-placeholder { /* Mozilla Firefox 4 to 18 */
color: #ECEFF1;
opacity: 1;
}
::-moz-placeholder { /* Mozilla Firefox 19+ */
color: #ECEFF1;
opacity: 1;
}
:-ms-input-placeholder { /* Internet Explorer 10-11 */
color: #ECEFF1;
}
[data-processout-input]:nth-child(1) {
width: 48%;
display: inline-block;
}
[data-processout-input]:nth-child(2) {
width: 25%;
display: inline-block;
margin-left: 1%;
}
[data-processout-input]:nth-child(3) {
width: 25%;
display: inline-block;
margin-left: 1%;
}
input[type="submit"] {
margin: 0;
box-shadow: 0 5px 7px rgba(50, 50, 93, 0.04), 0 1px 3px rgba(0, 0, 0, 0.03);
background: #3F51B5;
color: white;
border-radius: 4px;
border: 1px solid #303F9F;
}
#errors, #success {
margin-top: 1em;
text-align: center;
font-size: 0.9em;
color: #D84315;
}
#success {
color: #4CAF50;
}
document.addEventListener("DOMContentLoaded", function() {
var client = new ProcessOut.ProcessOut("test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x");
var formElement = document.getElementById("card-form");
style = {
fontSize: "14px",
"::placeholder": {
color: "#ECEFF1",
},
}
client.setupForm(formElement, {
style: style
}, processoutReadyHandler, function(err) {
console.log(err);
});
function processoutReadyHandler(form) {
form.getNumberField().on("focus", function(e) {
document.getElementById("errors").innerHTML = "";
});
form.getExpiryField().on("focus", function(e) {
document.getElementById("errors").innerHTML = "";
});
form.getCVCField().on("focus", function(e) {
document.getElementById("errors").innerHTML = "";
});
form.addEventListener("submit", function(e) {
e.preventDefault();
document.getElementById("paymentBtn").disabled = true;
// Let's tokenize the card
client.tokenize(form, {
name: document.getElementById("cardholdername").value,
contact: {
zip: document.getElementById("cardholderzip").value
}
}, function(token) {
document.getElementById("success").innerHTML = "Success! Your created card token is "+token;
document.getElementById("errors").innerHTML = "";
document.getElementById("paymentBtn").disabled = false;
}, function(err) {
document.getElementById("errors").innerHTML = err.message;
document.getElementById("success").innerHTML = "";
document.getElementById("paymentBtn").disabled = false;
});
return false;
});
}
});
Tokenization flow
Below is a sequence diagram that shows the flow of the tokenization process. The following sections explain the flow in more detail.
Setting up the form
The Smart Router API is designed to set up the card detail fields on the payment form automatically and receive data from them securely. Your own JavaScript code has no access to the customer's private data because the API directly gathers and encrypts this data to store in the ProcessOut vault. However, your code still has the flexibility to add styling to the secure fields and respond to events from them.
The following HTML sample shows the layout of a typical payment form. Note that the secure card fields are <div>
elements rather than <input>
fields. The API adds its own <iframe>
elements
inside the <div>
elements to receive the card details in accordance with our security standards .
Add a data-processout-input
attribute to each <div>
to specify which card field to generate. The possible values of this attribute are:
cc-number
: the main card number (16 digits)cc-exp-month
: the month of the expiration datecc-exp-year
: the year of the expiration datecc-exp
: the full expiration date in mm/yy formatcc-cvc
: the CVC number
Use standard HTML <input>
fields for other non-secure form elements such as the Submit button.
<form action="/your-endpoint" method="POST" id="card-form">
<div data-processout-input="cc-number"
data-processout-placeholder="4242 4242 4242 4242"></div>
<div data-processout-input="cc-exp-month"
data-processout-placeholder="Expiration month"></div>
<div data-processout-input="cc-exp-year"
data-processout-placeholder="Expiration year"></div>
<!-- You can also use a single combined field for the expiration date: -->
<div data-processout-input="cc-exp"
data-processout-placeholder="Expiration date (mm / yy)"></div>
<div data-processout-input="cc-cvc"
data-processout-placeholder="CVC"></div>
<input type="submit" class="submit" value="Submit Payment">
</form>
In your page's JavaScript, use the ProcessOut.setupForm()
function to set up the form with the secure fields. Firstly, load the processout.js
library into your page. Then, add the following code in a separate <script>
element. Placing the code in the DOMContentLoaded
event listener ensures that it will run as soon as the page has finished loading. Note that the processoutReadyHandler
function is a callback that runs when the form is correctly set up. It is described in the next section.
document.addEventListener("DOMContentLoaded", function() {
var client = new ProcessOut.ProcessOut(
"test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x");
var formElement = document.getElementById("card-form");
client.setupForm(
formElement, // The <form> element in your page.
{
// Form options. You can also add other options such as
// styling (see "Styling the form" below).
requireCVC: true,
// Form options. By default CVC field will be auto
// focused when expiry field is filled. You can turn
// off this default behavior. Note that this auto focus functionality
// is not supported in mobile and desktop Safari.
expiryAutoNext: false,
// By default expiry field will be focused when card number if filled.
// You can turn off this behavior. Note that this auto focus functionality
// is not supported in mobile and desktop Safari.
cardNumberAutoNext: false,
},
processoutReadyHandler, // Callback for successful setup.
// Error callback.
function(err) {
console.log("Whoops, couldn't setup the form: "+err);
}
);
});
The options available for ProcessOut.makeCardPayment()
are shown in the table below.
Option | Description |
---|---|
require_cvc | If true then the user must enter a CVC before submitting the form. |
style | Object containing CSS styles to apply to the form (see Styling the form below). |
Generating the card token
ProcessOut.setupForm()
calls a callback function (named processoutReadyHandler
in the example above) after it has set up the card fields. The CardForm
object that you use to tokenize the card is passed to processoutReadyHandler
as a parameter.
In the example below, processoutReadyHandler
adds an event listener that responds when the form is submitted. This listener calls CardForm.tokenize()
to generate the token. CardForm.tokenize()
itself also has a callback that receives the token string as a parameter and adds it into the form as a hidden field.
function processoutReadyHandler(form) {
// The form is fully loaded at this point.
formElement.addEventListener("submit", function(e) {
// Cancel the default behavior of the Submit button.
e.preventDefault();
// Blocking the form while performing the tokenization may also
// avoid race conditions (eg, when
// the user double-clicks the button).
document.getElementById("paymentBtn").disabled = true;
// Tokenize the card
client.tokenize(form, {
// You can also optionally send other information about
// the cardholder.
name: document.getElementById("cardholdername").value,
contact: {
zip: document.getElementById("cardholderzip").value,
// Available contact fields:
// address1, address2, city, state, country_code, zip
},
// You can offer the customer a preferred scheme
// to pay on, say, if the customer's card supports
// co-schemes such as carte bancaire.
preferred_scheme: "carte bancaire"
}, function(token, card) {
var field = document.createElement("input");
field.type = "hidden";
field.name = "token";
field.value = token;
// Re-enable the button
document.getElementById("paymentBtn").disabled = false;
// Add the token input so that it gets sent back to the server.
formElement.appendChild(field);
// Finally, submit the form.
formElement.submit();
}, function(err) {
// As well as the message, the error object contains
// a code value to denote the specific type of error
// (see the next section for details).
alert(err.message);
// Re-enable the button
document.getElementById("paymentBtn").disabled = false;
});
return false;
});
}
Handling errors
The examples so far handle errors by simply printing out the error message stored in the err.message
attribute. The error object also has a code
attribute that identifies the type of error. The most common values for err.code
are:
card.declined
: The card was declined by the PSPcard.expired
: The card is past its expiration datecard.invalid
: The card is invalidcard.invalid-number
: The supplied card number is invalidcard.invalid-date
: The supplied expiration date is invalidcard.invalid-month
: The month of the supplied expiration date is invalidcard.invalid-year
: The year of the supplied expiration date is invalidcard.invalid-cvc
: The supplied CVC number is invalid
If you use a switch
statement to handle these errors (as shown below) then you should include a default
case because other values for err.code
can sometimes occur.
client.tokenize(form, { ... }, function(token) { ... }, function(err) {
switch (err.code) {
case "card.declined":
// Code to handle a declined card
break;
case "card.expired":
// Code to handle an expired card
break;
...
default:
// Code to handle uncommon errors
}
});
Styling the form
You can add a style
attribute to the options
parameter of ProcessOut.setupForm()
. The style
object lets you supply a limited subset of CSS styles for the secure form fields.
var style = {
fontSize: "14px",
"::placeholder": {
color: "#ECEFF1"
}
};
client.setupForm(formElement, {
style: style
}, processoutReadyHandler, function(err) { ... }
);
By default, the secure fields are generated with very little styling. The background for the fields is transparent and there is no margin or padding. This lets you manage these CSS properties using the parent elements in your form (ie, the <div>
elements from the example given above).
You can apply the following CSS properties to the secure form fields using the style
option:
color
direction
- Setting this to
rtl
will applydirection: rtl;
to the CVC secure field, and will applytext-align: right;
to all the secure fields. This is to ensure a consistent formatting of the card number and expiry fields when utilising this RTL support.
- Setting this to
fontFamily
fontSize
fontSizeAdjust
fontStretch
fontSmoothing
fontStyle
fontVariant
fontWeight
fontHeight
lineHeight
textShadow
textTransform
textDecoration
transition
You can also use the :hover
and :focus
pseudo-classes and the ::placeholder
and ::selection
pseudo-elements by adding them as sub-objects of the style
object, as shown in the example.
ProcessOut.js
also supports custom fonts for enterprise users. Contact us if you want to use your own fonts and branding for the secure form fields.
Handling form events
The form
object passed to processoutReadyHander
lets you access the secure form fields using the following
functions:
CardForm.getNumberField()
CardForm.getCVCField()
CardForm.getExpiryField()
CardForm.getExpiryMonthField()
CardForm.getExpiryYearField()
For security reasons, the objects returned by these functions give you only limited access to the fields. However, you can add event listeners to them using the on()
function, as shown below. The listeners let you respond to events for the secure fields in the usual way.
client.setupForm(formElement, {},
processoutReadyHander,
function(err) { ... }
);
function processoutReadyHander(form) {
form.getNumberField().on("click", function(e) {
console.log(e);
});
form.getExpiryField().on("click", function(e) {
console.log(e);
});
form.getCVCField().on("click", function(e) {
console.log(e);
});
}
Each event will include useful information about the current state of the card form.
Events can be used to dynamically build a card scheme display or dynamically validating the card:
function processoutReadyHander(form) {
form.getNumberField().on("click", function(e) {
console.log(e);
/*
Will print the following structure:
{
card_number_length: 16
max_card_number_length: 19,
min_card_number_length: 12,
potentially_valid: true,
valid: false,
schemes: ["visa", "carte bancaire"]
}
*/
});
}
Event structure detail
Field | Description | Type | Example value |
---|---|---|---|
card_number_length | Current length of the card field | Integer | 16 |
max_card_number_length | Maximum length of the card according to its BIN | Integer | 19 |
min_card_number_length | Minimum length of the card according to its BIN | Integer | 12 |
potentially_valid | true if the current value can match a valid card number once completed | Boolean | true |
valid | true when the card passes the Luhn check | Boolean | true |
schemes | List of possible schemes according to the card BIN | List | ["visa", "carte bancaire"] |
Next steps
Once you have generated the card token successfully, the next step is to use it to capture a payment
from the customer. You may also be interested in saving a token to capture future payments.
Updated 11 months ago