Tilled.js

Let us sweat the small stuff. Embed a payments form in your application and store credit card information securely on our remote servers to limit 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:

1<script src="https://js.tilled.com/v2"></script>
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.

1const 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:

1const 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.
1// Create a Form instance
2const 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.
1// Create a cardNumber Form Field
2const 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 and pseudo-element can also be styled using a nested object inside of a variant:

  • :hover
  • :focus
  • ::placeholder

The following CSS properties are supported:

 1// Style a Form Field
 2const fieldOptions = {
 3    styles: {
 4        base: {
 5            fontFamily: "Helvetica Neue, Arial, sans-serif",
 6            color: "#304166",
 7            fontWeight: "400",
 8            fontSize: "14px",
 9            "::placeholder": {
10                color: "#94A3B8",
11            },
12        },
13        invalid: {
14            ":hover": {
15                textDecoration: "underline dotted red",
16            },
17        },
18        valid: {
19            color: "#00BDA5",
20        },
21    },
22};
23
24const cardNumberField = form.createField("cardNumber", fieldOptions);

The above example will yield the following styles:

  • base::placeholder

    1234 1234 1234 1234
  • 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.

1form.createField('cardNumber', { selector: '#card-number-container' });
2// is the same as
3form.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'.
1// Example handler event object
2{
3  fieldType: 'cardNumber',
4  empty: false,
5  valid: true,
6  error: undefined,
7  brand: 'visa',
8}
 1// Example of change event with card brand
 2cardNumberField.on('change', (evt) => {
 3	const cardBrand = evt.brand;
 4	const icon = document.getElementById('credit-card-logo');
 5
 6	switch (cardBrand) {
 7		case 'amex':
 8			icon.classList = 'fa fa-cc-amex';
 9			break;
10		case 'mastercard':
11			icon.classList = 'fa fa-cc-mastercard';
12			break;
13		case 'visa':
14			icon.classList = 'fa fa-cc-visa';
15			break;
16		case 'discover':
17			icon.classList = 'fa fa-cc-discover';
18			break;
19		case 'diners':
20			icon.classList = 'fa fa-cc-diners-club';
21			break;
22		default:
23			icon.classList = 'fa fa-credit-card';
24	}
25});

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()).

1await 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.
1// Promise
2const success = await form.teardown();
3
4// Callback
5form.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.

 1tilled
 2	.confirmPayment(paymentIntentClientSecret, {
 3		payment_method: {
 4			type: 'card',
 5			billing_details: {
 6				name: 'John Doe',
 7				address: {
 8					zip: '80021',
 9					country: 'US',
10				},
11			},
12		},
13	})
14	.then(
15		(paymentIntent) => {
16			// Be sure to check the `status` and/or `last_error_message`
17			// properties to know if the charge was successful
18		},
19		(error) => {
20			// Typically an error with the request (>400 status code)
21		}
22	);

tilled.createPaymentMethod(params): Promise<PaymentMethod>

Use this method 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.

In integrations that do not save payment methods, you will not need to use this method. However, to reuse a payment method, you will need to create it with this method first, attach it to a customer, and confirm it with the confirmPayment method or via the API.
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
 1tilled
 2	.createPaymentMethod({
 3		type: 'card',
 4		billing_details: {
 5			name: 'John Doe',
 6			address: {
 7				zip: '80021',
 8				country: 'US',
 9			},
10		},
11	})
12	.then(
13		(paymentMethod) => {
14			// Pass paymentMethod.id to your backend to attach it
15			// to a customer record for reusability
16		},
17		(error) => {
18			// An error with the request (>400 status code)
19		}
20	);

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.
style? - optional object
Optional styling for the Apple Pay button.
type - 'default' | 'buy' | 'book' | 'donate' | 'check-out' | 'subscribe' | 'order'
The type of button to display. By default, Buy with  Pay.
theme - 'black' | 'white' | 'white-outline'
The color scheme of the button.
requestPayerName? - optional 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? - optional boolean
See the requestPayerName option.
 1const paymentRequest = tilled.paymentRequest({
 2	total: {
 3		label: 'Tilled tee',
 4		amount: paymentIntent.amount,
 5	},
 6	style: {
 7		type: 'donate',
 8		theme: 'black',
 9	},
10	requestPayerName: true,
11	requestPayerEmail: true,
12});

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;

 1var prButton = form.createField('paymentRequestButton', {
 2	paymentRequest: paymentRequest,
 3});
 4
 5paymentRequest.canMakePayment().then((result) => {
 6	if (result) {
 7		// Inject paymentRequestButton Form Field to the DOM
 8		prButton.inject('#native-payment-element');
 9	}
10});

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.
 1// Example handler event object
 2{
 3  paymentMethod: {
 4    id: 'pm_123456789abc'
 5    type: 'card',
 6    ...
 7  },
 8  complete: function(status) {
 9    // Call this when you have processed the source data
10    // provided by the API.
11  },
12}

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.

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

Examples

See our simple payment example or our react payment example for full examples.

