ravelinjs
ravelinjs is a JavaScript library for the browser to augment your integration
with:
- an identifier for the customer's browser to be attached to an order (core);
- simple page events like loading, pasting and resizing (track); and
- cardholder data encrypted for transmission through your server (encrypt).
Gathering these values accurately, and ensuring they are made available to
Ravelin through our API or by calls made directly from this SDK, is critical to
a successful Ravelin integration.
Please feel welcome to create issues or submit pull requests on the
project. The Contribution
Guidelines
detail how to write and test code for ravelinjs.
Note that this documentation is for version 1 of ravelinjs. For version 0,
please see its usage guide,
reference
and source.
Table of Contents
Quickstart
Add https://*.ravelin.click
to your site's Content-Security-Policy
connect-src
directive. Get a copy of
ravelin-core+track+encrypt+promise.min.js on Github releases and
instantiate your Ravelin instance on the page:
<script src="ravelin-core+track+encrypt+promise.min.js"></script>
<script>var ravelin = new Ravelin({key: 'publishable_key_...'})</script>
If you have a build system, you can instead install ravelinjs with
npm using npm i ravelinjs@1
and require or
import Ravelin for instantiating:
import Ravelin from 'ravelinjs/core+track+encrypt+promise';
const Ravelin = require('ravelinjs/core+track+encrypt+promise');
var ravelin = new Ravelin({key: 'publishable_key_...'});
This will set the ravelinDeviceId
cookie on your domain, send a page-load
event, and then allow you to call:
ravelin.core.id().then(function(id) { ... })
to get the deviceId.ravelin.encrypt.card({pan: "4111 ..."})
to encrypt cardholder data to be sent
to Ravelin.ravelin.track.load()
to track a page load.
If you are wanting to track paste events then lastly add a data-rvn-pan
attribute to any inputs the user types a credit/debit card number into, and a
data-rvn-sensitive
to or around any elements you don't want Ravelin to report
any content from.
→ Read on for more details.
Bundles
The quickstart suggests using ravelin-core+track+encrypt+promise.min.js
which
contains all functionality offered by ravelinjs and is therefore the easiest to
get started with but also the largest file. If you are not using all of the
functionality of ravelinjs you can choose a bundle with only the components you
need.
The components are:
- core: API and error-reporting functionality used by all bundles, and Basic
device identification with
ravelin.core.id()
or a ravelinDeviceId
cookie. - encrypt: Cardholder data encryption with
ravelin.encrypt.card()
. - track: Automatically send page-load, resize and paste events, or manually
with
ravelin.track.load()
. - promise: Provide a fallback Promise polyfill required for Internet
Explorer support. Optional if you already have your own polyfill or do not
want to support any version of Internet Explorer.
The release files indicate which components they include using a
+component
naming convention. For example, ravelin-core+track.min.js
contains only the core and track components and so cannot be used to encrypt
cards and doesn't guarantee Internet Explorer compatibility.
npm
If you have a JavaScript build system and would prefer to include ravelinjs
using it, you can install ravelinjs from
npm with:
npm install ravelinjs@1
You can then import the desired bundle within the ravelinjs library. For
example, to load the core+track bundle using require
is:
var Ravelin = require('ravelinjs/core+track');
Or to load card encryption with ES6 imports is:
import Ravelin from 'ravelinjs/core+encrypt';
The bundles published to npm are in Universal Module Definition format.
Content-Security-Policy
RavelinJS will send track events and error reports back to the Ravelin API as
configured in the api
initialisation property, or inferred from your API key.
If your site is configured with a Content-Security-Policy, be sure to add the
API to the connect-src
directive:
Content-Security-Policy: connect-src 'self' https://*.ravelin.click;
Script Integrity
If you are including a ravelin bundle directly on your page, rather than in your
build system, we recommended setting the integrity
attribute on the script tag
to the corresponding value from the integrity file of the release. For example,
if the integrity file reads:
sha384-8de9e022e2f67e2072bb114e670d2fb37cab8eaf81616bcc3951087aa473e62a8b9fcc4c780a8d8d09df55c8b63bfd7c ravelin-1.0.0-rc1-core+promise.js
then your HTML becomes:
<script src="ravelin-1.0.0-rc1-core+promise.js" integrity="sha384-8de9e022e2f67e2072bb114e670d2fb37cab8eaf81616bcc3951087aa473e62a8b9fcc4c780a8d8d09df55c8b63bfd7c">
If the integrity file is next to the script in question, you can validate the
contents using:
sed s/^sha384-// integrity | shasum -c
Browser Compatibility
RavelinJS v1.0.0 is tested on IE8-11 and all newer
browsers. We plan to drop support for IE8-IE10 soon, so
please contact us if you still support these browsers.
A Promise/A+ polyfill is required for Internet Explorer support. If you do not
have one, or are not sure, then use a +promise ravelinjs bundle.
Card encryption uses window.crypto where available, and otherwise falls back to
a pseudo-random number generator which collects user movements and keypresses as
a source of entropy. If insufficient events have been collected before
encryption is attempted, an Error is thrown to prevent insecure transmission of
cardholder data.
Reference
var ravelin = new Ravelin({cfg: object})
During your page load you need to instantiate your Ravelin
instance:
var rav = new Ravelin({
key: 'publishable_key_...',
});
ravelin.core.id(): Promise<string>
ravelin.core.id
returns a Promise which resolves to the device ID
string. This will eventually match the ravelinDeviceId
cookie. Your goal is to
make a server-side API request to Ravelin where you send the customer's order
and device - using this deviceId - together in a v2/checkout
or v2/order API request, or the customer and device in a
v2/connect API request.
HTML example:
<form action=pay>
<input type=hidden name=device-id id=rav-device-id>
</form>
<script src="ravelin-core.min.js">
<script>
var ravelin = new Ravelin({
key: 'publishable_key_...'
});
ravelin.core.id().then(function(deviceId) {
document.getElementById('rav-device-id').value = deviceId;
});
</script>
If you are using a modern bundler and transpiler you can declare:
const deviceId = await ravelin.core.id();
Server-side example:
var card = JSON.parse(form.getValue('card-cipher'));
var action = fetch('https://api.ravelin.com/v2/order?score=true', {
method: 'POST',
headers: {...},
body: JSON.stringify({
timestamp: (new Date).getTime(),
customerId: customerId,
order: {...},
device: {
deviceId: form.getValue('device-id'),
userAgent: req.header('User-Agent'),
ipAddress: req.ip,
language: req.header('Accept-Language'),
}
})
});
Device ID Format
The device ID in the ravelinDeviceId
cookie or returned by ravelin.core.id()
should be treated as an opaque string. Do not attempt to parse or validate the
format of the ID as we may change it without warning in the future.
ravelin.encrypt.card(card: object): object
ravelin.encrypt.card
returns an object describing the encrypted form of
cardholder data for use with Ravelin's client-side
encryption.
This object can then be sent via your server to Ravelin without increasing the
scope of PCI compliance required of your server. The object can be used directly
as a paymentMethod in a v2/checkout,
v2/paymentmethod or v2/connect request,
for example.
Encrypting cardholder data is only necessary for non-PCI compliant merchants (PCI
SAQ-A or SAQ-AEP merchants) who are otherwise unable to provide cardholder data
(including a valid
instrumentId
)
to Ravelin when scoring an order.
The full set of fields are:
var cipher = ravelin.encrypt.card({
pan: '4111 1111 1111 1111',
year: '2020',
month: '1',
nameOnCard: 'Tom Johnson'
});
HTML example:
<form action=pay id=payment-form>
Card Number: <input name=pan>
Name: <input name=name>
Expiry Year: <input name=year>
Expiry Month: <input name=month>
<input type=hidden name=card-cipher>
</form>
<script src="ravelin-core+encrypt.min.js">
<script>
var ravelin = new Ravelin({
key: 'publishable_key_...',
rsaKey: '0|...'
});
var form = document.getElementById('payment-form');
form.onsubmit = function() {
var cipher = ravelin.encrypt.card({
pan: form['pan'].value,
year: form['year'].value,
month: form['month'].value,
nameOnCard: form['name'].value
});
form['card-cipher'].value = JSON.stringify(cipher);
form['pan'].value = '';
};
</script>
Server-side usage example:
var card = JSON.parse(form.getValue('card-cipher'));
var action = fetch('https://api.ravelin.com/v2/checkout?score=true', {
method: 'POST',
headers: {...},
body: JSON.stringify({
timestamp: (new Date).getTime(),
customer: {...},
order: {...},
transaction: {...},
paymentMethod: card,
device: {...}
})
});
Note that browsers which do not support
window.crypto
(including IE8-IE10) rely on
a pseudo-random number generator based on collecting user events from the page
and that if this generator has not collected enough events it may throw an
exception when trying to encrypt.
ravelin.track.load()
Send a page-load event. This is automatically triggered when Ravelin is
instantiated, but should be invoked manually after page navigation in a
single-page app. To ensure the correct page title is collected, call after the
page content has loaded - so the Window popstate event may be too
early.
ravelin.track.event(name, [props])
Send a named event to attach to the session, with optional descriptive
properties. Most event names use "UPPER_SNAKE_CASE" but the most important thing
is to have consistency between your browser and mobile applications where they
have common events. Returns a Promise that resolves once the event has been
sent.
ravelin.track.paste(event: ClipboardEvent)
Send a paste event to Ravelin. This is done automatically if the paste happens
in the same frame Ravelin is instantiated - except on IE8 which does not support
paste-event listening at the document level.
To correctly identify the paste contents you should annotate your forms with
attributes:
data-rvn-pan
if the user enters a credit-card number into that input; ordata-rvn-sensitive
if no values should be shared in the event back to Ravelin.
Note: It is possible to override these attributes by providing custom classifyPaste
logic in your Ravelin
instance. See Reference.
The paste event contains information about where the paste happened and
approximate shape of the paste content. For example, if a user pastes "h3ll0,
wor1d." into a field, Ravelin will receive "X0XX0, XXX0X.". However, if the
pasted content is an <input type=password>
, a <input data-rvn-sensitive>
or
a child of any <div data-rvn-sensitive>
(if using the default attributes) field we will not include any form of
pasted value - only that a paste event occurred.
Vendored Code
This library would not have been possible without the stellar works upon which
it relies:
Upgrading
Note that the format of the deviceId was changed in v1 to include a "rjs-"
prefix. If you do any validation or parsing that checks for a particular
format of the deviceId, please remove this logic and instead treat the deviceId as
an opaque string.
Upgrading to ravelinjs v1 from ravelinjs v0
If you are using RavelinJS v0 from a script or loaded via npm then equivalent
functionality is now covered by bundles with the core+track+encrypt components.
Please review which components you need in the bundles and complete
the quickstart setup instructions. You can now remove cdn.ravelin.net from
your Content-Security-Policy and make the following substitutions to complete
the upgrade:
ravelinjs.setFallbackJS(src)
→ Removed.ravelinjs.setCookieDomain(domain)
→ Set during instantiation in new Ravelin({cookieDomain: 'c.com'})
.ravelinjs.setPublicAPIKey(apiKey)
→ Set during instantiation in new Ravelin({key: apiKey})
.ravelinjs.setRSAKey(rawPubKey)
→ Set during instantiation in new Ravelin({rsaKey: rawPubKey})
.ravelinjs.setCustomerId(customerId)
→ Removed.ravelinjs.setTempCustomer
→ Removed.ravelinjs.encrypt(card)
→ JSON.stringify(ravelin.encrypt.card(card))
ravelinjs.encryptAsObject(card)
→ ravelin.encrypt.card(card)
ravelinjs.track(eventName, meta)
→ Removed.ravelinjs.trackPage(meta)
→ ravelin.track.load()
ravelinjs.trackLogout(meta)
→ Removed.ravelinjs.trackFingerprint(customer)
→ Removed. This method implemented some
privacy-insensitive browser fingerprinting that Ravelin no longer wishes to be
part of. Instead, follow the instructions of
ravelin.core.id()
to send the device via your server.ravelinjs.setOrderId(orderId)
→ Removed.
Upgrading to ravelinjs v1 from cdn.ravelin.net script snippet
If you previously used a snippet such as
(function(r,a,v,e,l,i,n){r[l]=r[l]||function(){(r[l].q=r[l].q||[]).push(arguments)};i=a.createElement(v);i.async=i.defer=1;i.src=e;a.body.appendChild(i)})(window,document,'script','https://cdn.ravelin.net/ravelin-beta.min.js','ravelin');
or
(function r(a,v,e,l,i,n){a[e]=a[e]||function(){(a[e].q=a[e].q||[]).push(arguments)};n=v.createElement("script");n.async=n.defer=1;n.src=l;if(i)n.onerror=function(){r(a,v,e,i)};v.body.appendChild(n)})(window,document,"ravelin","https://cdn.ravelin.net/js/rvn-beta.min.js","/rvn-lite.min.js")
then the functionality you were using is covered by bundles with the core+track
components. After following the quickstart instructions you can remove
cdn.ravelin.net from your Content-Security-Policy, and make the following
substitutions to complete the upgrade:
ravelin('setApiKey', 'k')
→ Set during instantiation in new Ravelin({key: 'k'})
.ravelin('setCookieDomain', 'c')
→ Set during instantiation in new Ravelin({cookieDomain: 'c.com'})
.ravelin('track')
→ Removed.ravelin('trackPage')
→ ravelin.track.load()
is now
called when Ravelin is instantiated, but you can call this method again when
the user navigates if you have a single-page application.ravelin('trackLogin')
→ Removed.ravelin('trackLogout')
→ Removed.ravelin('fingerprint')
→ Removed. This method implemented some
privacy-insensitive browser fingerprinting that Ravelin no longer wishes to be
part of. Instead, follow the instructions of
ravelin.core.id()
to send the device via your server.ravelin('send')
→ Removedravelin('setCustomerId')
→ Removed.ravelin('setTempCustomerId')
→ Removed.ravelin('setOrderId')
→ Removed.