Guides


Run Developer Home

Getting Started

Tokenize with Runner.js

Reporting with Run Merchant

Boarding API

Validation & Deployment

Changelog

What is Runner.js?


Runner.js is a browser-side JavaScript library used to tokenize and send sensitive payment information directly from a user’s browser, keeping your customer’s data secure and your software fully PCI-compliant. A single integration to Runner.js will provide your tokenization needs for your integration regardless of the end payment processor for your merchant accounts.

<aside> ✅ If you’d like to tokenize a payment account without integrating to Runner.js, log on to the Run Merchant portal and navigate to the Developer menu where you will find a card tokenizer for your merchant account. Please reach out to your Integration Delivery lead if you do not see this menu in your account.

</aside>

Versioning

Runner.js is being released periodically, introducing new features. In order not to break existing functionalities, we are keeping several releases simultaneously.

Important: Sometimes breaking changes will be introduced. Clients are notified when this happens and due to runner.js versioning we are minimizing impact of breaking changes. Developers and integrators should test everything in their staging environment and bump the runner.js version up only when ready.

In staging it is possible to use “latest” release and have always the latest changes, however we strongly recommend using a specific version in production environment.

Also important: After some time, old versions of runner.js will be discontinued, so make sure you are using the newer versions.

Parameters

Attribute Description Required
element ID (with #) of the element where to load the card number/CVV fields (iframe) or the account/routing number in case of ACH Yes
publicKey Your Public key linked to your account Yes
css Styling that will get applied to the iframe (account number/cvv) fields in case of card entry. Please send bot url encoded version or JS object No
env Possible values: local (requests sent to localhost:8000), staging (requests sent to javelin-staging env), production (requests sent to Run/Javelin production) No
useExpiry If true, it will render expiry month/year fields No
useCvv If true it will render the cvv field No
cardLabel Set your own label for the card number input field No
cvvLabel Set your own label for cvv field No
expiryLabel Set your own label for expiry field No
`cardPlaceholder`` Placeholder text for card number field No
cvvPlaceholder Placeholder text for cvv field No
accountNumberPlaceholder Placeholder text for account number field (ACH) No
routingNumberPlaceholder Placeholder text for routing # text (ACH) No
repeatedAccountNumberPlaceholder Placeholder text for repeated account # field No
accountNumberLabel Custom label for account number field (ACH) No
routingNumberLabel Custom label for routing number field No
repeatedAccountNumberLabel Custom label for repeated account number label No
accountTypeLabel Custom label for account type (IStream) No
customerNameLabel Custom label for customer name (IStream) No
entryClassCodeLabel Custom label for entry class code (Istream) No
tokenizeAutomatically If you want Runner.js to call tokenize after user stops typing instead of using onBlur event. Default false No
inactivityto How long to wait (in ms) for tokenize to be called. Works only if tokenizeAutomatically is set to true. Default 300 No
errorsMap URL encoded JS object of custom errors that you can supply to override default error messages. Only applies to ACH form and card connect gateway.

Example: URL encoded the following object

{
"1": "Account number is required",
"2": "Account number repeat does not match",
"3": "Routing number is required",
"4": "Routing number must be 9 digits",
"5": "Account type is required",
"6": "Customer name is required",
};
``` | No |
| `errorType` | Ability to choose the way errors are displayed in ACH form (applies only to card connect gateway). Possible values: `field`  - errors will be shown below each invalid input field
`list` - errors will be shown as unordered list at the top of the form | No |

# Applied Usage

1. Add Runner.js to your website by adding the following script tag to the ‘head’ of your HTML file. `version` number is required, or use “latest” for the last release. 
    
    ```jsx
    <script src=”<https://javelin.runpayments.io/javascripts/><version>/runner.js”></script>
    ```
    
    *Example using Version 1.1.4:*
    
    ```jsx
    <script src=”https://javelin-staging.runpayments.io/javascripts/1.2.8/runner.js”></script>
    ```
    
    *Or you can use “latest” to always correspond with the current release.*
    
    ```jsx
    <script src=”https://javelin-staging.runpayments.io/javascripts/latest/runner.js”></script>
    ```
    

1. Initialize runner.js
    
    ```jsx
    var runner = new Runner();
        runner.init({
          element: '#run-form',
          publicKey: <YOUR KEY>,
          css: 'input%7Bwidth%3A200px%3B%7D',
          useExpiry: true,
          useCvv: true,
          cardLabel: 'test label',
          cvvLabel: 'test cvv label',
          expiryLabel: 'test expiry label',
          tokenizeAutomatically: true,
          inactivityto: 200
        });
    ```
    
    *Example using ACH via iStream:*
    
    ```jsx
    var runner = new Runner();
        runner.init({
          element: '#run-form',
          publicKey: <YOUR KEY>,
          css: 'input%7Bwidth%3A200px%3B%7D',
          tokenizeAutomatically: true,
          inactivityto: 200,
          accountNumberLabel: 'test account number label',
          repeatedAccountNumberLabel: 'test repeated account number label',
          routingNumberLabel: 'test routing number label',
          accountTypeLabel: 'test account type label',
          customerNameLabel: 'test customer name label',
          entryClassCodeLabel: 'test entry class code label',
        });
    ```
    
- *(Optional)* Subscribe to iframe loaded event:
    
    ```jsx
    runner.onLoaded(function() {
           console.log('loaded');
      });
    ```
    
- *(Optional)* Subscribe to onTokenize event.
    
    This is very convenient in case you are making use of `inactivityto` param which tokenizes input after certain delay after user stops typing. 
    
    *onTokenize will fire and contain the latest token value:*
    
    ```jsx
    runner.onTokenize (function (res) {
        console.log(`tokenized: ${JSON.stringify(res)}`);
      });
    ```
    

---

1. Call the “`tokenize`” method to make an API call to Run Payments’ backend. (Note: This action can be completed during form submission.)

Now that you have the `runner` object from Step 2, call `tokenize` on it to fetch tokenized form information and send it to your backend. The resulting value may then be passed to the  Payments API to enact payment processing activities.

Definition:

`tokenize(successCallback)`

- `successCallback` returns with following object:

```jsx
{account_token: <tokenized card data here>, expiry: <month/year>}

Here is the example:

runner.tokenize((res) => {
  // let’s make API call to the backend
  var xhr = new XMLHttpRequest();
  xhr.open(”POST”, “<https://example.com/charge”>, true);
  xhr.setRequestHeader('Content-Type', 'application/json');

  xhr.onerror = function (err) {
		// handle error here…
    errorElement.textContent = typeof err === 'object' ? (err && err.error) || Object.keys(err).map((k) => err[k].join(' ')).join(' ') : JSON.stringify(err);
    errorElement.classList.add('visible');
    loaderElement.classList.remove('visible');
  };

  // other params from your payment form
	var extraDetails = {
		amount: 10000,
		name: “John Doe:,
		 email: '[email protected]',
		 phone: “1231231231”,
		 address1: '100 New York, 5th ',
		 address2: '',
		 address_city: 'Orlando',
		 address_state: 'FL',
		 account_zip: '32811',
		 address_country: 'USA',
	   capture: 'Y', // Y - auth and charge ‘N’ - just auth
	   vault: 'Y', // Y - return vault_id in successful response - stores customer and card info in a vault object; N - does not store card/customer data
	}
  var params = JSON.stringify({
    ...extraDetails,
    account_token: res.account_token, // card number tokenized
    expiration: res.expiry,           // expiration
  });

  xhr.onreadystatechange = function() {
    if (xhr.readyState == XMLHttpRequest.DONE) {
      var resp = JSON.parse(xhr.responseText);

      if (resp['trans_id']) { // successful resp returns trans_id identifier
        successElement.querySelector('.token').textContent = resp['trans_id'];
      } else {
       // handle error here…
      }
    }
  }

  xhr.send(params);

})};
     

It is also possible to subscribe to event listener that will fire when tokenize method fails for reasons such as invalid input, invalid card etc.:

runner.validationMessageChange(function (res) {
    console.log(`validationMessageChange: ${JSON.stringify(res)}`);
  });
  1. Call Payments API from the back end

Using the resulting In step 3 we sent tokenized credit card or bank data, along with supplemental order information (inside extra object) to your backend system. Now we are ready to make calls to Payments API.

Please read the Payments API documentation

<aside> 💡

If using runner.js to create tokens compatible with Cybersource Gateway, please use the endpoint below to authenticate the url that is in use with the iFrame:

Method: PUT

URL: https://javelin.runpayments.io/api/v1/accounts/update_settings

Headers:

Content-Type: application/json

Authorization: Bearer <token>

Request Parameters:

Field Formatting Description
target_origins string, url must begin with https unless it is localhost Target_origins is a standard json array that allows you to submit the url where the iFrame is being rendered for authentication purposes

Sample Request:

{
  "target_origins": [
    "<http://localhost:3000>",
    "<http://localhost:3001>"
  ]
}

</aside>

Examples:

Cardpointe Gateway

Passing Merchant ID while using the same Public Key to render the correct version of the iFrame

    var runner = new Runner();
    runner.init({
      element: '#run-form',
      publicKey: 'N1uWuiv8KneR8Rppnnt5fw2n',
      mid: '800000001780',

IStream ACH

var runner = new Runner();
    
runner.onLoaded(() => {
  console.log('loaded test');
});

runner.onTokenize((res) => {
  // let’s make API call to the backend
  var xhr = new XMLHttpRequest();
  xhr.open('POST', '<https://example.com/charge>', true);
  xhr.setRequestHeader('Content-Type', 'application/json');

  xhr.onerror = function (err) {
    // handle error here…
  };

  var extraDetails = {
    amount: 10000,
    name: 'John Doe',
  }
  var params = JSON.stringify({
    ...extraDetails,
    account_token: res.token, // card number tokenized
    mid: '123456',
    credit_or_debit: 'D',
    entry_class: 'TEL', // SEC Code CCD, PPD, TEL or WEB
    // ach_discretionary_data: 'S' (required for entry class code WEB) ('S' for single transaction, 'R' for recurring)
  });

  xhr.onreadystatechange = function() {
    if (xhr.readyState == XMLHttpRequest.DONE) {
      var resp = JSON.parse(xhr.responseText);

      if (resp['trans_id']) { // successful resp returns trans_id identifier
        console.log(resp['trans_id']);
      } else {
       // handle error here…
      }
    }
  }

  xhr.send(params);
});

runner.init({
  element: '#run-form',
  publicKey: '2jDUkp25t7odyDTTQq4XQY61',
});

Cybersource Gateway

If on your form there is a div like this:

 <div id="cvvId"></div>

Then in the init function, you should have cvvFieldSelector: '#cvvId',

for reference:

runner.init({
      element: '#run-form',
      publicKey: 'TQhD2CRs9hedqYkozkdM4GnW',
      useCvv: true,
      cybersourceCvvField: '#cvvId',
      cvvLabel: 'test cvv label',
      cardLabel: 'test card label',
      css: {
        formContainer: {
             'input': {
                'font-size': '26px',
                'color': 'red'
              },
        },
        cardNumber: {
          'input': {
            'font-size': '16px',
            'color': 'green'
          },
        },
      }
    });

html part:

<div class="group">
  <label id="cardNumber-label">Card Number</label>
  <div id="cardNumber-container"></div>
</div>
<div class="group" >
  <label id="securityCode-label">Cvv</label>
  <div id="cvvId"></div>
</div>

<div class="group" >
  <label id="exp-label">Exp month</label>
   <input name="expmonth" class="field input-box"  placeholder="" />
   <label id="exp-label">Exp year</label>
   <input name="expyear" class="field input-box"  placeholder="" />
</div>

Full html example:

<div id="pay-example">

  <form id="my-sample-form" onsubmit="return false;">
  <div id="run-form"></div>
      <div class="group">
        <label>
          <span>First Name</span>
          <input
  name="cardholder-first-name" class="field input-box" placeholder="Jane" />
        </label>
        <label>
          <span>Last Name</span>
          <input name="cardholder-last-name" class="field input-box" placeholder="Doe" />
        </label>
        <label>
          <span>Phone</span>
          <input name="phone" class="field input-box"  placeholder="+1000000000000" />
        </label>
        <label>
          <span>Vault ID</span>
          <input name="vault_id" class="field input-box"  placeholder="" />
        </label>
      </div>
      <div class="group">
        <label id="cardNumber-label">Card Number</label>
        <div id="cardNumber-container"></div>
      </div>
      <div class="group" >
        <label id="securityCode-label">Cvv</label>
        <div id="cvvId"></div>
        <%# <input name="cvv" id="cvvId" class="field input-box" /> %>
      </div>

      <div class="group" >
        <label id="exp-label">Exp month</label>
         <input name="expmonth" class="field input-box"  placeholder="" />
         <label id="exp-label">Exp year</label>
         <input name="expyear" class="field input-box"  placeholder="" />
      </div>

      <div class="group">
        <label id="exp-label">Transaction ID to capture:</label>
      <input name="capture_id" />
      </div>

      <div class="group">
        <label id="exp-label">Transaction ID to void:</label>
      <input name="void_id" />
      </div>

      <div class="group">
        <label id="exp-label">Transaction ID to refund:</label>
      <input name="refund_id" />
      </div>

      <button id="pay-button">Pay $100</button>
</form>
</div>

Javascript:

<script>
  (function() {
var runner = new Runner();
    runner.init({
      element: '#run-form',
      publicKey: '<your key here>',
      useCvv: true,
      cvvFieldSelector: '#cvvId',
      cardNumberFieldSelector: "#cardNumber-container",
      cvvLabel: 'test cvv label',
      cardLabel: 'test card label',
      css: {
        formContainer: {
             'input': {
                'font-size': '26px',
                'color': 'red'
              },
        },
        cardNumber: {
          'input': {
            'font-size': '16px',
            'color': 'green'
          },
        },
      }
    });

document.querySelector('#pay-button').onclick = () => {
      var successElement = document.querySelector('.success');
      var errorElement = document.querySelector('.error');
      var loaderElement = document.querySelector('.loader');

      successElement.classList.remove('visible');
      errorElement.classList.remove('visible');
      loaderElement.classList.add('visible');

      var form = document.querySelector('form');
      var extraDetails = {
        amount: 100,
        name: form.querySelector('input[name=cardholder-first-name]').value + ' ' + form.querySelector('input[name=cardholder-last-name]').value,
        company_name: 'acme',
        email: '[email protected]',
        phone: form.querySelector('input[name=phone]').value,
        address1: '100 S Orange Ave',
        address2: '',
        city: 'Orlando',
        region: 'FL',
        currency: 'USD',
        address_state: 'FL',
        account_zip: '32811',
        country: 'US',
        order_id: '100',
        capture: 'Y',
        vault: 'Y',
        vault_id: form.querySelector('input[name=vault_id]').value,
      };
console.log(extraDetails);

      runner.tokenize((res) => {
        // console.log(res);

        var xhr = new XMLHttpRequest();
        xhr.open("POST", '<http://localhost:3001>' + '/charge', true);
        xhr.setRequestHeader('Content-Type', 'application/json');

        xhr.onerror = function (err) {
          errorElement.textContent = typeof err === 'object' ? (err && err.error) || Object.keys(err).map((k) => err[k].join(' ')).join(' ') : JSON.stringify(err);
          errorElement.classList.add('visible');
          loaderElement.classList.remove('visible');
        };

        var params = JSON.stringify({
          ...extraDetails,
          account_token: document.querySelector('#mytoken').value,
          expiration: document.querySelector('input[name=expmonth]').value + document.querySelector('input[name=expyear]').value,
          cvn: document.querySelector('input[name=cvv]')?.value || '222',
        });

        xhr.onreadystatechange = function() {
            if (xhr.readyState == XMLHttpRequest.DONE) {
                alert(xhr.responseText);

                var resp = JSON.parse(xhr.responseText);
                console.log(resp);

                if (resp['trans_id']) {
                  successElement.querySelector('.token').textContent = resp['trans_id'];
                  successElement.classList.add('visible');
                  loaderElement.classList.remove('visible');
                } else {
                  errorElement.textContent = typeof resp === 'object' ? (resp && resp.error) || Object.keys(resp).map((k) => resp[k].join(' ')).join(' ') : JSON.stringify(resp);
                  errorElement.classList.add('visible');
                  loaderElement.classList.remove('visible');
                }
            }
        }

        console.log(params);
        // send the request and parse the response
        xhr.send(params);

        console.log(res.account_token)

      }, form.querySelector('input[name=expmonth]').value, form.querySelector('input[name=expyear]').value)
    };

When there is an error where the html element cannot be specified, check to ensure html includes form ID or credit card field identifier. The below must be present if using element: ‘#run-form’

<div id="run-form"></div>

Since cvvFieldSelector was omitted, runner.js will use the default #securityCode-container selector as well as the cardNumberFieldSelector if omitted, so #cardNumber-container selector should be used.

 <div class="group">
    <label id="cardNumber-label">Card Number</label>
    <div id="cardNumber-container"></div>
  </div>
  <div class="group" >
    <label id="securityCode-label">Cvv</label>
    <div id="securityCode-container"></div>
  </div>

Vaulting

Run Payments API supports vaults which means it is possible to store the payment method for later use. The flow is:

  1. Create initial transaction with vault=Y param and save the vault_id that will be present in the response (if successful)

  2. In subsequent transactions provide vault_id=xxxxx obtained from previous step. No need to provide card/bank account data when vault_id is present. Any other fields that are provided such as name, address, phone etc., will override the values stored in the vault itself.

Tokenizing the same payment method again and providing vault=Y will update all values in the vault that are different than what is present in the vault.