Skip to content

Repository files navigation

@tonder.io/ionic-lite-sdk

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.

Table of Contents

  1. Quick Start
  2. Installation
  3. Constructor & Configuration
  4. Initialization Sequence
  5. Collecting Card Data — mountCardFields
  6. Processing Payments
  7. 3DS Handling
  8. Managing Saved Cards
  9. Revealing Card Data — revealCardFields
  10. Error Handling
  11. Customization & Styling
  12. Deprecated API

1. Quick Start

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_KEY is hardcoded for brevity. In production, move this fetch call to your own backend and return only the access token to the frontend. See Section 4.


2. Installation

npm install @tonder.io/ionic-lite-sdk
# or
yarn add @tonder.io/ionic-lite-sdk

3. Constructor & Configuration

3.1 Options reference

import{LiteCheckout}from'@tonder.io/ionic-lite-sdk';constliteCheckout=newLiteCheckout(options);
PropertyTypeRequiredDefaultDescription
apiKeystringRequiredPublic API key from the Tonder Dashboard
mode'stage' | 'production'Required'stage'Target environment
returnUrlstringRequired for 3DSURL to which 3DS redirects return after authentication
callBack(response) => voidOptionalundefinedCalled after a successful payment or card enrollment
customizationILiteCustomizationOptionsOptionalundefinedStyles, labels, placeholders, and redirect behavior
eventsICardFormEventsOptionalundefinedonChange / onFocus / onBlur callbacks per field
tdsIframeIdstringOptional'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),},},});

3.2 Customization options

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:

ValueBehavior
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.
falseThe 3DS challenge is rendered inside the #tdsIframe element. The user stays in your app until the challenge resolves.

Use redirectOnComplete: false when you want to keep the user inside the app (e.g., in a mobile WebView). Make sure the #tdsIframe is styled to cover the screen when active — see Section 7.


3.3 Form events

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_number returns a partially masked value (first 8 digits for non-AMEX, first 6 for AMEX, rest masked). All other fields — including cvv — return value: ''. In stage, development, and sandbox, all fields return the actual value. Use isValid and isEmpty for UI state logic — never depend on value for 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;},},}

4. Initialization Sequence

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:

modeBase URL
'stage'https://stage.tonder.io
'production'https://app.tonder.io

Fetching the secure token

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_KEY is a server-side credential. In production, make this request from your own backend and return only the access token to the frontend. Never expose your secret key in client-side code.


injectCheckout()

Initializes the checkout session. Must be awaited before calling mountCardFields.

awaitliteCheckout.injectCheckout();

Important: Calling mountCardFields() before injectCheckout() resolves will throw SKYFLOW_NOT_INITIALIZED.


configureCheckout(data)

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,});

verify3dsTransaction()

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({ ... });

5. Collecting Card Data — 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 calling mountCardFields().

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';

5.1 New-card form (all 5 fields)

Default container IDs (used when no custom container_id is provided):

FieldDefault 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'},],});

5.2 Saved-card CVV only

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_id do 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,});}}

5.3 Unmounting fields

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_contextWhat 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:

  1. Never show the new-card form (all 5 fields) and a saved-card CVV field simultaneously.
  2. Only one saved-card CVV input should be active at a time.
  3. Always mountCardFields() and let the user fill in the fields before calling payment() or saveCustomerCard().

6. Processing Payments

6.1 New card payment

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}

Metadata for Reporting

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.

FieldTypeReport columnDescription
order_referencestringBusiness Transaction IDMerchant's internal order ID. Shown in Tonder dashboard filters and exports. Recommended on every payment.
metadata.order_idstringBusiness Transaction IDTakes precedence over order_reference when both are provided.
metadata.operation_dateDate | stringCustomer ID (metadata)Business operation date for reporting and reconciliation.
metadata.customer_emailstringCustomer EmailOverrides the email shown in reports. Falls back to customer.email if omitted.
metadata.business_userstringBusiness User (metadata)Internal user or system that initiated the payment (e.g. POS terminal ID, cashier ID).
metadata.customer_idstringCustomer ID (metadata)Your internal customer identifier — correlates payments with customer records.

Tip: At minimum, pass order_reference on 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;}}

6.2 Pay with a saved card

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});

