Link Search Menu Expand Document

TilledJS

Table of contents

  1. Including Tilled.js
  2. Initializing Tilled.js
    1. Sandbox Environment
  3. Form and Form Field objects
    1. tilled.form(options?): Promise<Form>
    2. form.createField(formFieldType, options?): FormField
    3. Form Field Styles
    4. field.inject(selector: string | DOM element): this
    5. field.on(event, handler): void
      1. Change event
      2. Ready event
      3. Focus event
      4. Blur event
    6. form.build(): Promise<void>
    7. form.teardown(handler?): Promise<boolean> | void
  4. Processing Payments Overview
    1. tilled.confirmPayment(clientSecret: string, params): Promise<PaymentIntent>
    2. tilled.createPaymentMethod(params): Promise<PaymentMethod>
  5. PaymentRequest (i.e. Apple Pay)
    1. tilled.paymentRequest(options)
    2. paymentRequest.canMakePayment(): Promise<boolean>
    3. paymentRequest.on('paymentmethod', handler): void
    4. paymentRequest.on('cancel', handler): void
  6. Examples
    1. Credit Card Form Example
    2. ACH Bank Account Form Example
    3. PaymentRequest Example

Tilled’s browser-side JavaScript library, Tilled.js, is the easiest way to collect sensitive payment details and complete transactions. It allows you to embed a payments form in your application and stores credit card information securely on remote servers instead of passing through your network which limits your PCI compliance burden.

Including Tilled.js

Add the following HTML snippet to your web page, preferably in the <head> tag of your web page:

<script src="https://js.tilled.com/v2"></script>

Note: To be PCI compliant, you must load Tilled.js directly from https://js.tilled.com. You cannot include it in a bundle or host it yourself.

Initializing Tilled.js

Instantiate an instance of Tilled by providing it with your publishable API key and the Tilled account id of the merchant account to perform the action on behalf of.

const tilled = new Tilled('pk_…', 'acct_…');

Use new Tilled(publishableKey, tilledAccount, options?) to create an instance of the Tilled object. The Tilled object provides access to the rest of the Tilled.js SDK. Your Tilled publishable API key is required when calling this function, replace the sample API key above with your actual API key. You can retrieve your API key by accessing https://app.tilled.com/api-keys.

Method parameters
publishableKey - string
Your publishable key.
tilledAccount - string
Connected account ID (e.g. acct_123...) to perform actions on behalf of.
options? - optional object
Initialization options.

sandbox - boolean
Send API requests to the sandbox environment. Defaults to false.

Sandbox Environment

For the sandbox environment, retrieve your API key from https://sandbox-app.tilled.com/api-keys and instantiate an instance of Tilled with the following options:

const tilled = new Tilled('pk_…', 'acct_…', { sandbox: true });

Form and Form Field objects

Tilled Form Fields are customizable UI components used to collect sensitive information in your payments forms. Use a Form instance to create and manage a group of individual Form Field instances.

tilled.form(options?): Promise<Form>

This method creates a Form instance, which manages a group of form fields.

Method parameters
options? - optional object
Initialization options.

payment_method_type? - ‘card’ | ‘ach_debit’
The payment method type being created by this form. Defaults to card.
// Create a Form instance
const form = await tilled.form({ payment_method_type: 'card' });

form.createField(formFieldType, options?): FormField

This method creates a Form Field instance.

Method parameters
formFieldType - ‘cardNumber’ | ‘cardExpiry’ | ‘cardCvv’ | ‘bankAccountNumber’ | ‘bankRoutingNumber’
Field type.
options? - optional object
Initialization options.

