Spreedly Iframe Payment Form
Documentation for version 0.10
There are a variety of ways to send payment data to Spreedly. However, to limit your PCI compliance scope while still retaining full control of the UI, the recommended approach is to use an embedded iFrame form. This form, served by Spreedly, collects the sensitive portions of your customers’ payment data and stores it in your Spreedly vault without it ever touching your server environment.
Since browsers implement strict security guarantees that prevent cross domain pages from leaking data, the interaction between the host page (your order or payment page) and the Spreedly iFrame fields (a number input field and a cvv input field) must pass through a specific and pre-determined channel. This channel is managed by the Spreedly iFrame Payment Form and exposes a set of hooks that allow you to style the form to fit your UI and extract tokenized payment method.
Before you begin
We recommend avoiding a complex checkout form. Complex checkout forms are problematic on a variety of fronts: They are difficult to implement, have a high abandonment rate, and make error handling difficult. Make the payment portion of your checkout process as simple as possible.
The first step is to integrate the iFrame payment form into your master payment form.
Master form
For the purposes of this guide, the “master form” will represent the form which you build and place on your checkout page.
The iFrame payment form only provides fields for the user’s card number and CVV (these are the only PCI-scoped fields). Any other information you wish to collect as part of the payment process must be defined in the master form. For this example we’re assuming the collection of the following fields which, in addition to the card number, are required by Spreedly for a valid payment method:
- Full name
- Card expiration month and year
Create an HTML page containing a form like so:
<form id="payment-form" action="your-action.html" onsubmit='submitPaymentForm(); return false;'>
<label for="full_name">Name</label>
<input type="text" id="full_name" name="full_name"><br/>
<label>Credit Card Number</label>
<div id="spreedly-number" style="width:225px; height:35px; border: 2px solid"></div><br/>
<label for="month">Expiration Date</label>
<input type="text" id="month" name="month" maxlength="2">
<input type="text" id="year" name="year" maxlength="4"><br/>
<label>CVV</label>
<div id="spreedly-cvv" style="width:60px; height:35px; border: 2px solid "></div><br/>
<input id="submit-button" type="submit" value="Pay Now" disabled>
<input type="hidden" name="payment_method_token" id="payment_method_token">
<script src="https://core.spreedly.com/iframe/iframe-0.10.min.js" id="spreedly-iframe"
data-environment-key="YourEnvironmentKeyHere"
data-number-id="spreedly-number"
data-cvv-id="spreedly-cvv">
</script>
</form>
Important information:
- The master form has a custom
onsubmit
handler that will manage the submission process (its implementation is covered later). - The master form will have input elements for all the non-sensitive data you wish to collect as part of the checkout process (here the first and last name, and card expiration month and year).
- The master form should also have a hidden input (
<input type="hidden" name="payment_method_token" id="payment_method_token" />
) that will contain the token representing the user’s payment method in the Spreedly vault once it’s been tokenized. - Although the payment frame fields will be submitted directly to Spreedly, your master form should have an
action
that submits it directly to your server environment. Since the user’s payment method will have first been tokenized by Spreedly, it is safe to do so. - Embedded within the master form is the Spreedly iFrame payment form, which is composed of two separate input fields, each placed in its own iframe.
- There is some extra HTML and default styling present in this example as well. None of it is mandatory, but read further before ripping it out.
Within the master form HTML, you need to include a script to pull in Spreedly iFrame Payment Form.
- Iframe payment form requires that the script tag has and a specific id (
id="spreedly-iframe"
) and a specific src (src="https://core.spreedly.com/iframe/iframe-0.10.min.js""
) - Set the
data-environment-key
property to your Spreedly environment key. More information about environment key can be found here: environment key - Set the
data-number-id
to be the id of the div you wish to contain the number iframe. - Set the
data-cvv-id
to be the id of the div you wish to contain the cvv iframe.
With this in place, you should be able to view the master form in a browser. It will look very bare and disjointed, but you should be able to see all fields, including the credit card number and CVV fields.
Submitting the form at this point will not work, since the form’s
onsubmit
handler is not yet defined and you have not yet registered to receive any iFrame events.
Ready
Spreedly iFrame requires code to load in mulitple iFrames. When the iFrames are loaded, a ‘ready’ event is triggered. In order to ensure that a form cannot be submitted prior to the iFrames loading, we recommend the submit button be disabled until the iFrame is ready.
The following event handler will remove the disabled attribute when everything is completely loaded.
Spreedly.on('ready', function() {
document.getElementById("submit-button").removeAttribute('disabled');
});
Tokenize Credit Card
The payment method token must be created before the master form is submitted to keep sensitive payment method information out of your server environment. This sequence should be managed by the custom form onsubmit handler defined in the master form HTML.
Form onsubmit handler
Create an onsubmit handler that will be responsible for the following:
- Create a hash of additional payment information: set additional parameters on the payment method beyond what the user has entered for the card number and CVV. Most often these values will be present in the master form. The following are required by a payment method to pass validation:
full_name
: Card holder’s full namemonth
: Card’s expiration month as a two digit number from 1-12year
: Card’s expiration year as a four digit number
- Call to tokenize the payment method.
This function can live anywhere as it’s defined in the global scope (for the purpose of this example).
function submitPaymentForm() {
// These are the fields whose values we want to transfer *from* the
// master form *to* the payment frame form.
var paymentMethodFields = ['full_name', 'month', 'year'];
options = {};
for(var i = 0; i < paymentMethodFields.length; i++) {
var field = paymentMethodFields[i];
options[field] = document.getElementById(field).value;
}
Spreedly.tokenizeCreditCard( options );
}
Payment method event
The Spreedly iFrame exposes a set of events. You can register to be notified when certain events have occurred. Registering to the paymentMethod
event will notified that the payment method has been tokenized and you are free to submit the master form.
Register a paymentMethod
callback that takes two arguments: the payment method token and the full JSON payment method representation. Within the callback set the payment_method_token
field of your master form and submit the form.
Spreedly.on('paymentMethod', function(token, pmData) {
var tokenField = document.getElementById("payment_method_token");
tokenField.setAttribute("value", token);
var masterForm = document.getElementById('payment-form');
masterForm.submit();
});
Try loading the master form in your browser, filling out all fields with valid values (see here for test card numbers), and pressing the submit button. The form should successfully submit the payment method to Spreedly and result in the payment method token being submitted to your server environment.
If you don’t see this behavior, open the Javascript console in your browser (often under “Developer tools”) to see any errors or further inspect the request/response to Spreedly.
Error handling
If an error occurs in the payment frame, at any point in the flow, an errors event will be triggered. To respond to errors, simply register an errors
event handler that takes an array of error objects in the form:
[
{
"attribute":"full_name",
"key":"errors.blank",
"message":"First name can't be blank"
}
]
Errors will always have a key
, which you can use to filter on, and a message
which is a human readable description of the error. You can reflect errors that have an attribute
in the form alongside the affected field. Errors that do not have an attribute
are not field-specific.
Spreedly.on('errors', function(errors) {
// Just here for debugging. Remove this in prod
console.log(errors);
for(var i = 0; i < errors.length; i++) {
console.log(errors[i]["key"] + ": " + errors[i]["message"]);
// set error style on invalid fields
}
});
With the error handler in place, submit an empty payment form. You should receive an array of errors for each invalid field. Here is a list of possible errors:
Key | Description |
---|---|
errors.account_inactive |
A non-test card number was submitted against a test (unpaid) account. This can also occur when an invalid card number is submitted while testing as Spreedly assumes all unknown card numbers are real. |
errors.environment_key_parameter_required |
An environment was not specified. Specify one when creating the Spreedly payment frame: new Spreedly.PaymentFrame("your_environment_key"); |
errors.invalid_environment_key_parameter |
The specified environment key is not valid. Check the supplied environment key value and re-submit. |
errors.blank |
The payment method field specified by the attribute property of this error was submitted without a value, which is required by Spreedly. Display an appropriate error message and prompt the user to fill in the specified fields. |
errors.invalid |
The payment method field specified by the attribute property of this error was submitted with an invalid value (e.g., a month value of “13”). Display an appropriate error message and prompt the user to correct the specified fields. |
errors.blank_card_type |
The credit card number was invalid; we were unable to determine the card type. |
errors.expired |
The payment method month and year indicates that it is expired. Display an appropriate error message and prompt the user to enter a non-expired payment method. |
errors.unknown_referrer |
Spreedly could not determine the source (referring URL) of the form submission. A referring URL is required to identify MITM attacks. |
errors.invalid_referrer |
The form submission did not originate from the Spreedly payment frame and has been rejected. This is a protection against MITM attempts. |
Styling
The fields that live inside the iFrame do not have access to the styling defined for the master form in the host page. To allow you to style the number and cvv fields, the iFrame library has defined a call that let you apply CSS styles. Styling can occur anytime after the ready
event.
Spreedly.setStyle(selector, styles)
: Apply the given CSS style to the given field in the iFrame.
setStyle
requires you to set the selector to 'number’ or 'cvv’ since those are the only 2 fields contained in the iFrame.
We can add styling to the ready event like this:
Spreedly.on('ready', function() {
document.getElementById("submit-button").removeAttribute('disabled');
Spreedly.setStyle('cvv', "width:60px; height:35px; font-size: 20px; text-align: center;");
Spreedly.setStyle('number', "width:225px; height:35px; font-size: 20px; text-align: center");
});
Reload the master form in your browser to see the styles applied to the payment frame fields.
Validation
There are many situations where it is useful to know details of the card information the user has entered in the payment form. For instance, if you’re doing real-time validation or are displaying an icon of the card type as the user types, you need to know basic details about the user input without seeing the actual values. The iFrame library provides this functionality via the input
callback which accepts a hash of metadata representing the user’s input.
Spreedly.on('input', function( data ) {
if (data["validCvv"]){
Spreedly.setStyle('cvv', "background-color: #CDFFE6;")
} else {
Spreedly.setStyle('cvv', "background-color: #FFFFFF;")
}
if (data["validNumber"]){
Spreedly.setStyle('number', "background-color: #CDFFE6;")
} else {
Spreedly.setStyle('number', "background-color: #FFFFFF;")
}
});
The input
event is fired on every keystroke and has the following keys:
validNumber
: the card number passes luhn validation and is the proper length for its type.validCvv
: the cvv is numeric and the proper length for its type.numberLength
: The length of the user input in the card number field.cvvLength
: The length of the user input in the verification value (CVV) field.cardType
: The type of card entered (from this list of Spreedly supported card types).nil
if the type of card could not be determined.luhnValid
:true
if the entered card number conforms to the Luhn algorithm.false
otherwise. While the Luhn algorithm does not guarantee a valid card number, it is a good way to quickly check for typos. The only way to guarantee a valid card is to run an auth or use theverify
operation.
It can also be useful to operate on this input metadata before form submission. In your form onsubmit handler, invoke validation
instead of submit
. Then, in the validation
event handler, perform your custom validation logic and, only if validation passes, tokenize the payment method:
function submitPaymentForm() {
// trigger validate to get back metadata
Spreedly.validate();
}
Spreedly.on('validation', function(inputProperties) {
if(!inputProperties["validNumber"]) {
// prompt user to correct input
} else {
var paymentMethodFields = ['full_name', 'month', 'year']
var options = {};
for(var i = 0; i < paymentMethodFields.length; i++) {
var field = paymentMethodFields[i];
options[field] = document.getElementById(field).value;
}
console.log("calling tokenize")
Spreedly.tokenizeCreditCard( options );
}
});
Since communicating with the iFrame is asynchronous, you must operate on the input metadata in a separate validation
event helper which will then become responsible for submitting the payment frame.
Field events
The fieldEvent is triggered when the number or cvv fields experience a focus, blur, mouseover, or mouseout event. These events can be handled by listening to the fieldEvent
Spreedly.on('fieldEvent', function(name, event){
// remove logging before pushing to prod
console.log("The " + name + " field just had a " + event + " event.")
})
Additional payment information
It is quite common to collect more than just the required fields when storing a payment method. If you wish to provide more detailed payment information, such as billing address, email etc…, you can simply add the key, value pair to the options argument of Spreedly.tokenizeCreditCard
The following are a list of additional properties you can set:
first_name
last_name
full_name
month
year
email
address1
address2
city
state
zip
country
phone_number
company
shipping_address1
shipping_address2
shipping_city
shipping_state
shipping_zip
shipping_country
shipping_phone_number
Reset and Reload
Spreedly iFrame payment form offers both resetFields
and reload
. resetFields
simply removes the any values from the number
and cvv
fields. reload
removes all listeners and reloads all code for the iFrames.
iFrame API Reference
Spreedly.tokenizeCreditCard()
- causes the iframe to request/return a tokenSpreedly.resetFields()
- clears the iframe fieldsSpreedly.validate()
- causes the iFrame to report the state of the inputsSpreedly.setStyle(id, style)
- sets a style on a field- id - must be either 'number’ or 'cvv’
Spreedly.reload()
- removes all listeners, reloads the iFrame payment form.
Events
Handlers can be registered to these events. They are not required.
ready
- triggered when the iframes are loaded and ready for styling.input
- triggered when a user inputs values into an iframe field. The hash passed to this function will contain the following:- cardType - 'visa’,'american_express’,'discover’,'jcb’,'master’,'diners_club’,'dankort’
- validCreditCardNumber - boolean which represents if the card number is the proper length and passes luhn validation
- validCvv - boolean which represents if the cvv is valid and the proper length for the card type
validation
- triggered when the input properties have been retrieved. Triggered when you callSpreedly.validate()
paymentMethod
- triggered when a payment method token has been successfully createderrors
- triggered when any errors are returned- errors - an array of errors from the iframe. All errors have a
key
andmessage
property.
- errors - an array of errors from the iframe. All errors have a
fieldEvent
- triggered when certain events occur in iframe fields. Passes the name of the field experiencing the event and what type of event occurred.- name - name of the target field, i.e. 'cvv’, 'cardNumber’
- notification - 'focus’, 'blur’, 'mouseover’, 'mouseout’
Browser compatibility
iFrame v0.10
is compatible with the following browser versions:
- Internet Explorer 8.0 and above. Support for IE8 will be discontinued January 2016.
- Google Chrome 30 and above
- Firefox 30 and above
- Safari 6 and above
- iOS Safari 3.2 and above
Any browser not supporting TLS 1.2 will no longer be supported after March 2016
Versioning
These docs are for iframe v0.10.
The current version of the iFrame payment form is v1
and is available at:
https://core.spreedly.com/iframe/iframe-1.min.js
The iframe library is versioned with a major and minor numerical, e.g., 1
. Once the major version is 1 or greater, backwards-compatible new features will cause the minor version number to increment. Backwards-incompatible new features will cause the major version number to increment. While the major version is 0, minor version increments could contain backward breaking changes.
Any critical bug fixes or security updates will be automatically deployed as the current version number, thus automatically allowing you to run a secure, stable version. Therefore, please do not store a copy of the JavaScript library on your servers; instead, always reference the version hosted by Spreedly.
Iframe v0.10 introduces breaking changes from v0.5. Documentation for version 0.5 is located at iFrame payment form v0.5
Security
In order to achieve the highest levels of PCI-DSS v3 compliance with Spreedly Express or iFrame, you need to disable the creation of payment methods via the direct API. After confirming you are not using the XML, JSON, JSONP, CORS or transparent direct methods of adding a payment method, enable the “Spreedly iFrame or Express Only option in the Environment Settings page of your environment.
This prevents direct API submission of payment methods which is required to detect malicious attacks and, in general, enforce adherence to the PCI standard.