@tonder.io/ionic-full-sdk
v1.0.0
Published
Tonder ionic full SDK
Keywords
Readme
@tonder.io/ionic-full-sdk
PCI DSS–compliant payment SDK for Ionic and Angular. Card data is collected through Skyflow secure iframes — raw card values never touch your application code.
Table of Contents
- Quick Start
- Installation
- Constructor & Configuration
- Initialization Sequence
- Processing Payments
- 3DS Handling
- Managing Saved Cards
- Error Handling
- Customization & Styling
- Mobile Settings
- API Reference
- Deprecated API
- License
1. Quick Start
Get a working payment form in under 5 minutes. This example shows the minimum required setup for an Angular component. See Section 4 for the full step-by-step explanation.
Angular template:
<!-- Host element — the checkout renders inside this div -->
<div id="tonder-checkout"></div>
<button (click)="pay()" [disabled]="loading">
{{ loading ? 'Processing…' : 'Pay' }}
</button>Angular component:
import { Component, OnInit, OnDestroy } from '@angular/core';
import { InlineCheckout } from '@tonder.io/ionic-full-sdk';
@Component({ selector: 'app-checkout', templateUrl: './checkout.component.html' })
export class CheckoutComponent implements OnInit, OnDestroy {
private inlineCheckout!: InlineCheckout;
loading = false;
async ngOnInit() {
// Step 1 — Create instance
this.inlineCheckout = new InlineCheckout({
apiKey: 'YOUR_PUBLIC_API_KEY',
mode: 'stage',
returnUrl: `${window.location.origin}/checkout`,
});
// Step 2 — Fetch secure token from YOUR backend
// Your backend calls POST https://stage.tonder.io/api/secure-token/ with the secret key
// and returns { access: string } to the client. Never expose the secret key on the frontend.
const { access } = await fetch('/api/tonder-secure-token', { method: 'POST' })
.then(r => r.json());
// Step 3 — Configure with customer + token
this.inlineCheckout.configureCheckout({
customer: { email: '[email protected]' },
secureToken: access,
});
// Step 4 — Render the checkout form
await this.inlineCheckout.injectCheckout();
// Step 5 — Check for a returning 3DS redirect
await this.inlineCheckout.verify3dsTransaction();
}
ngOnDestroy() {
this.inlineCheckout.removeCheckout();
}
async pay() {
this.loading = true;
try {
const response = await this.inlineCheckout.payment({
customer: { firstName: 'John', lastName: 'Doe', email: '[email protected]', phone: '+525512345678' },
cart: { total: 100, items: [{ name: 'Product', description: 'Desc', quantity: 1, price_unit: 100, discount: 0, taxes: 0, product_reference: 'SKU-001', amount_total: 100 }] },
currency: 'MXN',
});
console.log('Transaction status:', response.transaction_status);
} finally {
this.loading = false;
}
}
}Security note:
YOUR_SECRET_API_KEYis a server-side credential. Fetch the secure token from your own backend and return only theaccessvalue to the client.
2. Installation
npm install @tonder.io/ionic-full-sdk
# or
yarn add @tonder.io/ionic-full-sdkimport { InlineCheckout } from '@tonder.io/ionic-full-sdk';3. Constructor & Configuration
3.1 Options reference
Pass these options when constructing new InlineCheckout({ ... }):
| Property | Type | Required | Default | Description |
|---|---|:---:|---|---|
| apiKey | string | ✓ | — | Public API key from the Tonder Dashboard |
| mode | 'stage' \| 'production' | | 'stage' | Environment. 'production' routes to the production Tonder API and activates the Skyflow PROD vault — tokens created in one environment are not valid in another |
| returnUrl | string | | — | URL the browser returns to after a 3DS redirect (see Section 6) |
| styles | ICardStyles | | — | Deprecated — use customization.styles instead (see Section 9) |
| containerId | string | | 'tonder-checkout' | id of the host DOM element the checkout renders inside |
| collectorIds | CollectorIds | | (built-in defaults) | Override internal iframe container IDs. Only needed when running multiple checkout instances on the same page |
| callBack | (response?) => void | | — | Called after a successful payment |
| renderPaymentButton | boolean | | false | Show the built-in Tonder pay button inside the checkout |
| renderSaveCardButton | boolean | | false | Show the built-in save-card button inside the checkout |
| customization | IInlineCustomizationOptions | | — | UI and behavior overrides (see Section 3.2) |
| events | IEvents | | — | Event handlers for card form inputs (see Section 3.3) |
| isEnrollmentCard | boolean | | false | Use the SDK as enrollment-only (card saving without payment) |
3.2 Customization options
IInlineCustomizationOptions controls built-in UI behavior and card field styles:
| Property | Type | Default | Description |
|---|---|---|---|
| styles | ICardStyles | — | Card field styles. See Section 9 |
| saveCards.showSaved | boolean | true | Show the saved-cards list inside the checkout |
| saveCards.showSaveCardOption | boolean | true | Show the "Save this card" checkbox |
| saveCards.autoSave | boolean | false | Automatically save the card without showing the checkbox |
| paymentButton.show | boolean | false | Show the built-in pay button |
| paymentButton.text | string | 'Pagar' | Label for the built-in pay button |
| paymentButton.showAmount | boolean | true | Show the transaction amount inside the pay button |
Example:
new InlineCheckout({
apiKey: 'YOUR_KEY',
customization: {
styles: {
cardForm: {
inputStyles: { base: { border: '1px solid #d1d1d6', borderRadius: '8px' } },
},
},
saveCards: {
showSaved: true,
showSaveCardOption: true,
autoSave: false,
},
paymentButton: {
show: true,
text: 'Complete Payment',
showAmount: true,
},
},
});Important: To use the save-card feature you must provide a
secureTokenviaconfigureCheckout(). See the Tonder SecureToken guide.
3.3 Form events
Subscribe to input events on individual card fields via the events option.
Available event objects:
| Event key | Field |
|---|---|
| cardHolderEvents | Cardholder name |
| cardNumberEvents | Card number |
| cvvEvents | CVV |
| monthEvents | Expiration month |
| yearEvents | Expiration year |
Each event object supports onChange, onFocus, and onBlur callbacks. All three receive an IEventSecureInput payload:
| Property | Type | Description |
|---|---|---|
| elementType | string | Field identifier ('CARDHOLDER_NAME', 'CARD_NUMBER', 'CVV', 'EXPIRATION_MONTH', 'EXPIRATION_YEAR') |
| isEmpty | boolean | true when the field contains no input |
| isFocused | boolean | true when the field has keyboard focus |
| isValid | boolean | true when the value passes all validation rules |
| value | string | Current field value — see availability table below |
value availability by environment:
| Field | mode: 'stage' | mode: 'production' |
|---|---|---|
| cardholder_name | Full value | Full value |
| card_number | Full value | '' (masked by Skyflow) |
| cvv | Full value | '' (masked by Skyflow) |
| expiration_month | Full value | Full value |
| expiration_year | Full value | Full value |
Example:
new InlineCheckout({
apiKey: 'YOUR_KEY',
events: {
cardHolderEvents: {
onChange: (e) => console.log('Holder name changed — valid:', e.isValid),
onBlur: (e) => { if (!e.isValid) showError('Invalid cardholder name'); },
},
cardNumberEvents: {
onChange: (e) => console.log('Card number value:', e.value),
onFocus: (e) => clearErrors(),
},
cvvEvents: {
onBlur: (e) => { if (e.isEmpty) showError('CVV is required'); },
},
},
});4. Initialization Sequence
Follow these steps on every page load that hosts the checkout:
1. Create the instance
this.inlineCheckout = new InlineCheckout({
apiKey: 'YOUR_PUBLIC_API_KEY',
mode: 'stage', // 'production' in production — activates Skyflow PROD vault
returnUrl: `${window.location.origin}/checkout`,
});2. Fetch a secure token from your backend Environment URLs:
| mode | Base URL |
|--------|----------|
| 'stage' | https://stage.tonder.io |
| 'production' | https://app.tonder.io |
// Your backend endpoint calls POST https://stage.tonder.io/api/secure-token/
// with Authorization: Token YOUR_SECRET_API_KEY and returns { access: string }
const { access } = await fetch('/api/tonder-secure-token', { method: 'POST' })
.then(r => r.json());3. Configure with customer data and token
this.inlineCheckout.configureCheckout({
customer: { email: '[email protected]' },
secureToken: access,
// cart: { total: 100, items: [...] }, // set here or pass to payment()
// currency: 'MXN',
// order_reference: 'ORD-001',
// metadata: { internal_id: 'abc' },
});4. Render the checkout
await this.inlineCheckout.injectCheckout();5. Handle a returning 3DS redirect
Call verify3dsTransaction() on every page load. It resolves immediately if this is not a 3DS return:
const tdsResult = await this.inlineCheckout.verify3dsTransaction();
if (tdsResult) {
const status = (tdsResult as any).transaction_status;
if (status === 'Success') {
// Navigate to confirmation page
} else {
// Show error to user
}
}6. Trigger payment from your button
const response = await this.inlineCheckout.payment({ ...paymentData });
console.log(response.transaction_status); // 'Success' | 'Pending' | 'Declined' | 'Failed'5. Processing Payments
5.1 Standard payment
const response = await this.inlineCheckout.payment({
customer: {
firstName: 'John',
lastName: 'Doe',
country: 'MX',
street: '123 Main St',
city: 'Mexico City',
state: 'CDMX',
postCode: '06600',
email: '[email protected]',
phone: '+525512345678',
},
cart: {
total: 100,
items: [
{
name: 'Product Name',
description: 'Product description',
quantity: 1,
price_unit: 100,
discount: 0,
taxes: 0,
product_reference: 'SKU-001',
amount_total: 100,
},
],
},
currency: 'MXN',
metadata: { order_id: 'ORDER-123' },
// apm_config: {} // Optional — only for APMs like Mercado Pago (see Section 5.3)
});5.2 Enrollment — saveCard()
Use isEnrollmentCard: true to run the SDK as a card-saving form without triggering a payment.
this.inlineCheckout = new InlineCheckout({
apiKey: 'YOUR_KEY',
isEnrollmentCard: true,
});
// Configure with customer and secureToken (required for saving cards)
this.inlineCheckout.configureCheckout({
customer: { email: '[email protected]' },
secureToken: access,
});
await this.inlineCheckout.injectCheckout();
// Later — save the card entered by the user
const skyflowId = await this.inlineCheckout.saveCard();
console.log('Saved card skyflow_id:', skyflowId);5.3 APM config (Mercado Pago)
Pass apm_config inside payment() when using Mercado Pago or other APMs that require additional configuration.
| Field | Type | Description |
|---|---|---|
| binary_mode | boolean | If true, payment must be approved or rejected immediately (no pending) |
| additional_info | string | Extra info shown during checkout and in payment details |
| back_urls.success | string | Redirect URL after successful payment |
| back_urls.pending | string | Redirect URL after pending payment |
| back_urls.failure | string | Redirect URL after failed/cancelled payment |
| auto_return | 'approved' \| 'all' | Enables automatic redirection after payment completion |
| payment_methods.excluded_payment_methods[].id | string | Payment method ID to exclude (e.g. 'visa') |
| payment_methods.excluded_payment_types[].id | string | Payment type ID to exclude (e.g. 'ticket') |
| payment_methods.default_payment_method_id | string | Default payment method (e.g. 'master') |
| payment_methods.installments | number | Maximum number of installments allowed |
| payment_methods.default_installments | number | Default number of installments suggested |
| expires | boolean | Whether the preference has an expiration |
| expiration_date_from | string (ISO 8601) | Start of validity period |
| expiration_date_to | string (ISO 8601) | End of validity period |
| statement_descriptor | string | Text on payer's card statement (max 16 characters) |
| marketplace | string | Marketplace identifier (default: 'NONE') |
| marketplace_fee | number | Fee to collect as marketplace commission |
| differential_pricing.id | number | Differential pricing strategy ID |
| shipments.mode | 'custom' \| 'me2' \| 'not_specified' | Shipping mode |
| shipments.local_pickup | boolean | Enable local branch pickup |
| shipments.cost | number | Shipping cost (custom mode only) |
| shipments.free_shipping | boolean | Free shipping flag (custom mode only) |
| tracks[].type | 'google_ad' \| 'facebook_ad' | Ad tracker type |
| tracks[].values.conversion_id | string | Google Ads conversion ID |
| tracks[].values.pixel_id | string | Facebook Pixel ID |
5.4 Payment response reference
interface IStartCheckoutResponse {
status: string;
message: string;
transaction_status: string; // 'Success' | 'Pending' | 'Declined' | 'Failed'
transaction_id: number;
payment_id: number;
checkout_id: string;
is_route_finished: boolean;
provider: string;
psp_response: Record<string, any>; // Raw response from the payment processor
}6. 3DS Handling
When a payment requires 3DS authentication, the SDK handles the challenge automatically. Two display modes are available, controlled by redirectOnComplete inside customization:
| redirectOnComplete | Challenge display | User experience |
|---|---|---|
| true (default) | Full-page redirect to bank's 3DS page | User leaves the app; returns to returnUrl after completing auth |
| false | Rendered inside the checkout iframe | User stays in the app until the challenge resolves |
Note: Unlike
@tonder.io/ionic-lite-sdk, you do not need to add any iframe element or CSS to your template.injectCheckout()renders the 3DS iframe automatically as part of the checkout HTML.
redirectOnComplete: true (default — full-page redirect)
Set returnUrl in the constructor. Call verify3dsTransaction() on every page load — the SDK detects whether this load is a 3DS return and handles it automatically.
const tdsResult = await this.inlineCheckout.verify3dsTransaction();
if (tdsResult) {
const status = (tdsResult as any).transaction_status;
// 'Success' | 'Pending' | 'Declined' | 'Cancelled' | 'Failed' | 'Authorized'
}redirectOnComplete: false (iframe mode — user stays in app)
Recommended for Ionic / mobile WebViews where a full-page redirect would break the navigation flow. Set the option in customization — no additional HTML or CSS needed:
this.inlineCheckout = new InlineCheckout({
apiKey: 'YOUR_PUBLIC_API_KEY',
mode: 'production',
customization: {
redirectOnComplete: false,
},
});In this mode the payment() promise resolves directly when the 3DS challenge completes — verify3dsTransaction() is not required.
7. Managing Saved Cards
7.1 Save a card
saveCard() tokenizes the card data entered in the form and saves it to the customer's account. It returns the skyflow_id string.
saveCard(): Promise<string>Prerequisites:
- The SDK must be initialized with
isEnrollmentCard: trueor the checkout must have the save-card UI visible - A valid
secureTokenmust be set viaconfigureCheckout()
// Get a secure token for saving cards
const { access } = await fetch('/api/tonder-secure-token', {
method: 'POST',
}).then(r => r.json());
this.inlineCheckout.configureCheckout({
customer: { email: '[email protected]' },
secureToken: token,
});
const skyflowId = await this.inlineCheckout.saveCard();
console.log('Card saved with skyflow_id:', skyflowId);7.2 Card On File (subscription_id)
Card On File is a feature that Tonder activates on your merchant account. When active, saved cards receive a subscription_id — these cards do not require CVV entry on subsequent payments. The SDK handles this automatically in the checkout UI.
What happens under the hood:
| subscription_id present | CVV prompt shown | Behavior |
|---|:---:|---|
| Yes | No | Payment proceeds directly without CVV |
| No | Yes | SDK shows CVV entry before processing payment |
Existing cards without subscription_id:
Cards saved before Card On File was enabled may not have subscription_id. Options:
- Remove the saved card and ask the user to re-add it
- Run a payment with the card — the SDK creates a subscription and updates the card (note: this may generate a new
skyflow_id) - Contact Tonder support to reset saved cards for a user
If Card On File is not enabled for your merchant, contact Tonder support.
8. Error Handling
Public SDK methods that fail return an AppError object with name: 'TonderError'.
8.1 Error structure
{
"status": "error",
"name": "TonderError",
"code": "PAYMENT_PROCESS_ERROR",
"message": "There was an issue processing the payment.",
"statusCode": 500,
"details": {
"code": "PAYMENT_PROCESS_ERROR",
"statusCode": 500,
"systemError": "APP_INTERNAL_001"
}
}statusCodecomes from the HTTP response when available; defaults to500details.systemErrorcomes from the backend error code when available; defaults toAPP_INTERNAL_001
Usage:
try {
const response = await this.inlineCheckout.payment(data);
} catch (error: any) {
if (error.name === 'TonderError') {
console.error(`[${error.code}] ${error.message}`);
}
}8.2 Error code reference
| Method | Possible error.code |
|---|---|
| payment() | PAYMENT_PROCESS_ERROR | CARD_ON_FILE_DECLINED |
| saveCard() | SAVE_CARD_ERROR | CARD_ON_FILE_DECLINED |
9. Customization & Styling
9.1 Global form styles — cardForm
Pass styles inside customization.styles using ICardStyles. Use cardForm for styles applied to all fields:
new InlineCheckout({
apiKey: 'YOUR_KEY',
customization: {
styles: {
// Show the card brand icon inside the card number field (default: true)
enableCardIcon: true,
// Global styles — applied to all fields as a baseline
cardForm: {
inputStyles: {
base: {
fontFamily: '"Inter", sans-serif',
fontSize: '16px',
color: '#1d1d1d',
border: '1px solid #e0e0e0',
borderRadius: '5px',
padding: '10px 7px',
marginTop: '2px',
backgroundColor: 'white',
'&::placeholder': { color: '#ccc' },
},
focus: {
borderColor: '#6200ee',
boxShadow: '0 0 0 3px rgba(98, 0, 238, 0.15)',
outline: 'none',
},
complete: { borderColor: '#4caf50' },
invalid: { border: '1px solid #f44336' },
cardIcon: {
position: 'absolute',
left: '6px',
bottom: 'calc(50% - 12px)',
},
global: {
'@import': 'url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;700&display=swap")',
},
},
labelStyles: {
base: { fontSize: '12px', fontWeight: '500', fontFamily: '"Inter", sans-serif' },
},
errorStyles: {
base: { color: '#f44336', fontSize: '12px', fontWeight: '500' },
},
},
},
},
});ICardStyles shape:
{
enableCardIcon?: boolean, // default: true — show card brand icon in card number field
cardForm?: ICardFieldStyles, // Global — lowest priority (all fields)
cardholderName?: ICardFieldStyles,
cardNumber?: ICardFieldStyles,
cvv?: ICardFieldStyles,
expirationMonth?: ICardFieldStyles,
expirationYear?: ICardFieldStyles,
labels?: {
nameLabel?: string;
cardLabel?: string;
cvvLabel?: string;
expiryDateLabel?: string;
};
placeholders?: {
namePlaceholder?: string;
cardPlaceholder?: string;
cvvPlaceholder?: string;
expiryMonthPlaceholder?: string;
expiryYearPlaceholder?: string;
};
}
// ICardFieldStyles:
{
inputStyles?: {
base?: Record<string, any>;
focus?: Record<string, any>;
complete?: Record<string, any>;
invalid?: Record<string, any>;
empty?: Record<string, any>;
cardIcon?: Record<string, any>; // Card brand icon position (card_number only)
global?: Record<string, any>; // CSS @import / global rules
};
labelStyles?: { base?: Record<string, any> };
errorStyles?: { base?: Record<string, any> }; // developer-facing key
}Backward compatibility: Passing
stylesat the root ofInlineCheckoutoptions is still supported but deprecated — move it tocustomization.styles. See Section 12.
9.2 Per-field overrides
Per-field keys have higher priority than cardForm. Only the properties you specify are overridden — everything else falls back to cardForm or the SDK defaults.
Priority order: per-field → cardForm → SDK defaults
new InlineCheckout({
apiKey: 'YOUR_KEY',
customization: {
styles: {
// Baseline for all fields
cardForm: {
inputStyles: {
base: { border: '1px solid #e0e0e0', borderRadius: '5px', padding: '10px 7px' },
},
},
// Override only the card number field
cardNumber: {
inputStyles: {
base: { letterSpacing: '2px' },
invalid: { borderColor: '#e74c3c', color: '#e74c3c' },
},
},
// Override only the CVV field
cvv: {
inputStyles: {
base: { backgroundColor: '#faf5ff' },
},
errorStyles: {
base: { color: '#9b59b6' },
},
},
},
},
});Available field keys: cardholderName, cardNumber, cvv, expirationMonth, expirationYear.
9.3 Labels & placeholders
Customize field labels and placeholder text:
new InlineCheckout({
apiKey: 'YOUR_KEY',
customization: {
styles: {
labels: {
nameLabel: 'Cardholder Name',
cardLabel: 'Card Number',
cvvLabel: 'CVV',
expiryDateLabel: 'Expiration Date',
},
placeholders: {
namePlaceholder: 'Name as it appears on the card',
cardPlaceholder: '1234 1234 1234 1234',
cvvPlaceholder: '3-4 digits',
expiryMonthPlaceholder: 'MM',
expiryYearPlaceholder: 'YY',
},
},
},
});9.4 CSS container classes
The SDK generates the checkout HTML with the following CSS classes. Use these in your global styles to customize the checkout container and card list layout:
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;700&display=swap");
.container-tonder {
background-color: #f9f9f9;
margin: 0 auto;
padding: 30px 10px;
overflow: hidden;
transition: max-height 0.5s ease-out;
max-width: 600px;
border: solid 1px #e3e3e3;
}
.collect-row {
display: flex;
justify-content: space-between;
width: 100%;
}
.collect-row > :first-child {
min-width: 120px;
}
.expiration-year {
padding-top: 25px;
}
.empty-div {
height: 80px;
margin: 2px 10px 4px;
}
.error-container {
color: red;
background-color: #ffdbdb;
margin-bottom: 13px;
font-size: 80%;
padding: 8px 10px;
border-radius: 10px;
text-align: left;
}
.message-container {
color: green;
background-color: #90ee90;
margin-bottom: 13px;
font-size: 80%;
padding: 8px 10px;
border-radius: 10px;
text-align: left;
}
.pay-button {
font-size: 16px;
font-weight: bold;
min-height: 2.3rem;
border-radius: 0.5rem;
cursor: pointer;
width: 100%;
padding: 1rem;
border: none;
background-color: #000;
color: #fff;
margin-bottom: 10px;
display: none;
}
.pay-button:disabled {
background-color: #b9b9b9;
}
.cards-list-container {
display: flex;
flex-direction: column;
padding: 0 10px;
gap: 33% 20px;
}
.card-item {
display: flex;
justify-content: start;
align-items: center;
gap: 33% 20px;
}
.card-item .card-number,
.card-item .card-expiration {
font-size: 16px;
font-family: "Inter", sans-serif;
}
.checkbox {
margin: 10px 0;
width: 100%;
text-align: left;
}
.checkbox label {
margin-left: 10px;
font-size: 12px;
font-weight: 500;
color: #1d1d1d;
font-family: "Inter", sans-serif;
}10. Mobile Settings
Android: Add the Internet permission to your AndroidManifest.xml:
<!-- Required to fetch data from the internet -->
<uses-permission android:name="android.permission.INTERNET" />11. API Reference
| Method | Returns | Description |
|---|---|---|
| configureCheckout(data) | void | Set customer, secureToken, cart, currency, and metadata |
| injectCheckout() | Promise<void> | Render the checkout form into the DOM |
| payment(data) | Promise<IStartCheckoutResponse> | Process a payment |
| verify3dsTransaction() | Promise<ITransaction \| IStartCheckoutResponse \| void> | Verify a 3DS result on page return; resolves immediately if not a 3DS return |
| saveCard() | Promise<string> | Save the card entered in the form; returns skyflow_id |
| removeCheckout() | void | Remove the checkout from the DOM and clean up resources |
12. Deprecated API
| Deprecated | Alternative |
|---|---|
| new InlineCheckout({ styles: { ... } }) | new InlineCheckout({ customization: { styles: { ... } } }) |
| setCustomerEmail(email) | configureCheckout({ customer: { email } }) |
| setCartTotal(total) | configureCheckout({ cart: { total } }) |
| setPaymentData(data) | configureCheckout(data) |
13. License
Copyright © Tonder. All rights reserved.