selector? - string | DOM element
A CSS Selector (e.g., #card-number-container) or a DOM element.
styles? - object
This option enables the ability to specify several CSS styles for the field.
placeholder? - string
Specifies short hint that describes the expected value of the input field. Defaults to 'MM / YY' for cardExpiry.
// Create a cardNumber Form Field
const cardNumberField = form.createField('cardNumber');

Form Field Styles

Form Field objects are styled using a styles object, which consists of CSS properties nested under objects for any of the following variants:

  • base, base variant—all other variants inherit from these styles
  • valid, applied when the FormField has valid input
  • invalid, applied when the FormField has invalid input

The following pseudo-classes can also be styled using a nested object inside of a variant:

  • :hover
  • :focus

The following CSS properties are supported:

// Style a Form Field
const fieldOptions = {
  styles: {
    base: {
      fontFamily: 'Helvetica Neue, Arial, sans-serif',
      color: '#304166',
      fontWeight: '400',
      fontSize: '14px',
    },
    invalid: {
      ':hover': {
        textDecoration: 'underline dotted red',
      },
    },
    valid: {
      color: '#00BDA5',
    },
  },
};

const cardNumberField = form.createField('cardNumber', fieldOptions);

The above example will yield the following styles:

  • invalid:hover = 4111 1111 1111 1112
  • valid = 4111 1111 1111 1111

field.inject(selector: string | DOM element): this

You need to create a container DOM element to inject a Form Field into (e.g., <div id="card-number-container"></div>).

formField.inject accepts either a CSS Selector (e.g., #card-number-container) or a DOM element. For most form field types, it behaves the same as passing { selector: string | DOM element } when creating the Form Field.

form.createField('cardNumber', { selector: '#card-number-container'});
// is the same as
form.createField('cardNumber').inject('#card-number-container');

When form field type is paymentRequestButton, field.inject will insert the native payment request button into the DOM.

field.on(event, handler): void

The primary way to communicate with a Form Field is by listening to an event.

Method parameters
event - ‘blur’ | ‘focus’ | ‘ready’ | ‘change’
The name of the event.
handler - function
handler(event) => void A callback function that will be called when the event is fired.

Change event

The 'change' event is trigger when the Form Field’s value changes. The event payload will always contain certain keys, in addition to some Form Field-specific keys.

  • fieldType string
    The type of field that emitted this event.
  • empty boolean
    true if the value is empty.
  • valid boolean
    true if the value is well-formed and ready for submission.
  • error string
    The current validation error, if any.
  • brand string
    The card brand of the card number being entered. Can be one of amex, diners, discover, jcb, maestro, mastercard, solo, visa, visa_debit, visa_electron, or unknown.
    Only available when fieldType = 'cardNumber'.
// Example handler event object
{
  fieldType: 'cardNumber',
  empty: false,
  valid: true,
  error: undefined,
  brand: 'visa',
}
// Example of change event with card brand
cardNumberField.on('change', (evt) => {
  const cardBrand = evt.brand;
  const icon = document.getElementById('credit-card-logo');

  switch (cardBrand) {
    case 'amex': 
      icon.classList = 'fa fa-cc-amex'; break;
    case 'mastercard':
      icon.classList = 'fa fa-cc-mastercard'; break;
    case 'visa':
      icon.classList = 'fa fa-cc-visa'; break;
    case 'discover':
      icon.classList = 'fa fa-cc-discover'; break;
    case 'diners':
      icon.classList = 'fa fa-cc-diners-club'; break;
    default:
      icon.classList = 'fa fa-credit-card';
  }
});

Ready event

Triggered when the Form Field is fully rendered and can accept input.

Focus event

Triggered when the Form Field gains focus.

Blur event

Triggered when the Form Field loses focus.

form.build(): Promise<void>

Injects Form Field iframes into the DOM. Call this method after all Form Fields have been created and have their selectors specified (either via createField(, { selector: } or field.inject()).

await form.build();

form.teardown(handler?): Promise<boolean> | void

Removes Form Field iframes from the DOM. Call this method when you are done using the form.

Method parameters
handler? - function
handler(success: boolean) => void An optional callback function that will be called when teardown is complete. If no callback is provided, teardown returns a promise.
// Promise
const success = await form.teardown();

// Callback
form.teardown((success) => {});

Processing Payments Overview

Prior to displaying your checkout form and confirming the payment, your backend server will need to make an API call to Tilled to create a Payment Intent with the payment amount. You will pass the intent’s client_secret to your front end. Use the tilled.confirmPayment(client_secret, { payment_method }) method to process a payment with either an existing Payment Method id or a new one using this Form.

tilled.confirmPayment(clientSecret: string, params): Promise<PaymentIntent>

Confirms a Payment Intent and, optionally, creates a Payment Method from the Form Fields in the DOM.

Method parameters
clientSecret - string
The paymentIntent.client_secret generated from your backend server.
params - object
Payment intent confirmation params.

payment_method - string | PaymentMethodCreateParams
An existing payment method identifier (e.g., pm_123abc456) or a Create Payment Method request object, namely billing_details.

This method returns a Promise which will resolve with a Payment Intent object.

tilled
  .confirmPayment(paymentIntentClientSecret, {
    payment_method: {
      type: 'card',
      billing_details: {
        name: 'John Doe',
        address: {
          zip: '80021',
          country: 'US',
        },
      },
    },
  })
  .then(
    (paymentIntent) => {
      // Be sure to check the `status` and/or `last_error_message`
      // properties to know if the charge was successful
    },
    (error) => {
      // Typically an error with the request (>400 status code)
    },
  );

tilled.createPaymentMethod(params): Promise<PaymentMethod>

Use this method is to convert payment information collected by Form Fields into a Payment Method object that you safely pass to your server to use in an API call. It can be used to create re-usable payment methods.

Note: In most integrations, you will not need to use this method.

Method parameters
params - object
Create Payment Method params

type - ‘card’ | ‘ach_debit’
The type of the Payment Method.
billing_details - object
Billing information associated with the Payment Method.
ach_debit - object
Details about the ACH direct debit bank account. Only applicable (and required) for type: ach_debit
  • billing_details
    • name the card holder’s full name
    • address an object representing the card holder’s billing address
      • country and zip required for card
      • street, city, and state also required for ach_debit
    • email the email address of the card holder
  • ach_debit (for ACH Debit payments)
    • account_type Bank account type (checking | savings)
    • account_holder_name the name of the customer or company that owns the bank account
tilled
  .createPaymentMethod({
    type: 'card',
    billing_details: {
      name: 'John Doe',
      address: {
        zip: '80021',
        country: 'US',
      },
    },
  })
  .then(
    (paymentMethod) => {
      // Pass paymentMethod.id to your backend to attach it
      // to a customer record for reusability
    },
    (error) => {
      // An error with the request (>400 status code)
    },
  );

PaymentRequest (i.e. Apple Pay)

PaymentRequest instances emit several different types of events.

tilled.paymentRequest(options)

Use tilled.paymentRequest to create a PaymentRequest object. In Safari, tilled.paymentRequest uses Apple Pay.

options properties
total - object
Line item that is shown to the customer in the browser’s payment interface.
amount - number
The amount in the currency’s minor unit (e.g. cents)
label - string
A name that the browser shows the customer in the payment interface.
requestPayerName? - boolean
By default, the browser’s payment interface only asks the customer for actual payment information. A customer name can be collected by setting this option to true. We highly recommend you collect name as this also results in collection of billing address for Apple Pay, which can be used to perform address verification.
requestPayerEmail? - boolean
See the requestPayerName option.
const paymentRequest = tilled.paymentRequest({
  total: {
    label: 'Tilled tee',
    amount: paymentIntent.amount,
  },
  requestPayerName: true,
  requestPayerEmail: true,
});

paymentRequest.canMakePayment(): Promise<boolean>

Returns a Promise that resolves true if an enabled wallet is ready to pay. If no wallet is available, it resolves with false;

var prButton = form.createField('paymentRequestButton', {
  paymentRequest: paymentRequest,
});

paymentRequest.canMakePayment().then((result) => {
  if (result) {
    // Inject paymentRequestButton Form Field to the DOM
    prButton.inject('#native-payment-element');
  }
});

paymentRequest.on('paymentmethod', handler): void

Tilled.js automatically creates a payment method after the customer is done interacting with the browser’s payment interface. To access the created payment method, listen for this event.

Method parameters
event - ‘paymentmethod’
The name of the event.
handler - function
handler(event: object) => void
A callback function that will be called when the event is fired.
handler object properties
paymentMethod - PaymentMethod
A Payment Method object.
complete - function
complete(status) => void
A Tilled.js provided function. Call this when you have processed the payment method data provided by the API.
‘success’ - value
Report to the browser that the payment was successful, and that it can close any active payment interface.
‘fail’ - value
Report to the browser that the payment was unsuccessful. Browsers may re-show the payment interface, or simply show a message and close.
// Example handler event object
{
  paymentMethod: {
    id: 'pm_123456789abc'
    type: 'card',
    ...
  },
  complete: function(status) {
    // Call this when you have processed the source data
    // provided by the API.
  },
}

paymentRequest.on('cancel', handler): void

The cancel event is emitted from a PaymentRequest when the browser’s payment interface is dismissed.

Note that in some browsers, the payment interface may be dismissed by the customer even after they authorize the payment. This means you may receive a cancel event on your PaymentRequest after receiving a paymentmethod event. If you’re using the cancel event as a hook for canceling the customer’s order, make sure you also refund the payment that you just created.

paymentRequest.on('cancel', function() {
  // handle cancel event
});

Examples

See our simple payment example for a full example.

Credit Card Form Example

/**
 * Example assumptions:
 * The card fields have divs defined in the DOM
 * <div id="card-number-element"></div>
 * <div id="card-expiration-element"></div>
 * <div id="card-cvv-element"></div>
 * 
 * A submit button is defined
 * <button id='submit-btn'></button>
 */
const tilled = new Tilled('pk_…', 'acct_…');

const form = await tilled.form({
  payment_method_type: 'card',
});

const fieldOptions = {
  styles: {
    base: {
      fontFamily: 'Helvetica Neue, Arial, sans-serif',
      color: '#304166',
      fontWeight: '400',
      fontSize: '16px',
    },
    invalid: {
      ':hover': {
        textDecoration: 'underline dotted red',
      },
    },
    valid: {
      color: '#00BDA5',
    },
  },
};

form.createField('cardNumber', fieldOptions).inject('#card-number-element');
// Example of providing selector instead of using inject()
form.createField('cardExpiry', {
  ...fieldOptions, 
  selector: '#card-expiration-element'
})
form.createField('cardCvv', fieldOptions).inject('#card-cvv-element');

await form.build();

const submitButton = document.getElementById('submit-btn');
submitButton.on('click', () => {
  // A payment intent will be created on your backend server and the
  // payment_intent.client_secret will be passed to your frontend to
  // be used below. 
  tilled
    .confirmPayment(paymentIntentClientSecret, {
      payment_method: {
        billing_details: {
          name: 'John Doe',
          address: {
            zip: '80021',
            country: 'US', 
          },
        },
      },
    })
    .then(
      (paymentIntent) => {
        // Be sure to check the `status` and/or `last_payment_error`
        // properties to know if the charge was successful
        if (paymentIntent.status === 'succeeded') {
          alert('Payment successful');
        } else {
          const errMsg = paymentIntent.last_payment_error?.message;
          alert('Payment failed: ' + errMsg);
        }
      },
      (err) => {
        // Typically an error with the request (>400 status code)
      },
    );
});

ACH Bank Account Form Example

/**
 * Example assumptions:
 * The ach_debit fields have divs defined in the DOM
 * <div id="bank-account-number-element"></div>
 * <div id="bank-routing-number-element"></div>
 * 
 * A submit button is defined
 * <button id='submit-btn'></button>
 */
const tilled = new Tilled('pk_…', 'acct_…');

const form = await tilled.form({
  payment_method_type: 'ach_debit',
});

const fieldOptions = {
  styles: {
    base: {
      fontFamily: 'Helvetica Neue, Arial, sans-serif',
      color: '#304166',
      fontWeight: '400',
      fontSize: '16px',
    },
    invalid: {
      ':hover': {
        textDecoration: 'underline dotted red',
      },
    },
    valid: {
      color: '#00BDA5',
    },
  },
};

form.createField('bankAccountNumber', fieldOptions).inject('#bank-account-number-element');
form.createField('bankRoutingNumber', fieldOptions).inject('#bank-routing-number-element');

await form.build();

const submitButton = document.getElementById('submit-btn');
submitButton.on('click', () => {
  // A payment intent will be created on your backend server and the
  // payment_intent.client_secret will be passed to your frontend to
  // be used below. 
  tilled
    .confirmPayment(paymentIntentClientSecret, {
      payment_method: {
        billing_details: {
          name: 'John Doe',
          address: {
            street: '370 Interlocken Blvd',
            city: 'Broomfield',
            state: 'CO',
            zip: '80021',
            country: 'US', 
          },
        },
        ach_debit: {
          account_type: 'checking',
          account_holder_name: 'John Doe',
        },
      },
    })
    .then(
      (paymentIntent) => {
        // Be sure to check the `status` and/or `last_payment_error`
        // properties to know if the charge was successful
        if (paymentIntent.status === 'succeeded' || paymentIntent.status === 'processing') {
          alert('Payment successful');
        } else {
          const errMsg = paymentIntent.last_payment_error?.message;
          alert('Payment failed: ' + errMsg);
        }
      },
      (err) => {
        // Typically an error with the request (>400 status code)
      },
    );
});

PaymentRequest Example

/**
 * Example assumptions:
 * The paymentRequestButton field has a div defined in the DOM
 * <div id="native-payment-element"></div> 
 * 
 */
const form = tilled.form({
  payment_method_type: 'card',
});

const paymentRequest = tilled.paymentRequest({
  total: {
    label: 'Tilled tee',
    amount: secretData.amount,
  },
});

const prButton = form.createField('paymentRequestButton', {
  paymentRequest: paymentRequest,
});

paymentRequest.canMakePayment().then((result) => {
  if (result) {
    prButton.inject('#native-payment-element');
  } else {
    document.getElementById('native-payment-element').style.display =
      'none';
  }
});

paymentRequest.on('paymentmethod', (ev) => {
  let paymentMethod = ev.paymentMethod;
  tilled
    .confirmPayment(paymentIntentClientSecret, {
      payment_method: paymentMethod.id,
    })
    .then(
      (paymentIntent) => {
        // The payment intent confirmation occurred, but the
        // actual charge may still have failed. Check
        if (
          paymentIntent.status === 'succeeded' ||
          paymentIntent.status === 'processing'
        ) {
          ev.complete('success');
          alert('Successul payment');
        } else {
          ev.complete('fail');
          const errMsg = paymentIntent.last_payment_error?.message;
          alert('Payment failed: ' + errMsg);
        }
      },
      (err) => {
        ev.complete('fail');
      },
    );
});

Copyright © 2022 Tilled