6.3 Alternative Payment Method (APM)

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
FieldTypeDescription
binary_modebooleanIf true, payment must be approved or rejected immediately (no pending state)
additional_infostringExtra info shown during checkout
back_urls.successstringRedirect URL after successful payment
back_urls.pendingstringRedirect URL after pending payment
back_urls.failurestringRedirect URL after failed/canceled payment
auto_return'approved' | 'all'Enable auto-redirect after payment completion
payment_methods.excluded_payment_methods[].idstringPayment method to exclude (e.g. 'visa')
payment_methods.excluded_payment_types[].idstringPayment type to exclude (e.g. 'ticket')
payment_methods.default_payment_method_idstringDefault payment method (e.g. 'master')
payment_methods.installmentsnumberMax installments allowed
payment_methods.default_installmentsnumberDefault installments suggested
expiresbooleanWhether the preference has an expiration
expiration_date_fromstring (ISO 8601)Start of validity period
expiration_date_tostring (ISO 8601)End of validity period
statement_descriptorstringText on payer's card statement (max 16 chars)
marketplacestringMarketplace identifier (default: 'NONE')
marketplace_feenumberFee to collect as marketplace commission
differential_pricing.idnumberDifferential pricing strategy ID
shipments.mode'custom' | 'me2' | 'not_specified'Shipping mode
shipments.local_pickupbooleanEnable local branch pickup
shipments.costnumberShipping cost (custom mode only)
shipments.free_shippingbooleanFree shipping flag (custom mode only)
tracks[].type'google_ad' | 'facebook_ad'Ad tracker type
tracks[].values.conversion_idstringGoogle Ads conversion ID
tracks[].values.pixel_idstringFacebook Pixel ID

6.4 Payment response reference

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.


7. 3DS Handling

When a payment requires 3DS authentication, the SDK handles the challenge automatically. There are two display modes, controlled by redirectOnComplete:

redirectOnCompleteChallenge displayUser experience
true (default)Full-page redirect to bank's 3DS pageUser leaves the app; returns to returnUrl after completing auth
falseRendered inside #tdsIframe in your appUser stays in app until the challenge resolves

redirectOnComplete: true (default — full-page redirect)

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.


redirectOnComplete: false (iframe mode — user stays in app)

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.


8. Managing Saved Cards

8.1 List saved cards

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}`);});

8.2 Save a new card

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'],});

8.3 Remove a card

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

8.4 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.

Conditional CVV rule:

subscription_id presentCVV required?Action
YesNoCall payment() directly with card: skyflow_id
NoYesMount 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:

  1. Remove the card with removeCustomerCard() and let the user re-enroll.
  2. 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.
  3. Contact Tonder support to migrate specific cards.

9. Revealing Card Data — revealCardFields

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-card payment(). Calling it without a prior successful card operation throws MOUNT_COLLECT_ERROR.

Default container IDs and fixed redaction:

FieldDefault Container IDRedaction Applied
card_number#reveal_card_numberMASKED — e.g. 4111 11•• •••• 1234
cardholder_name#reveal_cardholder_namePLAIN_TEXT
expiration_month#reveal_expiration_monthPLAIN_TEXT
expiration_year#reveal_expiration_yearPLAIN_TEXT

PCI note:cvv cannot 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, and global style variants (unlike Collect elements which also support focus, 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'},],});

10. Error Handling

10.1 Error structure

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:

  • statusCode reflects the HTTP status when available; defaults to 500 for non-HTTP errors.
  • details contains 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}}

10.2 Error code reference

CodeThrown byWhen
PAYMENT_PROCESS_ERRORpayment()Any payment failure
CARD_ON_FILE_DECLINEDpayment(), saveCustomerCard()Card On File authorization declined (only when Card On File is active on your account)
MOUNT_COLLECT_ERRORmountCardFields(), revealCardFields()Secure fields fail to mount, or revealCardFields() called without a prior successful card operation
SAVE_CARD_ERRORsaveCustomerCard()Any error during card save
FETCH_CARDS_ERRORgetCustomerCards()Request to fetch saved cards fails
REMOVE_CARD_ERRORremoveCustomerCard()Request to remove a card fails
FETCH_PAYMENT_METHODS_ERRORgetCustomerPaymentMethods()Request to fetch APMs fails

11. Customization & Styling

11.1 Global form styles

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',},},},},},});

11.2 Per-field styles

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:

KeyField
cardholderNameCardholder name field
cardNumberCard number field
cvvCVV field
expirationMonthExpiration month field
expirationYearExpiration 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'},},},},}

11.3 Labels & placeholders

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',},}

12. Deprecated API

Deprecated API — click to expand

Deprecated constructor properties

PropertyReplacementNotes
apiKeyTonderapiKeyRenamed for clarity
baseUrlTondermodeReplaced by environment enum ('stage' | 'production' | ...)
signal(removed)AbortController signal is no longer needed

Deprecated methods

Deprecated MethodUse InsteadNotes
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

Deprecated data patterns

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'});

About

Ionic lite

Resources

Stars

0 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages