PCI DSS–compliant payment SDK for Ionic, Angular, and React. Card data is collected through Skyflow secure iframes — raw card values never touch your application code.
- Quick Start
- Installation
- Constructor & Configuration
- Initialization Sequence
- Collecting Card Data —
mountCardFields - Processing Payments
- 3DS Handling
- Managing Saved Cards
- Revealing Card Data —
revealCardFields - Error Handling
- Customization & Styling
- Deprecated API
Get a working payment form in under 5 minutes. This example uses the minimum required setup. See Section 4 for the full step-by-step explanation.
Angular template:
<!-- 3DS iframe — only add when using redirectOnComplete: false (see Section 7). Remove this element if you are using the default redirectOnComplete: true. --><iframeid="tdsIframe" allowtransparency="true" class="tds-iframe"></iframe><!-- Secure iframes — card values never touch your code --><divid="collect_cardholder_name"></div><divid="collect_card_number"></div><divid="collect_expiration_month"></div><divid="collect_expiration_year"></div><divid="collect_cvv"></div><button(click)="pay()">Pay</button>Angular component:
import{Component,OnInit}from'@angular/core';import{LiteCheckout,AppError}from'@tonder.io/ionic-lite-sdk';
@Component({selector: 'app-checkout',templateUrl: './checkout.component.html'})exportclassCheckoutComponentimplementsOnInit{privateliteCheckout!: LiteCheckout;loading=false;asyncngOnInit(){// Step 1 — Create instancethis.liteCheckout=newLiteCheckout({apiKey: 'YOUR_PUBLIC_API_KEY',mode: 'stage',returnUrl: `${window.location.origin}/checkout`,});// Step 2 — Fetch secure token from YOUR backend (never expose YOUR_SECRET_API_KEY on the frontend)// Your backend calls POST https://stage.tonder.io/api/secure-token/ with the secret key// and returns { access: string } to the client.const{ access }=awaitfetch('/api/tonder-secure-token',{method: 'POST',}).then(r=>r.json());// Step 3 — Configure with customer + tokenthis.liteCheckout.configureCheckout({customer: {email: 'user@example.com'},secureToken: access,// cart: { total: 100, items: [...] },// currency: 'MXN',// order_reference: 'ORD-001', // your internal order ID — shows in Tonder dashboard & exports// metadata: { order_id: 'ORD-001' }, // reporting metadata — see Section 6.1});// Step 4 — Initialize checkout (must be awaited)awaitthis.liteCheckout.injectCheckout();// Step 5 — Check if returning from a 3DS redirect// Only needed when redirectOnComplete: true (default). In iframe mode// (redirectOnComplete: false) the payment() promise resolves directly — skip this step.// If this page load is a return from a 3DS challenge, the SDK verifies the// transaction and, if routing is configured, may automatically retry with// the next payment route. Await the result before deciding what to do next.consttdsResult=awaitthis.liteCheckout.verify3dsTransaction();if(tdsResult){conststatus=(tdsResultasany).transaction_status;if(status==='Success'){// navigate to order confirmation}else{// show error to the user}return;// do not mount card fields — the payment flow already completed}// Step 6 — Normal page load: mount secure card input iframesawaitthis.liteCheckout.mountCardFields({fields: ['cardholder_name','card_number','expiration_month','expiration_year','cvv'],});}asyncpay(){this.loading=true;try{constresponse=awaitthis.liteCheckout.payment({customer: {email: 'user@example.com'},cart: {total: 100,items: [{name: 'Product A',description: 'Product description',quantity: 1,price_unit: 100,discount: 0,taxes: 0,product_reference: 'SKU-001',amount_total: 100,}],},currency: 'MXN',// order_reference: 'ORD-001', // your internal order ID — shows in Tonder dashboard & exports// metadata: { order_id: 'ORD-001' }, // reporting metadata — see Section 6.1});console.log('Transaction status:',response.transaction_status);}catch(error){if(errorinstanceofAppError){console.error(`[${error.code}] ${error.message}`);}}}}Security note: In this example
YOUR_SECRET_API_KEYis hardcoded for brevity. In production, move thisfetchcall to your own backend and return only theaccesstoken to the frontend. See Section 4.
npm install @tonder.io/ionic-lite-sdk
# or
yarn add @tonder.io/ionic-lite-sdkimport{LiteCheckout}from'@tonder.io/ionic-lite-sdk';constliteCheckout=newLiteCheckout(options);| Property | Type | Required | Default | Description |
|---|---|---|---|---|
apiKey | string | Required | — | Public API key from the Tonder Dashboard |
mode | 'stage' | 'production' | Required | 'stage' | Target environment |
returnUrl | string | Required for 3DS | — | URL to which 3DS redirects return after authentication |
callBack | (response) => void | Optional | undefined | Called after a successful payment or card enrollment |
customization | ILiteCustomizationOptions | Optional | undefined | Styles, labels, placeholders, and redirect behavior |
events | ICardFormEvents | Optional | undefined | onChange / onFocus / onBlur callbacks per field |
tdsIframeId | string | Optional | 'tdsIframe' | DOM id of the 3DS <iframe> element |
Full example:
constliteCheckout=newLiteCheckout({apiKey: 'YOUR_PUBLIC_API_KEY',mode: 'production',returnUrl: 'https://myapp.com/checkout',callBack: (response)=>console.log('Payment done',response),tdsIframeId: 'myCustomTdsFrame',customization: {redirectOnComplete: false,// render 3DS challenge inside #tdsIframe instead of full-page redirectstyles: {enableCardIcon: true},},events: {cardNumberEvents: {onChange: (e)=>console.log('Card number valid:',e.isValid),},},});interfaceILiteCustomizationOptions{styles?: IStyles;// Field and form visual styles (see Section 11)labels?: IFormLabels;// Label text per fieldplaceholders?: IFormPlaceholder;// Placeholder text per fieldredirectOnComplete?: boolean;// default: true}redirectOnComplete controls how 3DS challenges are displayed when the payment processor requires authentication:
| Value | Behavior |
|---|---|
true (default) | SDK performs a full-page redirect to the 3DS challenge URL. The user leaves your app, completes authentication on the bank's page, and is sent back to returnUrl. |
false | The 3DS challenge is rendered inside the #tdsIframe element. The user stays in your app until the challenge resolves. |
Use
redirectOnComplete: falsewhen you want to keep the user inside the app (e.g., in a mobile WebView). Make sure the#tdsIframeis styled to cover the screen when active — see Section 7.
Register callbacks to react to field state changes (validation, focus, etc.):
interfaceICardFormEvents{cardHolderEvents?: IInputEvents;cardNumberEvents?: IInputEvents;cvvEvents?: IInputEvents;monthEvents?: IInputEvents;yearEvents?: IInputEvents;}interfaceIInputEvents{onChange?: (event: IEventSecureInput)=>void;onFocus?: (event: IEventSecureInput)=>void;onBlur?: (event: IEventSecureInput)=>void;}interfaceIEventSecureInput{elementType: string;// e.g. 'CARD_NUMBER', 'CVV', 'CARDHOLDER_NAME'isEmpty: boolean;isFocused: boolean;isValid: boolean;value?: string;// See PCI note below}PCI note: In
production,card_numberreturns a partially masked value (first 8 digits for non-AMEX, first 6 for AMEX, rest masked). All other fields — includingcvv— returnvalue: ''. Instage,development, andsandbox, all fields return the actual value. UseisValidandisEmptyfor UI state logic — never depend onvaluefor business logic in production.
Example — live card form validation:
events: {cardNumberEvents: {onChange: (e)=>{this.cardNumberValid=e.isValid;},onBlur: (e)=>{this.showCardNumberError=!e.isValid&&!e.isEmpty;},},cvvEvents: {onChange: (e)=>{this.cvvValid=e.isValid;},},}The SDK must be initialized in this exact order before any other method is called:
1. new LiteCheckout(options)
↓
2. fetch baseUrl/api/secure-token/ → { access: string }
↓
3. configureCheckout({ customer, secureToken, ...optional })
↓
4. await injectCheckout() (must be awaited)
↓
5. result = await verify3dsTransaction() (redirectOnComplete: true only — void on normal loads)
↓ if result → handle and return early; if void → continue ↓
6. await mountCardFields(...) (mounts secure iframes into your divs)
Base URL by environment:
mode | Base URL |
|---|---|
'stage' | https://stage.tonder.io |
'production' | https://app.tonder.io |
The secure token is a short-lived credential required for configureCheckout. Fetch it from Tonder's API using your secret API key in the Authorization header:
const{ access }=awaitfetch(`${baseUrl}/api/secure-token/`,{method: 'POST',headers: {'Authorization': 'Token YOUR_SECRET_API_KEY','Content-Type': 'application/json',},}).then(r=>r.json());Security note:
YOUR_SECRET_API_KEYis a server-side credential. In production, make this request from your own backend and return only theaccesstoken to the frontend. Never expose your secret key in client-side code.
Initializes the checkout session. Must be awaited before calling mountCardFields.
awaitliteCheckout.injectCheckout();Important: Calling
mountCardFields()beforeinjectCheckout()resolves will throwSKYFLOW_NOT_INITIALIZED.
Sets the customer identity and secure token for the current session. Fields like cart, currency, metadata, and order_reference can also be passed here as defaults — any field provided again in payment() will override them.
interfaceIConfigureCheckout{customer: {email: string}|ICustomer;// Required — minimum: { email }secureToken: string;// Required — from the token fetchcart?: {total: number|string;items: IItem[]};currency?: string;order_reference?: string;// your internal order ID — shown in Tonder dashboard & exportsmetadata?: Record<string,any>;// reporting fields — see Section 6.1card?: string;// skyflow_id — pre-selects a saved card for payment()payment_method?: string;// APM identifier — pre-selects an APM for payment()}liteCheckout.configureCheckout({customer: {email: 'user@example.com'},secureToken: access,});Only relevant when redirectOnComplete: true (the default). When using redirectOnComplete: false (iframe mode), payment() resolves the promise directly after the challenge completes — skip this call entirely.
When using the default mode, call this on every page load. If the page was loaded as a return from a 3DS redirect, it verifies the transaction and — if the merchant has routing configured and the transaction was declined — automatically retries with the next payment route. Resolves with the final transaction result, or void on a normal (non-3DS) page load.
constresult=awaitliteCheckout.verify3dsTransaction();if(result){// Returning from 3DS — routing may have been applied automaticallyconststatus=(resultasany).transaction_status;if(status==='Success'){// navigate to confirmation}else{// show error}return;// do not proceed to mountCardFields}// Normal page load — continue with checkout initializationawaitliteCheckout.mountCardFields({ ... });Mounts secure iframes into your <div> containers. Card values are captured directly inside the iframe and never pass through your application code.
Prerequisite: The container
<div>elements must exist in the DOM before callingmountCardFields().
interfaceIMountCardFieldsRequest{fields: (CardField|{field: CardField;container_id?: string})[];card_id?: string;// Omit for new card; provide skyflow_id for saved-card CVVunmount_context?: 'all'|'current'|'create'|string;// default: 'all'}typeCardField=|'cardholder_name'|'card_number'|'expiration_month'|'expiration_year'|'cvv';Default container IDs (used when no custom container_id is provided):
| Field | Default Container ID |
|---|---|
cardholder_name | #collect_cardholder_name |
card_number | #collect_card_number |
expiration_month | #collect_expiration_month |
expiration_year | #collect_expiration_year |
cvv | #collect_cvv |
HTML:
<divid="collect_cardholder_name"></div><divid="collect_card_number"></div><divid="collect_expiration_month"></div><divid="collect_expiration_year"></div><divid="collect_cvv"></div>TypeScript — shorthand (string array):
awaitliteCheckout.mountCardFields({fields: ['cardholder_name','card_number','expiration_month','expiration_year','cvv'],});TypeScript — custom container IDs:
awaitliteCheckout.mountCardFields({fields: [{field: 'cardholder_name',container_id: '#my-name'},{field: 'card_number',container_id: '#my-card-number'},{field: 'expiration_month',container_id: '#my-month'},{field: 'expiration_year',container_id: '#my-year'},{field: 'cvv',container_id: '#my-cvv'},],});For saved-card payments, mount only the CVV field for the selected card. The default container ID is #collect_cvv_<skyflow_id>.
<!-- Use the card's skyflow_id as part of the container id --><divid="collect_cvv_abc123"></div>liteCheckout.mountCardFields({fields: ['cvv'],card_id: 'abc123',// card.fields.skyflow_id});Note: Cards with
subscription_iddo not require CVV entry. See Section 8.4.
Conditional CVV mount pattern:
handleSelectCard(card: ICard){if(this.selectedCard?.fields?.skyflow_id===card.fields.skyflow_id)return;this.selectedCard=card;// Only mount CVV for cards that don't have a Card On File subscriptionif(!card.fields.subscription_id){this.liteCheckout.mountCardFields({fields: ['cvv'],card_id: card.fields.skyflow_id,});}}mountCardFields() automatically unmounts previously mounted fields before mounting new ones — you don't need to call unmountCardFields() manually when switching between cards or modes.
The unmount_context parameter on mountCardFields controls what gets cleared before the new fields are mounted:
unmount_context | What gets unmounted before mounting |
|---|---|
'all' (default) | All mounted fields across all contexts |
'current' | Only the current context (new-card or the active saved-card CVV) |
'create' | New-card form fields only |
'update:skyflow_id' | CVV field for a specific saved card |
The only case where you call unmountCardFields() directly is when navigating away from the checkout screen without remounting:
ngOnDestroy(){this.liteCheckout.unmountCardFields();}Important notes:
- Never show the new-card form (all 5 fields) and a saved-card CVV field simultaneously.
- Only one saved-card CVV input should be active at a time.
- Always
mountCardFields()and let the user fill in the fields before callingpayment()orsaveCustomerCard().
Prerequisites:Section 5.1 — all 5 card fields must be mounted and filled by the user.
interfaceIProcessPaymentRequest{customer: ICustomer|{email: string};// Requiredcart: {total: string|number;items: IItem[]};// Requiredcurrency?: string;// Optional — ISO code e.g. 'MXN'order_reference?: string|null;// Recommended — your internal order ID; shown in Tonder dashboard, filters, and exportsmetadata?: Record<string,any>;// Recommended — fields shown in Tonder transaction exports (see table below)isSandbox?: boolean;// Optional — Openpay sandbox modeapm_config?: Record<string,any>;// Optional — APM-specific config (Mercado Pago, etc.)// card — OMIT for new-card payment// payment_method — OMIT for card payment}To ensure proper visibility in Tonder's transaction and dispute reports, pass the following fields inside metadata. They are included in exported reports and help link transactions to customer activity, business users, and external systems.
| Field | Type | Report column | Description |
|---|---|---|---|
order_reference | string | Business Transaction ID | Merchant's internal order ID. Shown in Tonder dashboard filters and exports. Recommended on every payment. |
metadata.order_id | string | Business Transaction ID | Takes precedence over order_reference when both are provided. |
metadata.operation_date | Date | string | Customer ID (metadata) | Business operation date for reporting and reconciliation. |
metadata.customer_email | string | Customer Email | Overrides the email shown in reports. Falls back to customer.email if omitted. |
metadata.business_user | string | Business User (metadata) | Internal user or system that initiated the payment (e.g. POS terminal ID, cashier ID). |
metadata.customer_id | string | Customer ID (metadata) | Your internal customer identifier — correlates payments with customer records. |
Tip: At minimum, pass
order_referenceon every payment so your orders appear correctly in Tonder's dashboard and exports.
ICustomer:
typeICustomer={firstName: string;// RequiredlastName: string;// Requiredemail: string;// Requiredphone?: string;country?: string;street?: string;city?: string;state?: string;postCode?: string;address?: string;identification?: {type: string;number: string};};IItem:
interfaceIItem{name: string;description: string;quantity: number;price_unit: number;amount_total: number;discount: number;taxes: number;product_reference: string|number;}Example:
asyncpay(){this.loading=true;try{constresponse=awaitthis.liteCheckout.payment({customer: {firstName: 'John',lastName: 'Doe',email: 'john@example.com',phone: '+1 555 0100',country: 'MX',city: 'CDMX',street: '123 Main St',state: 'CMX',postCode: '06600',},cart: {total: 150,items: [{name: 'Product A',description: 'Product description',quantity: 1,price_unit: 150,discount: 0,taxes: 0,product_reference: 'SKU-001',amount_total: 150,}],},currency: 'MXN',metadata: {order_id: 'ORD-789'},order_reference: 'ORD-789',});console.log('Transaction status:',response.transaction_status);}catch(error){if(errorinstanceofAppError){console.error(error.code,error.message);this.errorMessage=error.message;}}finally{this.loading=false;}}Prerequisites: Fetch saved cards (Section 8.1). Conditionally mount the CVV field (Section 5.2).
constresponse=awaitthis.liteCheckout.payment({customer: {email: 'user@example.com'},cart: {total: 100,items: [...]},currency: 'MXN',card: selectedCard.fields.skyflow_id,// the only addition vs. new card});No mountCardFields() call is needed for APM payments.
// 1. Fetch available APMsconstapms=awaitthis.liteCheckout.getCustomerPaymentMethods();// IPaymentMethod = { id, payment_method, priority, category, icon, label }// 2. User selects an APM// 3. Payconstresponse=awaitthis.liteCheckout.payment({customer: {email: 'user@example.com'},cart: {total: 100,items: [...]},currency: 'MXN',payment_method: selectedApm.payment_method,// e.g. 'Spei'});Mercado Pago — apm_config:
Pass Mercado Pago-specific preferences via apm_config:
constresponse=awaitthis.liteCheckout.payment({
...paymentData,payment_method: 'MercadoPago',apm_config: {back_urls: {success: 'https://myapp.com/success',pending: 'https://myapp.com/pending',failure: 'https://myapp.com/failure',},auto_return: 'approved',},});Full Mercado Pago apm_config fields
| Field | Type | Description |
|---|---|---|
binary_mode | boolean | If true, payment must be approved or rejected immediately (no pending state) |
additional_info | string | Extra info shown during checkout |
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/canceled payment |
auto_return | 'approved' | 'all' | Enable auto-redirect after payment completion |
payment_methods.excluded_payment_methods[].id | string | Payment method to exclude (e.g. 'visa') |
payment_methods.excluded_payment_types[].id | string | Payment type to exclude (e.g. 'ticket') |
payment_methods.default_payment_method_id | string | Default payment method (e.g. 'master') |
payment_methods.installments | number | Max installments allowed |
payment_methods.default_installments | number | Default 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 chars) |
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 |
interfaceIStartCheckoutResponse{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}Note: 3DS authentication is handled automatically by the SDK. You do not need to handle redirection yourself.
When a payment requires 3DS authentication, the SDK handles the challenge automatically. There are two display modes, controlled by redirectOnComplete:
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 #tdsIframe in your app | User stays in app until the challenge resolves |
No additional template changes required. Set returnUrl in the constructor and call verify3dsTransaction() on every page load — see Section 4 — verify3dsTransaction() for the full implementation pattern.
How it works:payment() redirects the browser to the bank's authentication page. After authentication, the bank sends the user back to returnUrl. On that page load, verify3dsTransaction() completes the verification — if routing is configured and the transaction was declined, it automatically retries with the next route.
Recommended for Ionic / mobile WebViews where a full-page redirect would break the app flow.
1. Add the iframe to your template:
<iframeid="tdsIframe" allowtransparency="true" class="tds-iframe"></iframe>2. Add CSS — hidden by default, shown full-screen when the challenge activates:
.tds-iframe {
display: none;
border: none;
position: fixed;
inset:0;
width:100vw;
height:100vh;
z-index:100;
background: white;
}In this mode the SDK resolves the original payment() promise directly when the challenge completes — verify3dsTransaction() is not needed.
getCustomerCards(): Promise<ICustomerCardsResponse>interfaceICustomerCardsResponse{user_id: number;cards: ICard[];}interfaceICard{fields: ICardSkyflowFields;icon?: string;// URL to card brand image}interfaceICardSkyflowFields{skyflow_id: string;card_number: string;// Masked — e.g. 'XXXX-XXXX-XXXX-4242'cardholder_name: string;expiration_month: string;// e.g. '12'expiration_year: string;// e.g. '25'card_scheme: string;// e.g. 'VISA', 'MASTERCARD'subscription_id?: string;// Present when Card On File is activated on your merchant account — see Section 8.4}Example:
const{ cards }=awaitthis.liteCheckout.getCustomerCards();cards.forEach((card)=>{constlastFour=card.fields.card_number.slice(-4);constexpiry=`${card.fields.expiration_month}/${card.fields.expiration_year}`;console.log(`${card.fields.card_scheme} •••• ${lastFour} — expires ${expiry}`);});Prerequisites: All 5 card fields must be mounted via mountCardFields() with no card_id, and the user must have filled them in.
saveCustomerCard(): Promise<ISaveCardResponse>// Returns: { skyflow_id: string; user_id: number }Complete enrollment flow:
// 1. Mount all 5 fields (see Section 5.1)awaitthis.liteCheckout.mountCardFields({fields: ['cardholder_name','card_number','expiration_month','expiration_year','cvv'],});// 2. User fills in the card form...// 3. Save the cardconstsaved=awaitthis.liteCheckout.saveCustomerCard();console.log('Saved card skyflow_id:',saved.skyflow_id);// 4. Optionally reveal the saved card data (see Section 9)awaitthis.liteCheckout.revealCardFields({fields: ['card_number','cardholder_name','expiration_month','expiration_year'],});removeCustomerCard(skyflowId: string): Promise<string>// skyflowId = card.fields.skyflow_id from getCustomerCards()awaitthis.liteCheckout.removeCustomerCard(card.fields.skyflow_id);// Refresh the card list after removalconst{ cards }=awaitthis.liteCheckout.getCustomerCards();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.
Conditional CVV rule:
subscription_id present | CVV required? | Action |
|---|---|---|
| Yes | No | Call payment() directly with card: skyflow_id |
| No | Yes | Mount CVV with mountCardFields({ fields: ['cvv'], card_id }) first |
constselectedCard=cards.find(c=>c.fields.skyflow_id===selectedId);if(!selectedCard.fields.subscription_id){// Need CVV — mount the fieldawaitthis.liteCheckout.mountCardFields({fields: ['cvv'],card_id: selectedCard.fields.skyflow_id,});}// Then payawaitthis.liteCheckout.payment({
...customerCartData,card: selectedCard.fields.skyflow_id,});Legacy cards without subscription_id
Cards saved before Card On File was enabled may not have subscription_id. You have three options:
- Remove the card with
removeCustomerCard()and let the user re-enroll. - Run a full new-card payment flow — the SDK creates a subscription and updates the card. Note: this may generate a new
skyflow_id; update any stored references in your app. - Contact Tonder support to migrate specific cards.
After a successful saveCustomerCard() or payment() with a new card, use revealCardFields() to display the card data in your UI through secure iframes — raw card values are never exposed to your application.
When to call: Only after a successful
saveCustomerCard()or new-cardpayment(). Calling it without a prior successful card operation throwsMOUNT_COLLECT_ERROR.
Default container IDs and fixed redaction:
| Field | Default Container ID | Redaction Applied |
|---|---|---|
card_number | #reveal_card_number | MASKED — e.g. 4111 11•• •••• 1234 |
cardholder_name | #reveal_cardholder_name | PLAIN_TEXT |
expiration_month | #reveal_expiration_month | PLAIN_TEXT |
expiration_year | #reveal_expiration_year | PLAIN_TEXT |
PCI note:
cvvcannot be revealed — PCI DSS Requirement 3.2.1 prohibits storing or displaying CVV post-authorization. Redaction levels are fixed by the SDK and cannot be overridden.
Note: Reveal elements only support
base,copyIcon, andglobalstyle variants (unlike Collect elements which also supportfocus,complete,invalid, etc.).
Types:
typeRevealableCardField='card_number'|'cardholder_name'|'expiration_month'|'expiration_year';interfaceIRevealCardFieldsRequest{fields: (RevealableCardField|IRevealCardField)[];styles?: IRevealElementStyles;// Applied to all fields unless overridden per-field}interfaceIRevealCardField{field: RevealableCardField;container_id?: string;// default: #reveal_<field>altText?: string;// Placeholder text shown before reveal() resolveslabel?: string;// Label rendered above the fieldstyles?: IRevealElementStyles;// Per-field override; takes priority over request.styles}interfaceIRevealElementStyles{inputStyles?: {base?: Record<string,any>;copyIcon?: Record<string,any>;global?: Record<string,any>;};labelStyles?: {base?: Record<string,any>;global?: Record<string,any>};errorTextStyles?: {base?: Record<string,any>;global?: Record<string,any>};}Example 1 — Basic (shorthand):
<divid="reveal_cardholder_name"></div><divid="reveal_card_number"></div><divid="reveal_expiration_month"></div><divid="reveal_expiration_year"></div>awaitthis.liteCheckout.saveCustomerCard();// Reveal immediately after savingawaitthis.liteCheckout.revealCardFields({fields: ['cardholder_name','card_number','expiration_month','expiration_year'],});// #reveal_card_number shows "4111 11•• •••• 1234"// #reveal_cardholder_name shows "John Doe"Example 2 — With altText and label per field:
awaitthis.liteCheckout.revealCardFields({fields: [{field: 'card_number',altText: '•••• •••• •••• ••••',label: 'Card Number'},{field: 'cardholder_name',altText: 'Loading…',label: 'Cardholder'},{field: 'expiration_month',label: 'Month'},{field: 'expiration_year',label: 'Year'},],});Example 3 — Custom styles:
awaitthis.liteCheckout.revealCardFields({fields: ['card_number','cardholder_name','expiration_month','expiration_year'],styles: {inputStyles: {base: {color: '#ffffff',fontFamily: '"Courier New", monospace',fontSize: '16px',background: 'transparent',letterSpacing: '2px',},},},});Example 4 — Custom container IDs:
awaitthis.liteCheckout.revealCardFields({fields: [{field: 'card_number',container_id: '#my-card-display'},{field: 'cardholder_name',container_id: '#my-name-display'},],});When a public SDK method fails, it throws an AppError instance with the following shape:
{
"name": "TonderError",
"status": "error",
"code": "PAYMENT_PROCESS_ERROR",
"message": "There was an issue processing the payment.",
"statusCode": 500,
"details": { ... }
}Notes:
statusCodereflects the HTTP status when available; defaults to500for non-HTTP errors.detailscontains additional context about the error when available.
TypeScript catch pattern:
import{AppError}from'@tonder.io/ionic-lite-sdk';try{constresponse=awaitthis.liteCheckout.payment(data);}catch(error){if(errorinstanceofAppError){console.error(`[${error.code}] ${error.message} (HTTP ${error.statusCode})`);// Use error.code to show a user-friendly message}}| Code | Thrown by | When |
|---|---|---|
PAYMENT_PROCESS_ERROR | payment() | Any payment failure |
CARD_ON_FILE_DECLINED | payment(), saveCustomerCard() | Card On File authorization declined (only when Card On File is active on your account) |
MOUNT_COLLECT_ERROR | mountCardFields(), revealCardFields() | Secure fields fail to mount, or revealCardFields() called without a prior successful card operation |
SAVE_CARD_ERROR | saveCustomerCard() | Any error during card save |
FETCH_CARDS_ERROR | getCustomerCards() | Request to fetch saved cards fails |
REMOVE_CARD_ERROR | removeCustomerCard() | Request to remove a card fails |
FETCH_PAYMENT_METHODS_ERROR | getCustomerPaymentMethods() | Request to fetch APMs fails |
Apply styles to all card input fields at once via customization.styles.cardForm. Per-field overrides (Section 11.2) take priority.
cardForm uses wrapper keys (inputStyles, labelStyles, errorStyles) defined by ILiteCardFormStyles. Per-field keys like cardholderName, cardNumber, cvv, etc. are directly CollectInputStylesVariant — no wrapper.
newLiteCheckout({apiKey: 'YOUR_KEY',mode: 'production',returnUrl: 'https://myapp.com/checkout',customization: {styles: {// Show card brand icon inside the card_number field (default: true)enableCardIcon: true,cardForm: {// Base typography applied to all fieldsbase: {fontFamily: 'Inter, sans-serif',fontSize: '14px',color: '#1d1d1f',},// Styles applied to the <input> element inside each iframeinputStyles: {base: {border: '1px solid #d1d1d6',borderRadius: '8px',padding: '10px 12px',backgroundColor: '#ffffff',},focus: {borderColor: '#6200ee',boxShadow: '0 0 0 3px rgba(98, 0, 238, 0.15)',outline: 'none',},complete: {borderColor: '#34c759',},invalid: {borderColor: '#ff3b30',color: '#ff3b30',},empty: {borderColor: '#d1d1d6',},},// Styles applied to the field labellabelStyles: {base: {fontSize: '12px',fontWeight: '500',color: '#6e6e73',marginBottom: '4px',},},// Styles applied to the validation error messageerrorStyles: {base: {color: '#ff3b30',fontSize: '11px',marginTop: '4px',},},},},},});Override styles for individual fields using the field keys in IStyles. These take priority over cardForm styles and use the same structure as cardForm (inputStyles, labelStyles, errorStyles), so you can override the label and error text per field as well.
Per-field keys in IStyles:
| Key | Field |
|---|---|
cardholderName | Cardholder name field |
cardNumber | Card number field |
cvv | CVV field |
expirationMonth | Expiration month field |
expirationYear | Expiration year field |
Example — highlight CVV with a different color scheme and custom label:
customization: {styles: {cvv: {inputStyles: {base: {borderColor: '#8e44ad',backgroundColor: '#faf5ff'},focus: {borderColor: '#6c3483',boxShadow: '0 0 0 3px rgba(108,52,131,0.2)'},invalid: {borderColor: '#e74c3c',color: '#e74c3c'},complete: {borderColor: '#27ae60'},},labelStyles: {base: {color: '#8e44ad',fontWeight: '600'},},errorStyles: {base: {color: '#e74c3c',fontSize: '11px'},},},},}Example — card number with custom input and icon styles:
customization: {styles: {cardNumber: {inputStyles: {base: {letterSpacing: '2px',fontFamily: '"Courier New", monospace'},cardIcon: {width: '32px',height: '20px'},},},expirationMonth: {inputStyles: {base: {textAlign: 'center'},},},expirationYear: {inputStyles: {base: {textAlign: 'center'},},},},}interfaceIFormLabels{name?: string;// Label for cardholder name fieldcard_number?: string;// Label for card number fieldcvv?: string;// Label for CVV fieldexpiry_date?: string;// Shared expiry label (if shown as one field)expiration_month?: string;// Label for expiration month fieldexpiration_year?: string;// Label for expiration year field}interfaceIFormPlaceholder{name?: string;// Placeholder for cardholder namecard_number?: string;// Placeholder for card numbercvv?: string;// Placeholder for CVVexpiration_month?: string;// Placeholder for expiration monthexpiration_year?: string;// Placeholder for expiration year}Example:
customization: {labels: {name: 'Cardholder Name',card_number: 'Card Number',cvv: 'Security Code (CVV)',expiration_month: 'Month',expiration_year: 'Year',},placeholders: {name: 'John Doe',card_number: '4111 1111 1111 1111',cvv: '•••',expiration_month: 'MM',expiration_year: 'YY',},}Deprecated API — click to expand
| Property | Replacement | Notes |
|---|---|---|
apiKeyTonder | apiKey | Renamed for clarity |
baseUrlTonder | mode | Replaced by environment enum ('stage' | 'production' | ...) |
signal | (removed) | AbortController signal is no longer needed |
| Deprecated Method | Use Instead | Notes |
|---|---|---|
getBusiness() | (auto-handled) | No longer needed |
customerRegister(email) | (auto-handled) | No longer needed |
createOrder(items) | payment() | Replaced by unified payment() |
createPayment(items) | payment() | Replaced by unified payment() |
startCheckoutRouter(data) | payment() | Replaced by unified payment() |
startCheckoutRouterFull(data) | payment() | Replaced by unified payment() |
registerCustomerCard(secureToken, customerToken, data) | saveCustomerCard() | Signature changed; call mountCardFields() first |
deleteCustomerCard(customerToken, skyflowId) | removeCustomerCard(skyflowId) | Signature simplified |
getActiveAPMs() | getCustomerPaymentMethods() | Renamed |
Raw card fields in payment() / saveCustomerCard()
Passing raw card values (card number, CVV, etc.) directly to these methods is no longer supported. Card data must be collected via mountCardFields() first:
// ❌ DeprecatedawaitliteCheckout.payment({ ...,card: {card_number: '4111...',cvv: '123', ... }});// ✅ CurrentawaitliteCheckout.mountCardFields({fields: ['cardholder_name','card_number','expiration_month','expiration_year','cvv']});// user fills in the formawaitliteCheckout.payment({ customer, cart, currency });returnUrl in IProcessPaymentRequest
Set returnUrl on the constructor instead of per-payment:
// ❌ DeprecatedawaitliteCheckout.payment({ ...,returnUrl: 'https://myapp.com/done'});// ✅ Current — set on constructornewLiteCheckout({ apiKey, mode,returnUrl: 'https://myapp.com/done'});