TilledJS
Table of contents
- Including Tilled.js
- Initializing Tilled.js
- Form and Form Field objects
- Processing Payments Overview
- PaymentRequest (i.e. Apple Pay)
- Examples
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 stylesvalid, applied when the FormField has valid inputinvalid, 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:
colorstring : The color CSS property.opacitystring : The opacity CSS property.letterSpacingstring : The leter-spacing CSS property.textAlignstring : The text-align CSS property.textIndentstring : The text-indent CSS property.textDecorationstring : The text-decoration CSS property.textShadowstring : The text-shadow CSS property.fontstring : The font CSS property.fontFamilystring : The font-family CSS property.fontSizestring : The font-size CSS property.fontStylestring : The font-style CSS property.fontWeightstring : The font-weight CSS property.lineWeightstring : The line-weight CSS property.transitionstring : The transition CSS property.
// 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 1112valid= 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 - functionhandler(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.
fieldTypestring
The type of field that emitted this event.emptybooleantrueif the value is empty.validbooleantrueif the value is well-formed and ready for submission.errorstring
The current validation error, if any.brandstring
The card brand of the card number being entered. Can be one ofamex,diners,discover,jcb,maestro,mastercard,solo,visa,visa_debit,visa_electron, orunknown.
Only available whenfieldType = '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? - functionhandler(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 - objectCreate Payment Method paramstype - ‘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_detailsnamethe card holder’s full nameaddressan object representing the card holder’s billing addresscountryandziprequired forcardstreet,city, andstatealso required forach_debit
emailthe email address of the card holder
ach_debit(for ACH Debit payments)account_typeBank account type (checking | savings)account_holder_namethe 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 - functionhandler(event: object) => voidA callback function that will be called when the event is fired. |
| handler object properties |
|---|
| paymentMethod - PaymentMethod A Payment Method object. |
complete - functioncomplete(status) => voidA 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');
},
);
});