Credit Card Form Example

 1/**
 2 * Example assumptions:
 3 * The card fields have divs defined in the DOM
 4 * <div id="card-number-element"></div>
 5 * <div id="card-expiration-element"></div>
 6 * <div id="card-cvv-element"></div>
 7 *
 8 * A submit button is defined
 9 * <button id='submit-btn'></button>
10 */
11const tilled = new Tilled('pk_…', 'acct_…');
12
13const form = await tilled.form({
14	payment_method_type: 'card',
15});
16
17const fieldOptions = {
18	styles: {
19		base: {
20			fontFamily: 'Helvetica Neue, Arial, sans-serif',
21			color: '#304166',
22			fontWeight: '400',
23			fontSize: '16px',
24		},
25		invalid: {
26			':hover': {
27				textDecoration: 'underline dotted red',
28			},
29		},
30		valid: {
31			color: '#00BDA5',
32		},
33	},
34};
35
36form.createField('cardNumber', fieldOptions).inject('#card-number-element');
37// Example of providing selector instead of using inject()
38form.createField('cardExpiry', {
39	...fieldOptions,
40	selector: '#card-expiration-element',
41});
42form.createField('cardCvv', fieldOptions).inject('#card-cvv-element');
43
44await form.build();
45
46const submitButton = document.getElementById('submit-btn');
47submitButton.on('click', () => {
48	// A payment intent will be created on your backend server and the
49	// payment_intent.client_secret will be passed to your frontend to
50	// be used below.
51	tilled
52		.confirmPayment(paymentIntentClientSecret, {
53			payment_method: {
54				billing_details: {
55					name: 'John Doe',
56					address: {
57						zip: '80021',
58						country: 'US',
59					},
60				},
61			},
62		})
63		.then(
64			(paymentIntent) => {
65				// Be sure to check the `status` and/or `last_payment_error`
66				// properties to know if the charge was successful
67				if (paymentIntent.status === 'succeeded') {
68					alert('Payment successful');
69				} else {
70					const errMsg = paymentIntent.last_payment_error?.message;
71					alert('Payment failed: ' + errMsg);
72				}
73			},
74			(err) => {
75				// Typically an error with the request (>400 status code)
76			}
77		);
78});

ACH Bank Account Form Example

 1/**
 2 * Example assumptions:
 3 * The ach_debit fields have divs defined in the DOM
 4 * <div id="bank-account-number-element"></div>
 5 * <div id="bank-routing-number-element"></div>
 6 *
 7 * A submit button is defined
 8 * <button id='submit-btn'></button>
 9 */
10const tilled = new Tilled('pk_…', 'acct_…');
11
12const form = await tilled.form({
13	payment_method_type: 'ach_debit',
14});
15
16const fieldOptions = {
17	styles: {
18		base: {
19			fontFamily: 'Helvetica Neue, Arial, sans-serif',
20			color: '#304166',
21			fontWeight: '400',
22			fontSize: '16px',
23		},
24		invalid: {
25			':hover': {
26				textDecoration: 'underline dotted red',
27			},
28		},
29		valid: {
30			color: '#00BDA5',
31		},
32	},
33};
34
35form
36	.createField('bankAccountNumber', fieldOptions)
37	.inject('#bank-account-number-element');
38form
39	.createField('bankRoutingNumber', fieldOptions)
40	.inject('#bank-routing-number-element');
41
42await form.build();
43
44const submitButton = document.getElementById('submit-btn');
45submitButton.on('click', () => {
46	// A payment intent will be created on your backend server and the
47	// payment_intent.client_secret will be passed to your frontend to
48	// be used below.
49	tilled
50		.confirmPayment(paymentIntentClientSecret, {
51			payment_method: {
52				billing_details: {
53					name: 'John Doe',
54					address: {
55						street: '370 Interlocken Blvd',
56						city: 'Broomfield',
57						state: 'CO',
58						zip: '80021',
59						country: 'US',
60					},
61				},
62				ach_debit: {
63					account_type: 'checking',
64					account_holder_name: 'John Doe',
65				},
66			},
67		})
68		.then(
69			(paymentIntent) => {
70				// Be sure to check the `status` and/or `last_payment_error`
71				// properties to know if the charge was successful
72				if (
73					paymentIntent.status === 'succeeded' ||
74					paymentIntent.status === 'processing'
75				) {
76					alert('Payment successful');
77				} else {
78					const errMsg = paymentIntent.last_payment_error?.message;
79					alert('Payment failed: ' + errMsg);
80				}
81			},
82			(err) => {
83				// Typically an error with the request (>400 status code)
84			}
85		);
86});

PaymentRequest Example

 1/**
 2 * Example assumptions:
 3 * The paymentRequestButton field has a div defined in the DOM
 4 * <div id="native-payment-element"></div>
 5 *
 6 */
 7const form = tilled.form({
 8	payment_method_type: 'card',
 9});
10
11const paymentRequest = tilled.paymentRequest({
12	total: {
13		label: 'Tilled tee',
14		amount: secretData.amount,
15	},
16});
17
18const prButton = form.createField('paymentRequestButton', {
19	paymentRequest: paymentRequest,
20});
21
22paymentRequest.canMakePayment().then((result) => {
23	if (result) {
24		prButton.inject('#native-payment-element');
25	} else {
26		document.getElementById('native-payment-element').style.display = 'none';
27	}
28});
29
30paymentRequest.on('paymentmethod', (ev) => {
31	let paymentMethod = ev.paymentMethod;
32	tilled
33		.confirmPayment(paymentIntentClientSecret, {
34			payment_method: paymentMethod.id,
35		})
36		.then(
37			(paymentIntent) => {
38				// The payment intent confirmation occurred, but the
39				// actual charge may still have failed. Check
40				if (
41					paymentIntent.status === 'succeeded' ||
42					paymentIntent.status === 'processing'
43				) {
44					ev.complete('success');
45					alert('Successul payment');
46				} else {
47					ev.complete('fail');
48					const errMsg = paymentIntent.last_payment_error?.message;
49					alert('Payment failed: ' + errMsg);
50				}
51			},
52			(err) => {
53				ev.complete('fail');
54			}
55		);
56});