@tonder.io/ionic-lite-sdk 0.0.69-beta.TEC-146.1 → 0.0.69-beta.TEC-192.ee4f7cd

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,920 +1,1320 @@
1
- # Tonder SDK
2
-
3
- Tonder SDK helps to integrate the services Tonder offers in your own mobile app
1
+ # @tonder.io/ionic-lite-sdk
4
2
 
3
+ PCI DSS–compliant payment SDK for Ionic, Angular, and React.
4
+ Card data is collected through **Skyflow secure iframes** — raw card values never touch your application code.
5
5
 
6
6
  ## Table of Contents
7
7
 
8
- 1. [Installation](#installation)
9
- 2. [Usage](#usage)
10
- 3. [Configuration Options](#configuration-options)
11
- 4. [Card On File](#card-on-file)
12
- 5. [Mobile Settings](#mobile-settings)
13
- 6. [Payment Data Structure](#payment-data-structure)
14
- 7. [Field Validation Functions](#field-validation-functions)
15
- 8. [API Reference](#api-reference)
16
- 9. [Error Handling](#error-handling)
17
- 10. [Examples](#examples)
18
- 11. [Deprecated Fields](#deprecated-fields)
19
- 12. [Deprecated Functions](#deprecated-functions)
20
- 13. [License](#license)
8
+ 1. [Quick Start](#1-quick-start)
9
+ 2. [Installation](#2-installation)
10
+ 3. [Constructor & Configuration](#3-constructor--configuration)
11
+ - [3.1 Options reference](#31-options-reference)
12
+ - [3.2 Customization options](#32-customization-options)
13
+ - [3.3 Form events](#33-form-events)
14
+ 4. [Initialization Sequence](#4-initialization-sequence)
15
+ 5. [Collecting Card Data — `mountCardFields`](#5-collecting-card-data--mountcardfields)
16
+ - [5.1 New-card form (all 5 fields)](#51-new-card-form-all-5-fields)
17
+ - [5.2 Saved-card CVV only](#52-saved-card-cvv-only)
18
+ - [5.3 Unmounting fields](#53-unmounting-fields)
19
+ 6. [Processing Payments](#6-processing-payments)
20
+ - [6.1 New card payment](#61-new-card-payment)
21
+ - [6.2 Saved card payment](#62-saved-card-payment)
22
+ - [6.3 Alternative Payment Method (APM)](#63-alternative-payment-method-apm)
23
+ - [6.4 Payment response reference](#64-payment-response-reference)
24
+ 7. [3DS Handling](#7-3ds-handling)
25
+ 8. [Managing Saved Cards](#8-managing-saved-cards)
26
+ - [8.1 List saved cards](#81-list-saved-cards)
27
+ - [8.2 Save a new card](#82-save-a-new-card)
28
+ - [8.3 Remove a card](#83-remove-a-card)
29
+ - [8.4 Card On File (subscription_id)](#84-card-on-file-subscription_id)
30
+ 9. [Revealing Card Data — `revealCardFields`](#9-revealing-card-data--revealcardfields)
31
+ 10. [Error Handling](#10-error-handling)
32
+ - [10.1 Error structure](#101-error-structure)
33
+ - [10.2 Error code reference](#102-error-code-reference)
34
+ 11. [Customization & Styling](#11-customization--styling)
35
+ - [11.1 Global form styles](#111-global-form-styles)
36
+ - [11.2 Per-field styles](#112-per-field-styles)
37
+ - [11.3 Labels & placeholders](#113-labels--placeholders)
38
+ 12. [Deprecated API](#12-deprecated-api)
21
39
 
40
+ ---
22
41
 
23
- ## Installation
42
+ ## 1. Quick Start
24
43
 
25
- You can install using NPM
26
- ```bash
27
- npm i @tonder.io/ionic-lite-sdk
28
- ```
44
+ Get a working payment form in under 5 minutes. This example uses the minimum required setup. See [Section 4](#4-initialization-sequence) for the full step-by-step explanation.
45
+
46
+ **Angular template:**
29
47
 
30
- Add dependencies to the root of the app (index.html) only if you are going to use Openpay as the payment processor.
31
48
  ```html
32
- <script src=https://openpay.s3.amazonaws.com/openpay.v1.min.js></script>
33
- <script src=https://openpay.s3.amazonaws.com/openpay-data.v1.min.js></script>
34
- ```
49
+ <!-- 3DS iframe — only add when using redirectOnComplete: false (see Section 7).
50
+ Remove this element if you are using the default redirectOnComplete: true. -->
51
+ <iframe id="tdsIframe" allowtransparency="true" class="tds-iframe"></iframe>
52
+
53
+ <!-- Secure iframes — card values never touch your code -->
54
+ <div id="collect_cardholder_name"></div>
55
+ <div id="collect_card_number"></div>
56
+ <div id="collect_expiration_month"></div>
57
+ <div id="collect_expiration_year"></div>
58
+ <div id="collect_cvv"></div>
35
59
 
36
- ## Usage
37
- LiteCheckout allows you to build a custom checkout interface using Tonder's core functionality
38
- ### Import LiteCheckout class
39
- ```javascript
40
- import { LiteCheckout } from "@tonder.io/ionic-lite-sdk"
60
+ <button (click)="pay()">Pay</button>
41
61
  ```
42
- ### Create instance
43
62
 
44
- ```javascript
45
- const liteCheckout = new LiteCheckout({
46
- signal,
47
- baseUrlTonder,
48
- apiKeyTonder
49
- })
63
+ **Angular component:**
50
64
 
51
- // The configureCheckout function allows you to set initial information,
52
- // such as the customer's email, which is used to retrieve a list of saved cards, save new card, etc.
53
- inlineCheckout.configureCheckout({ customer: { email: "example@email.com" } });
65
+ ```typescript
66
+ import { Component, OnInit } from '@angular/core';
67
+ import { LiteCheckout, AppError } from '@tonder.io/ionic-lite-sdk';
54
68
 
55
- // Initialize the checkout
56
- await liteCheckout.injectCheckout();
69
+ @Component({ selector: 'app-checkout', templateUrl: './checkout.component.html' })
70
+ export class CheckoutComponent implements OnInit {
71
+ private liteCheckout!: LiteCheckout;
72
+ loading = false;
57
73
 
58
- // To verify a 3ds transaction you can use the following method
59
- // It should be called after the injectCheckout method
60
- // The response status will be one of the following
61
- // ['Declined', 'Cancelled', 'Failed', 'Success', 'Pending', 'Authorized']
74
+ async ngOnInit() {
75
+ // Step 1 Create instance
76
+ this.liteCheckout = new LiteCheckout({
77
+ apiKey: 'YOUR_PUBLIC_API_KEY',
78
+ mode: 'stage',
79
+ returnUrl: `${window.location.origin}/checkout`,
80
+ });
62
81
 
63
- inlineCheckout.verify3dsTransaction().then(response => {
64
- console.log('Verify 3ds response', response)
65
- })
66
- ```
82
+ // Step 2 — Fetch secure token from YOUR backend (never expose YOUR_SECRET_API_KEY on the frontend)
83
+ // Your backend calls POST https://stage.tonder.io/api/secure-token/ with the secret key
84
+ // and returns { access: string } to the client.
85
+ const { access } = await fetch('/api/tonder-secure-token', {
86
+ method: 'POST',
87
+ }).then(r => r.json());
88
+
89
+ // Step 3 — Configure with customer + token
90
+ this.liteCheckout.configureCheckout({
91
+ customer: { email: 'user@example.com' },
92
+ secureToken: access,
93
+ // cart: { total: 100, items: [...] },
94
+ // currency: 'MXN',
95
+ // order_reference: 'ORD-001', // your internal order ID — shows in Tonder dashboard & exports
96
+ // metadata: { order_id: 'ORD-001' }, // reporting metadata — see Section 6.1
97
+ });
67
98
 
68
- ```javascript
69
- // Retrieve customer's saved cards
70
- const cards = await liteCheckout.getCustomerCards();
99
+ // Step 4 — Initialize checkout (must be awaited)
100
+ await this.liteCheckout.injectCheckout();
101
+
102
+ // Step 5 — Check if returning from a 3DS redirect
103
+ // Only needed when redirectOnComplete: true (default). In iframe mode
104
+ // (redirectOnComplete: false) the payment() promise resolves directly — skip this step.
105
+ // If this page load is a return from a 3DS challenge, the SDK verifies the
106
+ // transaction and, if routing is configured, may automatically retry with
107
+ // the next payment route. Await the result before deciding what to do next.
108
+ const tdsResult = await this.liteCheckout.verify3dsTransaction();
109
+ if (tdsResult) {
110
+ const status = (tdsResult as any).transaction_status;
111
+ if (status === 'Success' ) {
112
+ // navigate to order confirmation
113
+ } else {
114
+ // show error to the user
115
+ }
116
+ return; // do not mount card fields — the payment flow already completed
117
+ }
118
+
119
+ // Step 6 — Normal page load: mount secure card input iframes
120
+ await this.liteCheckout.mountCardFields({
121
+ fields: ['cardholder_name', 'card_number', 'expiration_month', 'expiration_year', 'cvv'],
122
+ });
123
+ }
124
+
125
+ async pay() {
126
+ this.loading = true;
127
+ try {
128
+ const response = await this.liteCheckout.payment({
129
+ customer: { email: 'user@example.com' },
130
+ cart: {
131
+ total: 100,
132
+ items: [{
133
+ name: 'Product A', description: 'Product description',
134
+ quantity: 1, price_unit: 100, discount: 0, taxes: 0,
135
+ product_reference: 'SKU-001', amount_total: 100,
136
+ }],
137
+ },
138
+ currency: 'MXN',
139
+ // order_reference: 'ORD-001', // your internal order ID — shows in Tonder dashboard & exports
140
+ // metadata: { order_id: 'ORD-001' }, // reporting metadata — see Section 6.1
141
+ });
142
+ console.log('Transaction status:', response.transaction_status);
143
+ } catch (error) {
144
+ if (error instanceof AppError) {
145
+ console.error(`[${error.code}] ${error.message}`);
146
+ }
147
+ }
148
+ }
149
+ }
71
150
  ```
72
151
 
73
- ```javascript
74
- // Mount Skyflow Elements for card fields, then save
75
- await liteCheckout.mountCardFields({ fields: ['cardholder_name', 'card_number', 'expiration_month', 'expiration_year', 'cvv'] });
76
- // After user fills in the fields:
77
- const newCard = await liteCheckout.saveCustomerCard();
152
+ > **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](#4-initialization-sequence).
153
+
154
+ ---
155
+
156
+ ## 2. Installation
157
+
158
+ ```bash
159
+ npm install @tonder.io/ionic-lite-sdk
160
+ # or
161
+ yarn add @tonder.io/ionic-lite-sdk
78
162
  ```
79
163
 
80
- ```javascript
81
- // Remove a saved card
82
- await liteCheckout.removeCustomerCard(cardId);
164
+ ---
165
+
166
+ ## 3. Constructor & Configuration
167
+
168
+ ### 3.1 Options reference
169
+
170
+ ```typescript
171
+ import { LiteCheckout } from '@tonder.io/ionic-lite-sdk';
172
+
173
+ const liteCheckout = new LiteCheckout(options);
83
174
  ```
84
175
 
85
- ```javascript
86
- // Get available payment methods
87
- const paymentMethods = await liteCheckout.getCustomerPaymentMethods();
176
+ | Property | Type | Required | Default | Description |
177
+ |----------|------|----------|---------|-------------|
178
+ | `apiKey` | `string` | **Required** | — | Public API key from the Tonder Dashboard |
179
+ | `mode` | `'stage' \| 'production'` | **Required** | `'stage'` | Target environment |
180
+ | `returnUrl` | `string` | **Required for 3DS** | — | URL to which 3DS redirects return after authentication |
181
+ | `callBack` | `(response) => void` | Optional | `undefined` | Called after a successful payment or card enrollment |
182
+ | `customization` | `ILiteCustomizationOptions` | Optional | `undefined` | Styles, labels, placeholders, and redirect behavior |
183
+ | `events` | `ICardFormEvents` | Optional | `undefined` | `onChange` / `onFocus` / `onBlur` callbacks per field |
184
+ | `tdsIframeId` | `string` | Optional | `'tdsIframe'` | DOM `id` of the 3DS `<iframe>` element |
185
+
186
+ **Full example:**
187
+
188
+ ```typescript
189
+ const liteCheckout = new LiteCheckout({
190
+ apiKey: 'YOUR_PUBLIC_API_KEY',
191
+ mode: 'production',
192
+ returnUrl: 'https://myapp.com/checkout',
193
+ callBack: (response) => console.log('Payment done', response),
194
+ tdsIframeId: 'myCustomTdsFrame',
195
+ customization: {
196
+ redirectOnComplete: false, // render 3DS challenge inside #tdsIframe instead of full-page redirect
197
+ styles: { enableCardIcon: true },
198
+ },
199
+ events: {
200
+ cardNumberEvents: {
201
+ onChange: (e) => console.log('Card number valid:', e.isValid),
202
+ },
203
+ },
204
+ });
88
205
  ```
89
206
 
90
- ```javascript
91
- // New card payment: mount fields first, then pay
92
- await liteCheckout.mountCardFields({ fields: ['cardholder_name', 'card_number', 'expiration_month', 'expiration_year', 'cvv'] });
93
- // After user fills in the fields:
94
- const paymentResponse = await liteCheckout.payment(paymentData); // no card field needed
207
+ ---
208
+
209
+ ### 3.2 Customization options
210
+
211
+ ```typescript
212
+ interface ILiteCustomizationOptions {
213
+ styles?: IStyles; // Field and form visual styles (see Section 11)
214
+ labels?: IFormLabels; // Label text per field
215
+ placeholders?: IFormPlaceholder; // Placeholder text per field
216
+ redirectOnComplete?: boolean; // default: true
217
+ }
95
218
  ```
96
219
 
97
- ## Configuration Options
220
+ **`redirectOnComplete`** controls how 3DS challenges are displayed when the payment processor requires authentication:
221
+
222
+ | Value | Behavior |
223
+ |-------|----------|
224
+ | `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`. |
225
+ | `false` | The 3DS challenge is rendered **inside the `#tdsIframe` element**. The user stays in your app until the challenge resolves. |
226
+
227
+ > 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](#7-3ds-handling).
98
228
 
99
- | Property | Type | Description |
100
- |:---------:|:--------:|:--------------------------------------------------------------------------------------------:|
101
- | mode | string | Environment mode. Options: 'stage', 'production', 'sandbox', 'development'. Default: 'stage' |
102
- | apiKey | string | Your API key from the Tonder Dashboard |
103
- | returnrl | string | URL where the checkout form is mounted (used for 3DS) |
104
- | callBack | function | Callback function to be invoked after the payment process ends successfully. |
229
+ ---
105
230
 
106
- ## Card On File
231
+ ### 3.3 Form events
107
232
 
108
- Card On File is applied automatically when enabled for your merchant account. No extra SDK configuration is required. For saved-card UIs, you must handle CVV collection based on the card data returned by `getCustomerCards()`:
233
+ Register callbacks to react to field state changes (validation, focus, etc.):
109
234
 
110
- - If a saved card has `subscription_id`, CVV is not required.
111
- - If a saved card does not have `subscription_id`, you must collect CVV and pass the card id to `payment()`, or the SDK will error.
112
- - Only call `mountCardFields()` when the selected card does not have `subscription_id`.
235
+ ```typescript
236
+ interface ICardFormEvents {
237
+ cardHolderEvents?: IInputEvents;
238
+ cardNumberEvents?: IInputEvents;
239
+ cvvEvents?: IInputEvents;
240
+ monthEvents?: IInputEvents;
241
+ yearEvents?: IInputEvents;
242
+ }
113
243
 
114
- Example (conditional CVV mount):
115
- ```ts
116
- const selectedCard = cardsResponse.cards.find(
117
- (card) => card.fields.skyflow_id === selectedCardId
118
- );
119
- const needsCvv = !selectedCard?.fields?.subscription_id;
244
+ interface IInputEvents {
245
+ onChange?: (event: IEventSecureInput) => void;
246
+ onFocus?: (event: IEventSecureInput) => void;
247
+ onBlur?: (event: IEventSecureInput) => void;
248
+ }
120
249
 
121
- if (needsCvv) {
122
- liteCheckout.mountCardFields({ fields: ['cvv'], card_id: selectedCardId });
250
+ interface IEventSecureInput {
251
+ elementType: string; // e.g. 'CARD_NUMBER', 'CVV', 'CARDHOLDER_NAME'
252
+ isEmpty: boolean;
253
+ isFocused: boolean;
254
+ isValid: boolean;
255
+ value?: string; // See PCI note below
123
256
  }
124
257
  ```
125
258
 
126
- ### Existing saved cards without subscription_id
127
- Cards saved before Card On File was enabled may not have `subscription_id`. You have three options:
128
- 1) Remove the card using `removeCustomerCard()` and let the user add it again.
129
- 2) Run a payment flow that shows the full card form; the SDK will create a subscription and update the card. Note: this can generate a new `skyflow_id`, so update any references in your app.
130
- 3) Ask Tonder support to remove a specific card or all saved cards for a user.
259
+ > **PCI note:** `value` is only populated in `development` mode. In `production`, `value` is always `''` for sensitive fields (`card_number`, `cvv`); non-sensitive fields (`cardholder_name`, `expiration_month`, `expiration_year`) may still return their value. Use `isValid` and `isEmpty` for UI state logic — never depend on `value` in production.
131
260
 
132
- ## Mobile settings
261
+ **Example live card form validation:**
133
262
 
134
- <font size="3">If you are deploying to Android, edit your AndroidManifest.xml file to add the Internet permission.</font>
263
+ ```typescript
264
+ events: {
265
+ cardNumberEvents: {
266
+ onChange: (e) => { this.cardNumberValid = e.isValid; },
267
+ onBlur: (e) => { this.showCardNumberError = !e.isValid && !e.isEmpty; },
268
+ },
269
+ cvvEvents: {
270
+ onChange: (e) => { this.cvvValid = e.isValid; },
271
+ },
272
+ }
273
+ ```
274
+
275
+ ---
276
+
277
+ ## 4. Initialization Sequence
278
+
279
+ The SDK must be initialized in this exact order before any other method is called:
135
280
 
136
- ```xml
137
- <!-- Required to fetch data from the internet. -->
138
- <uses-permission android:name="android.permission.INTERNET" />
139
281
  ```
282
+ 1. new LiteCheckout(options)
283
+
284
+ 2. fetch baseUrl/api/secure-token/ → { access: string }
285
+
286
+ 3. configureCheckout({ customer, secureToken, ...optional })
287
+
288
+ 4. await injectCheckout() (must be awaited)
289
+
290
+ 5. result = await verify3dsTransaction() (redirectOnComplete: true only — void on normal loads)
291
+ ↓ if result → handle and return early; if void → continue ↓
292
+ 6. await mountCardFields(...) (mounts secure iframes into your divs)
293
+ ```
294
+
295
+ **Base URL by environment:**
296
+
297
+ | `mode` | Base URL |
298
+ |--------|---------|
299
+ | `'stage'` | `https://stage.tonder.io` |
300
+ | `'production'` | `https://app.tonder.io` |
301
+
302
+ ---
303
+
304
+ ### Fetching the secure token
140
305
 
141
- <font size="3">Likewise, if you are deploying to macOS, edit your macos/Runner/DebugProfile.entitlements and macos/Runner/Release.entitlements files to include the network client entitlement.</font>
306
+ 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:
142
307
 
143
- ```xml
144
- <!-- Required to fetch data from the internet. -->
145
- <key>com.apple.security.network.client</key>
146
- <true>
308
+ ```typescript
309
+ const { access } = await fetch(`${baseUrl}/api/secure-token/`, {
310
+ method: 'POST',
311
+ headers: {
312
+ 'Authorization': 'Token YOUR_SECRET_API_KEY',
313
+ 'Content-Type': 'application/json',
314
+ },
315
+ }).then(r => r.json());
147
316
  ```
148
317
 
149
- ## Payment Data Structure
318
+ > **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.
150
319
 
151
- When calling the `payment` method, use the following data structure:
320
+ ---
152
321
 
153
- ### Field Descriptions
322
+ ### `injectCheckout()`
154
323
 
155
- - **customer**: Object containing the customer's personal information to be registered in the transaction.
324
+ Initializes the checkout session. Must be **awaited** before calling `mountCardFields`.
156
325
 
157
- - **cart**: Object containing the total amount and an array of items to be registered in the Tonder order.
326
+ ```typescript
327
+ await liteCheckout.injectCheckout();
328
+ ```
158
329
 
159
- - **total**: The total amount of the transaction.
160
- - **items**: An array of objects, each representing a product or service in the order.
161
- - name: name of the product
162
- - price_unit: valid float string with the price of the product
163
- - quantity: valid integer string with the quantity of this product
330
+ > **Important:** Calling `mountCardFields()` before `injectCheckout()` resolves will throw `SKYFLOW_NOT_INITIALIZED`.
164
331
 
165
- - **currency**: String representing the currency code for the transaction (e.g., "MXN" for Mexican Peso).
332
+ ---
166
333
 
167
- - **metadata**: Object for including any additional information about the transaction. This can be used for internal references or tracking.
334
+ ### `configureCheckout(data)`
168
335
 
169
- - **card**: (for LiteCheckout) Used only when paying with a **saved card** pass the `skyflow_id` string of the saved card. For a **new card**, omit this field and use `mountCardFields()` to collect card data via Skyflow Elements before calling `payment()`. This field is not used when paying with `payment_method`.
336
+ 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.
170
337
 
171
- - **payment_method**: (for LiteCheckout) String indicating the alternative payment method to be used (e.g., "Spei"). This is only used when not paying with a card.
172
- - **order_reference**: Unique order reference from the merchant. Used to visually identify/filter the order in dashboard.
173
- - **apm_config**: (Optional) Configuration object for APM-specific options. Only applicable when using alternative payment methods like Mercado Pago.
174
- <details>
175
- <summary>APM Config Fields - Mercado Pago</summary>
176
-
177
- | **Field** | **Type** | **Description** |
178
- |-------------------------------------|--------------------------------------------|---------------------------------------------------------------------------|
179
- | `binary_mode` | `boolean` | If `true`, payment must be approved or rejected immediately (no pending). |
180
- | `additional_info` | `string` | Extra info shown during checkout and in payment details. |
181
- | `back_urls` | `object` | URLs to redirect the user after payment. |
182
- | └─ `success` | `string` | Redirect URL after successful payment. |
183
- | └─ `pending` | `string` | Redirect URL after pending payment. |
184
- | └─ `failure` | `string` | Redirect URL after failed/canceled payment. |
185
- | `auto_return` | `"approved"` \| `"all"` | Enables auto redirection after payment completion. |
186
- | `payment_methods` | `object` | Payment method restrictions and preferences. |
187
- | └─ `excluded_payment_methods[]` | `array` | List of payment methods to exclude. |
188
- | └─ `excluded_payment_methods[].id` | `string` | ID of payment method to exclude (e.g., "visa"). |
189
- | └─ `excluded_payment_types[]` | `array` | List of payment types to exclude. |
190
- | └─ `excluded_payment_types[].id` | `string` | ID of payment type to exclude (e.g., "ticket"). |
191
- | └─ `default_payment_method_id` | `string` | Default payment method (e.g., "master"). |
192
- | └─ `installments` | `number` | Max number of installments allowed. |
193
- | └─ `default_installments` | `number` | Default number of installments suggested. |
194
- | `expires` | `boolean` | Whether the preference has expiration. |
195
- | `expiration_date_from` | `string` (ISO 8601) | Start of validity period (e.g. `"2025-01-01T12:00:00-05:00"`). |
196
- | `expiration_date_to` | `string` (ISO 8601) | End of validity period. |
197
- | `differential_pricing` | `object` | Configuration for differential pricing. |
198
- | └─ `id` | `number` | ID of the differential pricing strategy. |
199
- | `marketplace` | `string` | Marketplace identifier (default: "NONE"). |
200
- | `marketplace_fee` | `number` | Fee to collect as marketplace commission. |
201
- | `tracks[]` | `array` | Ad tracking configurations. |
202
- | └─ `type` | `"google_ad"` \| `"facebook_ad"` | Type of tracker. |
203
- | └─ `values.conversion_id` | `string` | Google Ads conversion ID. |
204
- | └─ `values.conversion_label` | `string` | Google Ads label. |
205
- | └─ `values.pixel_id` | `string` | Facebook Pixel ID. |
206
- | `statement_descriptor` | `string` | Text on payer’s card statement (max 16 characters). |
207
- | `shipments` | `object` | Shipping configuration. |
208
- | └─ `mode` | `"custom"` \| `"me2"` \| `"not_specified"` | Type of shipping mode. |
209
- | └─ `local_pickup` | `boolean` | Enable pickup at local branch (for `me2`). |
210
- | └─ `dimensions` | `string` | Package dimensions (e.g. `10x10x10,500`). |
211
- | └─ `default_shipping_method` | `number` | Default shipping method (for `me2`). |
212
- | └─ `free_methods[]` | `array` | Shipping methods offered for free (for `me2`). |
213
- | └─ `free_methods[].id` | `number` | ID of free shipping method. |
214
- | └─ `cost` | `number` | Shipping cost (only for `custom` mode). |
215
- | └─ `free_shipping` | `boolean` | If `true`, shipping is free (`custom` only). |
216
- | └─ `receiver_address` | `object` | Shipping address. |
217
- | └─ `receiver_address.zip_code` | `string` | ZIP or postal code. |
218
- | └─ `receiver_address.street_name` | `string` | Street name. |
219
- | └─ `receiver_address.street_number` | `number` | Street number. |
220
- | └─ `receiver_address.city_name` | `string` | City name. |
221
- | └─ `receiver_address.state_name` | `string` | State name. |
222
- | └─ `receiver_address.country_name` | `string` | Country name. |
223
- | └─ `receiver_address.floor` | `string` | Floor (optional). |
224
- | └─ `receiver_address.apartment` | `string` | Apartment or unit (optional). |
225
- </details>
338
+ ```typescript
339
+ interface IConfigureCheckout {
340
+ customer: { email: string } | ICustomer; // Required minimum: { email }
341
+ secureToken: string; // Required — from the token fetch
342
+ cart?: { total: number | string; items: IItem[] };
343
+ currency?: string;
344
+ order_reference?: string; // your internal order ID — shown in Tonder dashboard & exports
345
+ metadata?: Record<string, any>; // reporting fields — see Section 6.1
346
+ card?: string; // skyflow_id pre-selects a saved card for payment()
347
+ payment_method?: string; // APM identifier pre-selects an APM for payment()
348
+ }
349
+ ```
226
350
 
227
- ```javascript
228
- const paymentData = {
229
- customer: {
230
- firstName: "John",
231
- lastName: "Doe",
232
- country: "USA",
233
- address: "123 Main St",
234
- city: "Anytown",
235
- state: "CA",
236
- postCode: "12345",
237
- email: "john.doe@example.com",
238
- phone: "1234567890",
239
- identification:{
240
- type: "CPF",
241
- number: "19119119100"
242
- }
243
- },
244
- cart: {
245
- total: "100.00",
246
- items: [
247
- {
248
- description: "Product description",
249
- quantity: 1,
250
- price_unit: "100.00",
251
- discount: "0.00",
252
- taxes: "0.00",
253
- product_reference: "PROD123",
254
- name: "Product Name",
255
- amount_total: "100.00",
256
- },
257
- ],
258
- },
259
- currency: "MXN",
260
- metadata: {
261
- order_id: "ORDER123",
262
- },
263
- // For a new card: omit `card` — use mountCardFields() before calling payment().
264
- // card: "skyflow_id" // for a selected saved card.
265
- // payment_method: "Spei", // For the selected payment method.
266
- // apm_config: {} // Optional, only for APMs like Mercado Pago, Oxxo Pay
267
- };
351
+ ```typescript
352
+ liteCheckout.configureCheckout({
353
+ customer: { email: 'user@example.com' },
354
+ secureToken: access,
355
+ });
268
356
  ```
269
357
 
270
- ## Field Validation Functions
271
-
272
- For LiteCheckout implementations, the SDK provides validation functions to ensure the integrity of card data before submitting:
273
-
274
- - `validateCardNumber(cardNumber)`: Validates the card number using the Luhn algorithm.
275
- - `validateCardholderName(name)`: Checks if the cardholder name is valid.
276
- - `validateCVV(cvv)`: Ensures the CVV is in the correct format.
277
- - `validateExpirationDate(expirationDate)`: Validates the expiration date in MM/YY format.
278
- - `validateExpirationMonth(month)`: Checks if the expiration month is valid.
279
- - `validateExpirationYear(year)`: Validates the expiration year.
280
-
281
- Example usage:
282
-
283
- ```javascript
284
- import {
285
- validateCardNumber,
286
- validateCardholderName,
287
- validateCVV,
288
- validateExpirationDate,
289
- } from "@tonder.io/ionic-lite-sdk";
290
-
291
- const cardNumber = "4111111111111111";
292
- const cardholderName = "John Doe";
293
- const cvv = "123";
294
- const expirationDate = "12/25";
295
-
296
- if (
297
- validateCardNumber(cardNumber) &&
298
- validateCardholderName(cardholderName) &&
299
- validateCVV(cvv) &&
300
- validateExpirationDate(expirationDate)
301
- ) {
302
- // Proceed with payment
303
- } else {
304
- // Show error message
358
+ ---
359
+
360
+ ### `verify3dsTransaction()`
361
+
362
+ 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.
363
+
364
+ 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.
365
+
366
+ ```typescript
367
+ const result = await liteCheckout.verify3dsTransaction();
368
+
369
+ if (result) {
370
+ // Returning from 3DS — routing may have been applied automatically
371
+ const status = (result as any).transaction_status;
372
+ if (status === 'Success') {
373
+ // navigate to confirmation
374
+ } else {
375
+ // show error
376
+ }
377
+ return; // do not proceed to mountCardFields
305
378
  }
379
+
380
+ // Normal page load — continue with checkout initialization
381
+ await liteCheckout.mountCardFields({ ... });
306
382
  ```
307
383
 
384
+ ---
308
385
 
309
- ## API Reference
386
+ ## 5. Collecting Card Data — `mountCardFields`
310
387
 
311
- ### LiteCheckout Methods
388
+ Mounts secure iframes into your `<div>` containers. Card values are captured directly inside the iframe and **never pass through your application code**.
312
389
 
313
- - `configureCheckout(data)`: Set initial checkout data
314
- - `injectCheckout()`: Initialize the checkout
315
- - `getCustomerCards()`: Retrieve saved cards
316
- - `saveCustomerCard()`: Save a new card (requires `mountCardFields()` called first)
317
- - `removeCustomerCard(cardId)`: Remove a saved card
318
- - `getCustomerPaymentMethods()`: Get available payment methods
319
- - `payment(data)`: Process a payment
320
- - `verify3dsTransaction()`: Verify a 3DS transaction
321
- - `mountCardFields({ fields, card_id })`: Mount Skyflow Elements (secure iframes) into developer-provided `<div>` containers. Used for both **new card enrollment** and **saved-card CVV collection**.
390
+ > **Prerequisite:** The container `<div>` elements must exist in the DOM before calling `mountCardFields()`.
322
391
 
323
- #### mountCardFields
392
+ ```typescript
393
+ interface IMountCardFieldsRequest {
394
+ fields: (CardField | { field: CardField; container_id?: string })[];
395
+ card_id?: string; // Omit for new card; provide skyflow_id for saved-card CVV
396
+ unmount_context?: 'all' | 'current' | 'create' | string; // default: 'all'
397
+ }
324
398
 
325
- Mounts Skyflow Elements (secure iframes) into developer-provided `<div>` containers. This is the secure way to collect card data — card values never pass through your application.
399
+ type CardField =
400
+ | 'cardholder_name'
401
+ | 'card_number'
402
+ | 'expiration_month'
403
+ | 'expiration_year'
404
+ | 'cvv';
405
+ ```
326
406
 
327
- **Parameters:**
407
+ ---
328
408
 
329
- | Name | Type | Required | Description |
330
- |------------------|--------------------------------------------------|----------|------------------------------------------------------------------------------------------|
331
- | fields | `CardField[]` or `{ field, container_id? }[]` | Yes | Fields to mount. Use string names or objects with an optional custom `container_id`. |
332
- | card_id | string | No | Skyflow ID of a saved card. When provided, elements update that card (e.g. CVV only). |
333
- | unmount_context | string | No | Which context to unmount before mounting: `'all'` (default), `'current'`, or `'none'`. |
409
+ ### 5.1 New-card form (all 5 fields)
334
410
 
335
411
  **Default container IDs** (used when no custom `container_id` is provided):
336
412
 
337
- | Field | Default div ID |
338
- |--------------------|-----------------------------------------|
339
- | `cardholder_name` | `#collect_cardholder_name` |
340
- | `card_number` | `#collect_card_number` |
341
- | `expiration_month` | `#collect_expiration_month` |
342
- | `expiration_year` | `#collect_expiration_year` |
343
- | `cvv` (new card) | `#collect_cvv` |
344
- | `cvv` (saved card) | `#collect_cvv_<card_id>` |
413
+ | Field | Default Container ID |
414
+ |-------|---------------------|
415
+ | `cardholder_name` | `#collect_cardholder_name` |
416
+ | `card_number` | `#collect_card_number` |
417
+ | `expiration_month` | `#collect_expiration_month` |
418
+ | `expiration_year` | `#collect_expiration_year` |
419
+ | `cvv` | `#collect_cvv` |
345
420
 
346
- **Use case 1 — New card enrollment / payment:**
421
+ **HTML:**
347
422
  ```html
348
- <!-- Place these divs where you want the fields to appear -->
349
423
  <div id="collect_cardholder_name"></div>
350
424
  <div id="collect_card_number"></div>
351
425
  <div id="collect_expiration_month"></div>
352
426
  <div id="collect_expiration_year"></div>
353
427
  <div id="collect_cvv"></div>
354
428
  ```
355
- ```ts
356
- // Mount all 5 fields (call once during setup, before payment or save)
429
+
430
+ **TypeScript shorthand (string array):**
431
+ ```typescript
357
432
  await liteCheckout.mountCardFields({
358
- fields: ['cardholder_name', 'card_number', 'expiration_month', 'expiration_year', 'cvv']
433
+ fields: ['cardholder_name', 'card_number', 'expiration_month', 'expiration_year', 'cvv'],
359
434
  });
360
-
361
- // New card payment — no `card` field needed
362
- await liteCheckout.payment({ customer, cart, currency });
363
-
364
- // Or save the new card
365
- await liteCheckout.saveCustomerCard();
366
435
  ```
367
436
 
368
- **Use case 2 Saved-card CVV collection:**
369
- ```html
370
- <div id="collect_cvv_saved-card-id"></div>
371
- ```
372
- ```ts
373
- // Mount only the CVV field for the selected saved card
374
- liteCheckout.mountCardFields({ fields: ['cvv'], card_id: 'saved-card-id' });
375
-
376
- // Then pay with the saved card
377
- await liteCheckout.payment({ customer, cart, card: 'saved-card-id' });
378
- ```
379
-
380
- **Custom container IDs:**
381
- ```ts
437
+ **TypeScriptcustom container IDs:**
438
+ ```typescript
382
439
  await liteCheckout.mountCardFields({
383
440
  fields: [
384
- { field: 'card_number', container_id: '#my-card-number' },
385
- { field: 'cvv', container_id: '#my-cvv' },
386
441
  { field: 'cardholder_name', container_id: '#my-name' },
442
+ { field: 'card_number', container_id: '#my-card-number' },
387
443
  { field: 'expiration_month', container_id: '#my-month' },
388
444
  { field: 'expiration_year', container_id: '#my-year' },
389
- ]
445
+ { field: 'cvv', container_id: '#my-cvv' },
446
+ ],
447
+ });
448
+ ```
449
+
450
+ ---
451
+
452
+ ### 5.2 Saved-card CVV only
453
+
454
+ For saved-card payments, mount only the CVV field for the selected card. The default container ID is `#collect_cvv_<skyflow_id>`.
455
+
456
+ ```html
457
+ <!-- Use the card's skyflow_id as part of the container id -->
458
+ <div id="collect_cvv_abc123"></div>
459
+ ```
460
+
461
+ ```typescript
462
+ liteCheckout.mountCardFields({
463
+ fields: ['cvv'],
464
+ card_id: 'abc123', // card.fields.skyflow_id
390
465
  });
391
466
  ```
392
467
 
393
- **Important Notes:**
394
- 1. **New card vs saved card are mutually exclusive:** Do not show the full card form and a saved-card CVV input simultaneously.
395
- 2. **Single saved card at a time:** Only one saved-card CVV input should be active at once.
396
- 3. **Call before submitting:** Always call `mountCardFields()` and let the user fill in the fields before calling `payment()` or `saveCustomerCard()`.
468
+ > **Note:** Cards with `subscription_id` do not require CVV entry. See [Section 8.4](#84-card-on-file-subscription_id).
469
+
470
+ **Conditional CVV mount pattern:**
471
+ ```typescript
472
+ handleSelectCard(card: ICard) {
473
+ if (this.selectedCard?.fields?.skyflow_id === card.fields.skyflow_id) return;
474
+ this.selectedCard = card;
475
+
476
+ // Only mount CVV for cards that don't have a Card On File subscription
477
+ if (!card.fields.subscription_id) {
478
+ this.liteCheckout.mountCardFields({
479
+ fields: ['cvv'],
480
+ card_id: card.fields.skyflow_id,
481
+ });
482
+ }
483
+ }
484
+ ```
397
485
 
398
486
  ---
399
487
 
400
- #### revealCardFields
488
+ ### 5.3 Unmounting fields
401
489
 
402
- Reveals card data collected in the last `saveCustomerCard()` or `payment()` (with a new card) using Skyflow Reveal Elements secure iframes that render the actual or masked values in your `<div>` containers without ever exposing raw card data to the application.
490
+ `mountCardFields()` automatically unmounts previously mounted fields before mounting new onesyou don't need to call `unmountCardFields()` manually when switching between cards or modes.
403
491
 
404
- > **When to call:** After a successful `saveCustomerCard()` or `payment()` that processed a new card (not a saved card).
492
+ The `unmount_context` parameter on `mountCardFields` controls what gets cleared before the new fields are mounted:
405
493
 
406
- **Parameters:**
494
+ | `unmount_context` | What gets unmounted before mounting |
495
+ |-------------------|-------------------------------------|
496
+ | `'all'` (default) | All mounted fields across all contexts |
497
+ | `'current'` | Only the current context (new-card or the active saved-card CVV) |
498
+ | `'create'` | New-card form fields only |
499
+ | `'update:skyflow_id'` | CVV field for a specific saved card |
407
500
 
408
- | Name | Type | Required | Description |
409
- |--------|----------------------------------------|----------|--------------------------------------------------------------------|
410
- | fields | `(CardField \| IRevealCardField)[]` | Yes | Fields to reveal. Use string shorthand or objects for custom config. |
411
- | styles | `IRevealElementStyles` | No | Global styles applied to all reveal elements. |
501
+ **The only case where you call `unmountCardFields()` directly** is when navigating away from the checkout screen without remounting:
412
502
 
413
- **Default container IDs** (used when no custom `container_id` is provided):
503
+ ```typescript
504
+ ngOnDestroy() {
505
+ this.liteCheckout.unmountCardFields();
506
+ }
507
+ ```
414
508
 
415
- | Field | Default div ID | Default Redaction |
416
- |--------------------|---------------------------------|-------------------|
417
- | `cardholder_name` | `#reveal_cardholder_name` | `PLAIN_TEXT` |
418
- | `card_number` | `#reveal_card_number` | `MASKED` |
419
- | `expiration_month` | `#reveal_expiration_month` | `PLAIN_TEXT` |
420
- | `expiration_year` | `#reveal_expiration_year` | `PLAIN_TEXT` |
421
- | `cvv` | `#reveal_cvv` | `REDACTED` |
509
+ > **Important notes:**
510
+ > 1. Never show the new-card form (all 5 fields) and a saved-card CVV field simultaneously.
511
+ > 2. Only one saved-card CVV input should be active at a time.
512
+ > 3. Always `mountCardFields()` and let the user fill in the fields **before** calling `payment()` or `saveCustomerCard()`.
422
513
 
423
- **Basic usage — reveal after enrollment:**
424
- ```html
425
- <!-- Place these divs in your confirmation screen -->
426
- <div id="reveal_cardholder_name"></div>
427
- <div id="reveal_card_number"></div>
428
- <div id="reveal_expiration_month"></div>
429
- <div id="reveal_expiration_year"></div>
514
+ ---
515
+
516
+ ## 6. Processing Payments
517
+
518
+ ### 6.1 New card payment
519
+
520
+ **Prerequisites:** [Section 5.1](#51-new-card-form-all-5-fields) — all 5 card fields must be mounted and filled by the user.
521
+
522
+ ```typescript
523
+ interface IProcessPaymentRequest {
524
+ customer: ICustomer | { email: string }; // Required
525
+ cart: { total: string | number; items: IItem[] }; // Required
526
+ currency?: string; // Optional — ISO code e.g. 'MXN'
527
+ order_reference?: string | null; // Recommended — your internal order ID; shown in Tonder dashboard, filters, and exports
528
+ metadata?: Record<string, any>; // Recommended — fields shown in Tonder transaction exports (see table below)
529
+ isSandbox?: boolean; // Optional — Openpay sandbox mode
530
+ apm_config?: Record<string, any>; // Optional — APM-specific config (Mercado Pago, etc.)
531
+ // card — OMIT for new-card payment
532
+ // payment_method — OMIT for card payment
533
+ }
430
534
  ```
431
- ```ts
432
- // After successful saveCustomerCard():
433
- await liteCheckout.saveCustomerCard();
434
535
 
435
- // Reveal card info in the divs above (no raw data exposed)
436
- await liteCheckout.revealCardFields({
437
- fields: ['cardholder_name', 'card_number', 'expiration_month', 'expiration_year']
438
- });
439
- // #reveal_card_number will display e.g. "4111 11•• •••• 1111" (MASKED)
440
- // #reveal_cardholder_name will display the full name (PLAIN_TEXT)
536
+ #### Metadata for Reporting
537
+
538
+ 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.
539
+
540
+ | Field | Type | Report column | Description |
541
+ |-------|------|---------------|-------------|
542
+ | `order_reference` | `string` | Business Transaction ID | Merchant's internal order ID. Shown in Tonder dashboard filters and exports. Recommended on every payment. |
543
+ | `metadata.order_id` | `string` | Business Transaction ID | Takes precedence over `order_reference` when both are provided. |
544
+ | `metadata.operation_date` | `Date \| string` | Customer ID (metadata) | Business operation date for reporting and reconciliation. |
545
+ | `metadata.customer_email` | `string` | Customer Email | Overrides the email shown in reports. Falls back to `customer.email` if omitted. |
546
+ | `metadata.business_user` | `string` | Business User (metadata) | Internal user or system that initiated the payment (e.g. POS terminal ID, cashier ID). |
547
+ | `metadata.customer_id` | `string` | Customer ID (metadata) | Your internal customer identifier — correlates payments with customer records. |
548
+
549
+ > **Tip:** At minimum, pass `order_reference` on every payment so your orders appear correctly in Tonder's dashboard and exports.
550
+
551
+ **`ICustomer`:**
552
+ ```typescript
553
+ type ICustomer = {
554
+ firstName: string; // Required
555
+ lastName: string; // Required
556
+ email: string; // Required
557
+ phone?: string;
558
+ country?: string;
559
+ street?: string;
560
+ city?: string;
561
+ state?: string;
562
+ postCode?: string;
563
+ address?: string;
564
+ identification?: { type: string; number: string };
565
+ };
441
566
  ```
442
567
 
443
- **Custom redaction and altText per field:**
444
- ```ts
445
- await liteCheckout.revealCardFields({
446
- fields: [
447
- { field: 'card_number', redaction: 'PLAIN_TEXT' }, // show full number
448
- { field: 'cardholder_name', altText: 'Loading name...' }, // placeholder while loading
449
- { field: 'cvv', redaction: 'PLAIN_TEXT', label: 'CVV' }, // show CVV (not recommended in prod)
450
- ]
451
- });
568
+ **`IItem`:**
569
+ ```typescript
570
+ interface IItem {
571
+ name: string;
572
+ description: string;
573
+ quantity: number;
574
+ price_unit: number;
575
+ amount_total: number;
576
+ discount: number;
577
+ taxes: number;
578
+ product_reference: string | number;
579
+ }
452
580
  ```
453
581
 
454
- **Custom styles:**
455
- ```ts
456
- await liteCheckout.revealCardFields({
457
- fields: ['card_number', 'cardholder_name'],
458
- styles: {
459
- inputStyles: {
460
- base: {
461
- fontFamily: 'Inter, sans-serif',
462
- fontSize: '16px',
463
- color: '#1d1d1d',
464
- }
582
+ **Example:**
583
+ ```typescript
584
+ async pay() {
585
+ this.loading = true;
586
+ try {
587
+ const response = await this.liteCheckout.payment({
588
+ customer: {
589
+ firstName: 'John',
590
+ lastName: 'Doe',
591
+ email: 'john@example.com',
592
+ phone: '+1 555 0100',
593
+ country: 'MX',
594
+ city: 'CDMX',
595
+ street: '123 Main St',
596
+ state: 'CMX',
597
+ postCode: '06600',
598
+ },
599
+ cart: {
600
+ total: 150,
601
+ items: [{
602
+ name: 'Product A',
603
+ description: 'Product description',
604
+ quantity: 1,
605
+ price_unit: 150,
606
+ discount: 0,
607
+ taxes: 0,
608
+ product_reference: 'SKU-001',
609
+ amount_total: 150,
610
+ }],
611
+ },
612
+ currency: 'MXN',
613
+ metadata: { order_id: 'ORD-789' },
614
+ order_reference: 'ORD-789',
615
+ });
616
+ console.log('Transaction status:', response.transaction_status);
617
+ } catch (error) {
618
+ if (error instanceof AppError) {
619
+ console.error(error.code, error.message);
620
+ this.errorMessage = error.message;
465
621
  }
622
+ } finally {
623
+ this.loading = false;
466
624
  }
625
+ }
626
+ ```
627
+
628
+ ---
629
+
630
+ ### 6.2 Saved card payment
631
+
632
+ **Prerequisites:** Fetch saved cards ([Section 8.1](#81-list-saved-cards)). Conditionally mount the CVV field ([Section 5.2](#52-saved-card-cvv-only)).
633
+
634
+ ```typescript
635
+ const response = await this.liteCheckout.payment({
636
+ customer: { email: 'user@example.com' },
637
+ cart: { total: 100, items: [...] },
638
+ currency: 'MXN',
639
+ card: selectedCard.fields.skyflow_id, // the only addition vs. new card
640
+ });
641
+ ```
642
+
643
+ ---
644
+
645
+ ### 6.3 Alternative Payment Method (APM)
646
+
647
+ No `mountCardFields()` call is needed for APM payments.
648
+
649
+ ```typescript
650
+ // 1. Fetch available APMs
651
+ const apms = await this.liteCheckout.getCustomerPaymentMethods();
652
+ // IPaymentMethod = { id, payment_method, priority, category, icon, label }
653
+
654
+ // 2. User selects an APM
655
+
656
+ // 3. Pay
657
+ const response = await this.liteCheckout.payment({
658
+ customer: { email: 'user@example.com' },
659
+ cart: { total: 100, items: [...] },
660
+ currency: 'MXN',
661
+ payment_method: selectedApm.payment_method, // e.g. 'Spei'
662
+ });
663
+ ```
664
+
665
+ **Mercado Pago — `apm_config`:**
666
+
667
+ Pass Mercado Pago-specific preferences via `apm_config`:
668
+
669
+ ```typescript
670
+ const response = await this.liteCheckout.payment({
671
+ ...paymentData,
672
+ payment_method: 'MercadoPago',
673
+ apm_config: {
674
+ back_urls: {
675
+ success: 'https://myapp.com/success',
676
+ pending: 'https://myapp.com/pending',
677
+ failure: 'https://myapp.com/failure',
678
+ },
679
+ auto_return: 'approved',
680
+ },
467
681
  });
468
682
  ```
469
683
 
470
- **Available redaction values:**
684
+ <details>
685
+ <summary>Full Mercado Pago <code>apm_config</code> fields</summary>
686
+
687
+ | Field | Type | Description |
688
+ |-------|------|-------------|
689
+ | `binary_mode` | `boolean` | If `true`, payment must be approved or rejected immediately (no pending state) |
690
+ | `additional_info` | `string` | Extra info shown during checkout |
691
+ | `back_urls.success` | `string` | Redirect URL after successful payment |
692
+ | `back_urls.pending` | `string` | Redirect URL after pending payment |
693
+ | `back_urls.failure` | `string` | Redirect URL after failed/canceled payment |
694
+ | `auto_return` | `'approved' \| 'all'` | Enable auto-redirect after payment completion |
695
+ | `payment_methods.excluded_payment_methods[].id` | `string` | Payment method to exclude (e.g. `'visa'`) |
696
+ | `payment_methods.excluded_payment_types[].id` | `string` | Payment type to exclude (e.g. `'ticket'`) |
697
+ | `payment_methods.default_payment_method_id` | `string` | Default payment method (e.g. `'master'`) |
698
+ | `payment_methods.installments` | `number` | Max installments allowed |
699
+ | `payment_methods.default_installments` | `number` | Default installments suggested |
700
+ | `expires` | `boolean` | Whether the preference has an expiration |
701
+ | `expiration_date_from` | `string` (ISO 8601) | Start of validity period |
702
+ | `expiration_date_to` | `string` (ISO 8601) | End of validity period |
703
+ | `statement_descriptor` | `string` | Text on payer's card statement (max 16 chars) |
704
+ | `marketplace` | `string` | Marketplace identifier (default: `'NONE'`) |
705
+ | `marketplace_fee` | `number` | Fee to collect as marketplace commission |
706
+ | `differential_pricing.id` | `number` | Differential pricing strategy ID |
707
+ | `shipments.mode` | `'custom' \| 'me2' \| 'not_specified'` | Shipping mode |
708
+ | `shipments.local_pickup` | `boolean` | Enable local branch pickup |
709
+ | `shipments.cost` | `number` | Shipping cost (custom mode only) |
710
+ | `shipments.free_shipping` | `boolean` | Free shipping flag (custom mode only) |
711
+ | `tracks[].type` | `'google_ad' \| 'facebook_ad'` | Ad tracker type |
712
+ | `tracks[].values.conversion_id` | `string` | Google Ads conversion ID |
713
+ | `tracks[].values.pixel_id` | `string` | Facebook Pixel ID |
471
714
 
472
- | Value | Effect |
473
- |--------------|-------------------------------------------------|
474
- | `PLAIN_TEXT` | Full value revealed |
475
- | `MASKED` | Partial value (e.g. `4111 11•• •••• 1111`) |
476
- | `REDACTED` | All characters replaced (e.g. `***`) |
477
- | `DEFAULT` | Uses the vault's configured redaction |
715
+ </details>
478
716
 
479
717
  ---
480
718
 
481
- ## Error Handling
719
+ ### 6.4 Payment response reference
482
720
 
483
- Public SDK methods that fail due to API/SDK execution return an `AppError` (with `name: "TonderError"`).
721
+ ```typescript
722
+ interface IStartCheckoutResponse {
723
+ status: string;
724
+ message: string;
725
+ transaction_status: string; // 'Success' | 'Pending' | 'Declined' | 'Failed'
726
+ transaction_id: number;
727
+ payment_id: number;
728
+ checkout_id: string;
729
+ is_route_finished: boolean;
730
+ provider: string;
731
+ psp_response: Record<string, any>; // Raw response from the payment processor
732
+ }
733
+ ```
484
734
 
485
- ### Error structure
735
+ > **Note:** 3DS authentication is handled automatically by the SDK. You do not need to handle redirection yourself.
486
736
 
487
- ```json
488
- {
489
- "status": "error",
490
- "name": "TonderError",
491
- "code": "PAYMENT_PROCESS_ERROR",
492
- "message": "There was an issue processing the payment.",
493
- "statusCode": 500,
494
- "details": {
495
- "code": "PAYMENT_PROCESS_ERROR",
496
- "statusCode": 500,
497
- "systemError": "APP_INTERNAL_001"
498
- }
737
+ ---
738
+
739
+ ## 7. 3DS Handling
740
+
741
+ When a payment requires 3DS authentication, the SDK handles the challenge automatically. There are two display modes, controlled by [`redirectOnComplete`](#32-customization-options):
742
+
743
+ | `redirectOnComplete` | Challenge display | User experience |
744
+ |----------------------|-------------------|-----------------|
745
+ | `true` (default) | Full-page redirect to bank's 3DS page | User leaves the app; returns to `returnUrl` after completing auth |
746
+ | `false` | Rendered inside `#tdsIframe` in your app | User stays in app until the challenge resolves |
747
+
748
+ ### `redirectOnComplete: true` (default — full-page redirect)
749
+
750
+ No additional template changes required. Set `returnUrl` in the constructor and call `verify3dsTransaction()` on every page load — see [Section 4 — `verify3dsTransaction()`](#verify3dstransaction) for the full implementation pattern.
751
+
752
+ **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.
753
+
754
+ ---
755
+
756
+ ### `redirectOnComplete: false` (iframe mode — user stays in app)
757
+
758
+ Recommended for **Ionic / mobile WebViews** where a full-page redirect would break the app flow.
759
+
760
+ **1. Add the iframe to your template:**
761
+ ```html
762
+ <iframe id="tdsIframe" allowtransparency="true" class="tds-iframe"></iframe>
763
+ ```
764
+
765
+ **2. Add CSS — hidden by default, shown full-screen when the challenge activates:**
766
+ ```css
767
+ .tds-iframe {
768
+ display: none;
769
+ border: none;
770
+ position: fixed;
771
+ inset: 0;
772
+ width: 100vw;
773
+ height: 100vh;
774
+ z-index: 100;
775
+ background: white;
499
776
  }
500
777
  ```
501
778
 
502
- Notes:
503
- - `statusCode` comes from HTTP response when available; otherwise defaults to `500`.
504
- - `details.systemError` comes from backend error code when available; otherwise defaults to `APP_INTERNAL_001`.
505
- - In card-on-file flow failures, the SDK returns `CARD_ON_FILE_DECLINED`.
779
+ In this mode the SDK resolves the original `payment()` promise directly when the challenge completes — `verify3dsTransaction()` is not needed.
506
780
 
507
- ### Public method error mapping
781
+ ---
508
782
 
509
- | Method | Returned `error.code` |
510
- |---|---|
511
- | `payment(data)` | `PAYMENT_PROCESS_ERROR` or `CARD_ON_FILE_DECLINED` |
512
- | `getCustomerCards()` | `FETCH_CARDS_ERROR` |
513
- | `saveCustomerCard(cardData)` | `SAVE_CARD_ERROR` or `CARD_ON_FILE_DECLINED` |
514
- | `removeCustomerCard(cardId)` | `REMOVE_CARD_ERROR` |
515
- | `getCustomerPaymentMethods()` | `FETCH_PAYMENT_METHODS_ERROR` |
783
+ ## 8. Managing Saved Cards
516
784
 
785
+ ### 8.1 List saved cards
517
786
 
518
- ## Examples
787
+ ```typescript
788
+ getCustomerCards(): Promise<ICustomerCardsResponse>
789
+ ```
519
790
 
520
- Here are examples of how to implement Tonder Lite SDK:
791
+ ```typescript
792
+ interface ICustomerCardsResponse {
793
+ user_id: number;
794
+ cards: ICard[];
795
+ }
521
796
 
522
- ### Angular
797
+ interface ICard {
798
+ fields: ICardSkyflowFields;
799
+ icon?: string; // URL to card brand image
800
+ }
523
801
 
524
- For Angular, we recommend using a service to manage the Tonder instance:
802
+ interface ICardSkyflowFields {
803
+ skyflow_id: string;
804
+ card_number: string; // Masked — e.g. 'XXXX-XXXX-XXXX-4242'
805
+ cardholder_name: string;
806
+ expiration_month: string; // e.g. '12'
807
+ expiration_year: string; // e.g. '25'
808
+ card_scheme: string; // e.g. 'VISA', 'MASTERCARD'
809
+ subscription_id?: string; // Present when Card On File is activated on your merchant account — see Section 8.4
810
+ }
811
+ ```
525
812
 
813
+ **Example:**
526
814
  ```typescript
527
- // tonder.service.ts
528
- import { Injectable } from "@angular/core";
529
- import { LiteCheckout } from "@tonder.io/ionic-lite-sdk";
530
- import {ILiteCheckout} from "@tonder.io/ionic-lite-sdk/dist/types/liteInlineCheckout";
815
+ const { cards } = await this.liteCheckout.getCustomerCards();
531
816
 
532
- @Injectable({
533
- providedIn: "root",
534
- })
535
- export class TonderService {
536
- private liteCheckout!: ILiteCheckout;
817
+ cards.forEach((card) => {
818
+ const lastFour = card.fields.card_number.slice(-4);
819
+ const expiry = `${card.fields.expiration_month}/${card.fields.expiration_year}`;
820
+ console.log(`${card.fields.card_scheme} •••• ${lastFour} — expires ${expiry}`);
821
+ });
822
+ ```
537
823
 
538
- constructor(@Inject(Object) private sdkParameters: IInlineLiteCheckoutOptions) {
539
- this.initializeInlineCheckout();
540
- }
824
+ ---
541
825
 
542
- private initializeInlineCheckout(): void {
543
- this.liteCheckout = new LiteCheckout({ ...this.sdkParameters });
544
- }
826
+ ### 8.2 Save a new card
545
827
 
546
- configureCheckout(customerData: IConfigureCheckout): void {
547
- return this.liteCheckout.configureCheckout({ ...customerData });
548
- }
828
+ **Prerequisites:** All 5 card fields must be mounted via `mountCardFields()` with no `card_id`, and the user must have filled them in.
549
829
 
550
- async injectCheckout(): Promise<void> {
551
- return await this.liteCheckout.injectCheckout();
552
- }
830
+ ```typescript
831
+ saveCustomerCard(): Promise<ISaveCardResponse>
832
+ // Returns: { skyflow_id: string; user_id: number }
833
+ ```
553
834
 
554
- verify3dsTransaction(): Promise<ITransaction | IStartCheckoutResponse | void> {
555
- return this.liteCheckout.verify3dsTransaction();
556
- }
835
+ **Complete enrollment flow:**
836
+ ```typescript
837
+ // 1. Mount all 5 fields (see Section 5.1)
838
+ await this.liteCheckout.mountCardFields({
839
+ fields: ['cardholder_name', 'card_number', 'expiration_month', 'expiration_year', 'cvv'],
840
+ });
557
841
 
558
- payment(
559
- checkoutData: IProcessPaymentRequest,
560
- ): Promise<IStartCheckoutResponse> {
561
- return this.inlineCheckout.payment(checkoutData);
562
- }
842
+ // 2. User fills in the card form...
563
843
 
564
- // Add more functions, for example for lite sdk: get payment methods
844
+ // 3. Save the card
845
+ const saved = await this.liteCheckout.saveCustomerCard();
846
+ console.log('Saved card skyflow_id:', saved.skyflow_id);
565
847
 
566
- // getCustomerPaymentMethods(): Promise<IPaymentMethod[]> {
567
- // return this.liteCheckout.getCustomerPaymentMethods();
568
- // }
569
- }
848
+ // 4. Optionally reveal the saved card data (see Section 9)
849
+ await this.liteCheckout.revealCardFields({
850
+ fields: ['card_number', 'cardholder_name', 'expiration_month', 'expiration_year'],
851
+ });
852
+ ```
570
853
 
571
- // checkout.component.ts
572
- import { Component, OnInit } from "@angular/core";
573
- import { TonderService } from "./tonder.service";
574
-
575
- @Component({
576
- selector: "app-tonder-checkout",
577
- template: `
578
- <div id="container">
579
- <!-- Skyflow Elements will be injected into these divs -->
580
- <div id="collect_cardholder_name"></div>
581
- <div id="collect_card_number"></div>
582
- <div id="collect_expiration_month"></div>
583
- <div id="collect_expiration_year"></div>
584
- <div id="collect_cvv"></div>
585
-
586
- <div id="msgError">{{ errorMessage }}</div>
587
- <button (click)="pay()">Pay</button>
588
- </div>
589
- `,
590
- providers: [
591
- {
592
- provide: TonderService,
593
- useFactory: () =>
594
- new TonderService({
595
- apiKey: "YOUR_API_KEY",
596
- returnUrl: "http://localhost:8100/tabs/tab5",
597
- mode: "stage",
598
- }),
599
- },
600
- ],
601
- })
602
- export class TonderCheckoutComponent implements OnInit {
603
- loading = false;
604
- errorMessage = "";
605
- checkoutData: IProcessPaymentRequest = {
606
- customer: {
607
- firstName: "John",
608
- lastName: "Doe",
609
- email: "john.doe@example.com",
610
- phone: "+1234567890"
611
- },
612
- cart: {
613
- total: 25,
614
- items: [
615
- {
616
- description: "Test product",
617
- quantity: 1,
618
- price_unit: 25,
619
- discount: 0,
620
- taxes: 0,
621
- product_reference: 1,
622
- name: "Test product",
623
- amount_total: 25
624
- }
625
- ]
626
- },
627
- currency: "MXN"
628
- };
854
+ ---
629
855
 
630
- constructor(private tonderService: TonderService) {}
856
+ ### 8.3 Remove a card
631
857
 
632
- async ngOnInit() {
633
- this.tonderService.configureCheckout({
634
- customer: { email: this.checkoutData.customer.email },
635
- });
636
- await this.tonderService.injectCheckout();
637
- this.tonderService.verify3dsTransaction().then((response) => {
638
- console.log("Verify 3ds response", response);
639
- });
858
+ ```typescript
859
+ removeCustomerCard(skyflowId: string): Promise<string>
860
+ // skyflowId = card.fields.skyflow_id from getCustomerCards()
861
+ ```
640
862
 
641
- // Mount Skyflow Elements for the new card form
642
- await this.tonderService.mountCardFields({
643
- fields: ['cardholder_name', 'card_number', 'expiration_month', 'expiration_year', 'cvv']
644
- });
645
- }
863
+ ```typescript
864
+ await this.liteCheckout.removeCustomerCard(card.fields.skyflow_id);
865
+ // Refresh the card list after removal
866
+ const { cards } = await this.liteCheckout.getCustomerCards();
867
+ ```
646
868
 
647
- async pay() {
648
- this.loading = true;
649
- try {
650
- // No `card` field needed — data is collected from Skyflow Elements
651
- const response = await this.tonderService.payment(this.checkoutData);
652
- console.log("Payment successful:", response);
653
- alert("Payment successful");
654
- } catch (error) {
655
- console.error("Payment failed:", error);
656
- this.errorMessage = (error as any).message;
657
- } finally {
658
- this.loading = false;
659
- }
660
- }
869
+ ---
870
+
871
+ ### 8.4 Card On File (`subscription_id`)
872
+
873
+ 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.
874
+
875
+ **Conditional CVV rule:**
876
+
877
+ | `subscription_id` present | CVV required? | Action |
878
+ |--------------------------|--------------|--------|
879
+ | Yes | No | Call `payment()` directly with `card: skyflow_id` |
880
+ | No | Yes | Mount CVV with `mountCardFields({ fields: ['cvv'], card_id })` first |
881
+
882
+ ```typescript
883
+ const selectedCard = cards.find(c => c.fields.skyflow_id === selectedId);
884
+
885
+ if (!selectedCard.fields.subscription_id) {
886
+ // Need CVV — mount the field
887
+ await this.liteCheckout.mountCardFields({
888
+ fields: ['cvv'],
889
+ card_id: selectedCard.fields.skyflow_id,
890
+ });
661
891
  }
892
+
893
+ // Then pay
894
+ await this.liteCheckout.payment({
895
+ ...customerCartData,
896
+ card: selectedCard.fields.skyflow_id,
897
+ });
662
898
  ```
663
899
 
664
- ### React: Request CVV for saved card
900
+ **Legacy cards without `subscription_id`**
665
901
 
666
- ```tsx
667
- import { LiteCheckout } from '@tonder.io/ionic-lite-sdk';
668
- import { useEffect, useState } from 'react';
669
-
670
- const checkoutData = {
671
- customer: {
672
- firstName: "Adrian",
673
- lastName: "Martinez",
674
- country: "Mexico",
675
- address: "Pinos 507, Col El Tecuan",
676
- city: "Durango",
677
- state: "Durango",
678
- postCode: "34105",
679
- email: "test@example.com",
680
- phone: "8161234567",
681
- },
682
- cart: {
683
- total: 120,
684
- items: [
685
- {
686
- description: "Test product description",
687
- quantity: 1,
688
- price_unit: 120,
689
- discount: 25,
690
- taxes: 12,
691
- product_reference: 12,
692
- name: "Test product",
693
- amount_total: 120
694
- }
695
- ]
696
- },
697
- currency: "MXN",
698
- // Reference from the merchant
699
- order_reference: "ORD-123456",
700
- metadata: {
701
- business_user: "123456-test"
702
- },
703
- };
902
+ Cards saved before Card On File was enabled may not have `subscription_id`. You have three options:
704
903
 
705
- const ExploreContainer = () => {
706
- const [liteCheckout, setLiteCheckout] = useState<any>(null);
707
- const [cards, setCards] = useState<any[]>([]);
708
- const [selectedCardId, setSelectedCardId] = useState<string | null>(null);
709
- const [loading, setLoading] = useState<boolean>(false);
904
+ 1. Remove the card with `removeCustomerCard()` and let the user re-enroll.
905
+ 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.
906
+ 3. Contact Tonder support to migrate specific cards.
710
907
 
711
- useEffect(() => {
712
- if (!liteCheckout) {
713
- setLoading(true);
714
- initializeTonderSDK();
715
- }
716
- }, []);
908
+ ---
717
909
 
718
- useEffect(() => {
719
- if (liteCheckout) {
720
- fetchCards();
721
- }
722
- }, [liteCheckout]);
723
-
724
- const initializeTonderSDK = async () => {
725
- const sdk = new LiteCheckout({
726
- mode: "stage",
727
- apiKey: "YOUR_API_KEY",
728
- returnUrl: window.location.href,
729
- customization: { redirectOnComplete: false },
730
- events: {
731
- cvvEvents: {
732
- onChange: (data) => {
733
- console.log("CVV onChange event data:", data);
734
- }
735
- }
736
- }
737
- });
738
- setLiteCheckout(sdk);
910
+ ## 9. Revealing Card Data — `revealCardFields`
739
911
 
740
- // Get secure token from your backend
741
- const token = "123"
912
+ 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.
742
913
 
743
- sdk.configureCheckout({ ...checkoutData, secureToken: token });
744
- await sdk.injectCheckout();
745
- sdk.verify3dsTransaction().then((response: any) => {
746
- console.log('Verify 3ds response', response);
747
- });
748
- setLoading(false);
749
- };
914
+ > **When to call:** Only after a successful `saveCustomerCard()` or new-card `payment()`. Calling it without a prior successful card operation throws `MOUNT_COLLECT_ERROR`.
750
915
 
751
- const fetchCards = async () => {
752
- const response = await liteCheckout.getCustomerCards();
753
- setCards(response.cards || []);
754
- };
916
+ **Default container IDs and fixed redaction:**
755
917
 
756
- const handleSelectCard = (cardId: string) => {
757
- if (cardId === selectedCardId) return;
758
- setSelectedCardId(cardId);
759
- liteCheckout.mountCardFields({ fields: ["cvv"], card_id: cardId });
760
- };
918
+ | Field | Default Container ID | Redaction Applied |
919
+ |-------|---------------------|-------------------|
920
+ | `card_number` | `#reveal_card_number` | `MASKED` — e.g. `4111 11•• •••• 1234` |
921
+ | `cardholder_name` | `#reveal_cardholder_name` | `PLAIN_TEXT` |
922
+ | `expiration_month` | `#reveal_expiration_month` | `PLAIN_TEXT` |
923
+ | `expiration_year` | `#reveal_expiration_year` | `PLAIN_TEXT` |
761
924
 
762
- const handlePayment = async () => {
763
- if (!selectedCardId) return;
764
- try {
765
- const response = await liteCheckout.payment({ ...checkoutData, card: selectedCardId });
766
- } catch (err) {
767
- console.error("Payment error:", err);
768
- }
925
+ > **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**.
926
+
927
+ > **Note:** Reveal elements only support `base`, `copyIcon`, and `global` style variants (unlike Collect elements which also support `focus`, `complete`, `invalid`, etc.).
928
+
929
+ **Types:**
930
+
931
+ ```typescript
932
+ type RevealableCardField = 'card_number' | 'cardholder_name' | 'expiration_month' | 'expiration_year';
933
+
934
+ interface IRevealCardFieldsRequest {
935
+ fields: (RevealableCardField | IRevealCardField)[];
936
+ styles?: IRevealElementStyles; // Applied to all fields unless overridden per-field
937
+ }
938
+
939
+ interface IRevealCardField {
940
+ field: RevealableCardField;
941
+ container_id?: string; // default: #reveal_<field>
942
+ altText?: string; // Placeholder text shown before reveal() resolves
943
+ label?: string; // Label rendered above the field
944
+ styles?: IRevealElementStyles; // Per-field override; takes priority over request.styles
945
+ }
946
+
947
+ interface IRevealElementStyles {
948
+ inputStyles?: {
949
+ base?: Record<string, any>;
950
+ copyIcon?: Record<string, any>;
951
+ global?: Record<string, any>;
769
952
  };
953
+ labelStyles?: { base?: Record<string, any>; global?: Record<string, any> };
954
+ errorTextStyles?: { base?: Record<string, any>; global?: Record<string, any> };
955
+ }
956
+ ```
770
957
 
771
- return (
772
- <div className="container">
773
- <iframe className="tds-iframe" allowTransparency={true} id="tdsIframe"></iframe>
774
- {loading ? (
775
- <div style={{ textAlign: 'center', padding: '40px 0', color: '#007AFF' }}>
776
- <div style={{ fontWeight: 600, fontSize: 18 }}>Loading checkout...</div>
777
- </div>
778
- ) : (
779
- <>
780
- <p>Saved cards:</p>
781
- <div style={{ marginBottom: 24 }}>
782
- {cards.length > 0 ? (
783
- cards.map((card: any) => (
784
- <div
785
- key={card.fields.skyflow_id}
786
- style={{
787
- background: selectedCardId === card.fields.skyflow_id ? '#E3F2FD' : '#f9f9f9',
788
- borderRadius: 12,
789
- border: selectedCardId === card.fields.skyflow_id ? '2px solid #007AFF' : '2px solid transparent',
790
- marginBottom: 12,
791
- padding: 0,
792
- cursor: 'pointer',
793
- textAlign: 'left',
794
- }}
795
- onClick={() => handleSelectCard(card.fields.skyflow_id)}
796
- >
797
- <div style={{ display: 'flex', alignItems: 'center', padding: 16 }}>
798
- {card.icon && (
799
- <img src={card.icon} alt="card" style={{ width: 50, height: 32, marginRight: 16, objectFit: 'contain' }} />
800
- )}
801
- <div style={{ flex: 1, textAlign: 'left' }}>
802
- <div style={{ fontWeight: 'bold', color: '#333', marginBottom: 4 }}>{card.fields.cardholder_name}</div>
803
- <div style={{ color: '#666', marginBottom: 4 }}>•••• •••• •••• {card.fields.card_number.slice(-4)}</div>
804
- <div style={{ color: '#999', fontSize: 12 }}>Expires: {card.fields.expiration_month}/{card.fields.expiration_year}</div>
805
- </div>
806
- {selectedCardId === card.fields.skyflow_id && (
807
- <div
808
- style={{ marginLeft: 12, width: 120, background: '#fff', borderRadius: 8, boxShadow: '0 2px 8px #eee', padding: 8, textAlign: 'left', display: 'flex', flexDirection: 'column', alignItems: 'flex-start' }}
809
- onClick={e => e.stopPropagation()}
810
- >
811
- <div style={{ maxHeight: '90px' }} id={`collect_cvv_${card.fields.skyflow_id}`}></div>
812
- </div>
813
- )}
814
- </div>
815
- </div>
816
- ))
817
- ) : (
818
- <div style={{ textAlign: 'center', padding: '40px 0', color: '#999' }}>
819
- <div style={{ fontWeight: 600, fontSize: 16 }}>No saved cards</div>
820
- <div style={{ fontSize: 14, color: '#bbb' }}>Add a card to use this method</div>
821
- </div>
822
- )}
823
- </div>
824
- <button style={{ padding: '10px', background: '#ddd' }} onClick={handlePayment}>
825
- Pay with saved card
826
- </button>
827
- </>
828
- )}
829
- </div>
830
- );
831
- };
958
+ **Example 1 — Basic (shorthand):**
959
+
960
+ ```html
961
+ <div id="reveal_cardholder_name"></div>
962
+ <div id="reveal_card_number"></div>
963
+ <div id="reveal_expiration_month"></div>
964
+ <div id="reveal_expiration_year"></div>
832
965
  ```
833
966
 
967
+ ```typescript
968
+ await this.liteCheckout.saveCustomerCard();
969
+
970
+ // Reveal immediately after saving
971
+ await this.liteCheckout.revealCardFields({
972
+ fields: ['cardholder_name', 'card_number', 'expiration_month', 'expiration_year'],
973
+ });
974
+ // #reveal_card_number shows "4111 11•• •••• 1234"
975
+ // #reveal_cardholder_name shows "John Doe"
976
+ ```
977
+
978
+ **Example 2 — With `altText` and `label` per field:**
979
+
980
+ ```typescript
981
+ await this.liteCheckout.revealCardFields({
982
+ fields: [
983
+ { field: 'card_number', altText: '•••• •••• •••• ••••', label: 'Card Number' },
984
+ { field: 'cardholder_name', altText: 'Loading…', label: 'Cardholder' },
985
+ { field: 'expiration_month', label: 'Month' },
986
+ { field: 'expiration_year', label: 'Year' },
987
+ ],
988
+ });
989
+ ```
990
+
991
+ **Example 3 — Custom styles:**
992
+
993
+ ```typescript
994
+ await this.liteCheckout.revealCardFields({
995
+ fields: ['card_number', 'cardholder_name', 'expiration_month', 'expiration_year'],
996
+ styles: {
997
+ inputStyles: {
998
+ base: {
999
+ color: '#ffffff',
1000
+ fontFamily: '"Courier New", monospace',
1001
+ fontSize: '16px',
1002
+ background: 'transparent',
1003
+ letterSpacing: '2px',
1004
+ },
1005
+ },
1006
+ },
1007
+ });
1008
+ ```
834
1009
 
835
- ## Return secure token
1010
+ **Example 4 Custom container IDs:**
836
1011
 
837
1012
  ```typescript
1013
+ await this.liteCheckout.revealCardFields({
1014
+ fields: [
1015
+ { field: 'card_number', container_id: '#my-card-display' },
1016
+ { field: 'cardholder_name', container_id: '#my-name-display' },
1017
+ ],
1018
+ });
1019
+ ```
1020
+
1021
+ ---
1022
+
1023
+ ## 10. Error Handling
1024
+
1025
+ ### 10.1 Error structure
1026
+
1027
+ When a public SDK method fails, it throws an `AppError` instance with the following shape:
1028
+
1029
+ ```json
838
1030
  {
839
- access: string;
1031
+ "name": "TonderError",
1032
+ "status": "error",
1033
+ "code": "PAYMENT_PROCESS_ERROR",
1034
+ "message": "There was an issue processing the payment.",
1035
+ "statusCode": 500,
1036
+ "details": { ... }
840
1037
  }
841
1038
  ```
842
1039
 
843
- ## Deprecated Fields
1040
+ **Notes:**
1041
+ - `statusCode` reflects the HTTP status when available; defaults to `500` for non-HTTP errors.
1042
+ - `details` contains additional context about the error when available.
844
1043
 
845
- The following fields have been deprecated and should no longer be used. Consider using the recommended alternatives:
1044
+ **TypeScript catch pattern:**
846
1045
 
847
- ## Register customer card
848
- ### `apiKeyTonder` Property
1046
+ ```typescript
1047
+ import { AppError } from '@tonder.io/ionic-lite-sdk';
1048
+
1049
+ try {
1050
+ const response = await this.liteCheckout.payment(data);
1051
+ } catch (error) {
1052
+ if (error instanceof AppError) {
1053
+ console.error(`[${error.code}] ${error.message} (HTTP ${error.statusCode})`);
1054
+ // Use error.code to show a user-friendly message
1055
+ }
1056
+ }
1057
+ ```
849
1058
 
850
- - **Deprecated Reason:** The `apiKeyTonder` property in the constructor and `IInlineLiteCheckoutOptions` interface is no longer required.
851
- - **Alternative:** Use the `apiKey` field.
1059
+ ---
852
1060
 
853
- ### `baseUrlTonder` Property
1061
+ ### 10.2 Error code reference
854
1062
 
855
- - **Deprecated Reason:** The `baseUrlTonder` property in the constructor and `IInlineLiteCheckoutOptions` interface is no longer required.
856
- - **Alternative:** Use the `mode` field with `stage` | `development` | `sandbox` | `production` options.
1063
+ | Code | Thrown by | When |
1064
+ |------|-----------|------|
1065
+ | `PAYMENT_PROCESS_ERROR` | `payment()` | Any payment failure |
1066
+ | `CARD_ON_FILE_DECLINED` | `payment()`, `saveCustomerCard()` | Card On File authorization declined (only when Card On File is active on your account) |
1067
+ | `MOUNT_COLLECT_ERROR` | `mountCardFields()`, `revealCardFields()` | Secure fields fail to mount, or `revealCardFields()` called without a prior successful card operation |
1068
+ | `SAVE_CARD_ERROR` | `saveCustomerCard()` | Any error during card save |
1069
+ | `FETCH_CARDS_ERROR` | `getCustomerCards()` | Request to fetch saved cards fails |
1070
+ | `REMOVE_CARD_ERROR` | `removeCustomerCard()` | Request to remove a card fails |
1071
+ | `FETCH_PAYMENT_METHODS_ERROR` | `getCustomerPaymentMethods()` | Request to fetch APMs fails |
857
1072
 
858
- ### `signal` Property
1073
+ ---
859
1074
 
860
- - **Deprecated Reason:** The `signal` property in the constructor and `IInlineLiteCheckoutOptions` interface is no longer required.
1075
+ ## 11. Customization & Styling
861
1076
 
1077
+ ### 11.1 Global form styles
862
1078
 
863
- ## Deprecated Functions
1079
+ Apply styles to all card input fields at once via `customization.styles.cardForm`. Per-field overrides (Section 11.2) take priority.
864
1080
 
865
- The following functions have been deprecated and should no longer be used. Consider using the recommended alternatives:
1081
+ `cardForm` uses wrapper keys (`inputStyles`, `labelStyles`, `errorStyles`) defined by `ILiteCardFormStyles`. Per-field keys like `cardholderName`, `cardNumber`, `cvv`, etc. are directly `CollectInputStylesVariant` no wrapper.
866
1082
 
867
- ### `customerRegister`
1083
+ ```typescript
1084
+ new LiteCheckout({
1085
+ apiKey: 'YOUR_KEY',
1086
+ mode: 'production',
1087
+ returnUrl: 'https://myapp.com/checkout',
1088
+ customization: {
1089
+ styles: {
1090
+ // Show card brand icon inside the card_number field (default: true)
1091
+ enableCardIcon: true,
1092
+
1093
+ cardForm: {
1094
+ // Base typography applied to all fields
1095
+ base: {
1096
+ fontFamily: 'Inter, sans-serif',
1097
+ fontSize: '14px',
1098
+ color: '#1d1d1f',
1099
+ },
1100
+ // Styles applied to the <input> element inside each iframe
1101
+ inputStyles: {
1102
+ base: {
1103
+ border: '1px solid #d1d1d6',
1104
+ borderRadius: '8px',
1105
+ padding: '10px 12px',
1106
+ backgroundColor: '#ffffff',
1107
+ },
1108
+ focus: {
1109
+ borderColor: '#6200ee',
1110
+ boxShadow: '0 0 0 3px rgba(98, 0, 238, 0.15)',
1111
+ outline: 'none',
1112
+ },
1113
+ complete: {
1114
+ borderColor: '#34c759',
1115
+ },
1116
+ invalid: {
1117
+ borderColor: '#ff3b30',
1118
+ color: '#ff3b30',
1119
+ },
1120
+ empty: {
1121
+ borderColor: '#d1d1d6',
1122
+ },
1123
+ },
1124
+ // Styles applied to the field label
1125
+ labelStyles: {
1126
+ base: {
1127
+ fontSize: '12px',
1128
+ fontWeight: '500',
1129
+ color: '#6e6e73',
1130
+ marginBottom: '4px',
1131
+ },
1132
+ },
1133
+ // Styles applied to the validation error message
1134
+ errorStyles: {
1135
+ base: {
1136
+ color: '#ff3b30',
1137
+ fontSize: '11px',
1138
+ marginTop: '4px',
1139
+ },
1140
+ },
1141
+ },
1142
+ },
1143
+ },
1144
+ });
1145
+ ```
868
1146
 
869
- - **Deprecated Reason:** This function is no longer necessary as registration is now automatically handled during payment processing or when using card management methods.
1147
+ ---
870
1148
 
871
- ### `createOrder` and `createPayment`
1149
+ ### 11.2 Per-field styles
872
1150
 
873
- - **Deprecated Reason:** These functions have been replaced by the `payment` function, which now automatically handles order creation and payment processing.
874
- - **Alternative:** Use the `payment` function.
1151
+ Override styles for individual fields using the field keys in `IStyles`. These take priority over `cardForm` styles.
875
1152
 
876
- ### `startCheckoutRouter` and `startCheckoutRouterFull`
1153
+ > **Key difference from `cardForm`:** Per-field keys are directly `CollectInputStylesVariant` variants like `base`, `focus`, `invalid` go at the top level. There is **no** `inputStyles` wrapper.
877
1154
 
878
- - **Deprecated Reason:** These functions have been replaced by the `payment` function.
879
- - **Alternative:** Use the `payment` function.
1155
+ **Available style variants for `CollectInputStylesVariant`:**
880
1156
 
881
- ### `registerCustomerCard`
1157
+ ```typescript
1158
+ interface CollectInputStylesVariant {
1159
+ base?: Record<string, any>; // Default state
1160
+ focus?: Record<string, any>; // When field has focus
1161
+ complete?: Record<string, any>; // When field has a valid value
1162
+ invalid?: Record<string, any>; // When value fails validation
1163
+ empty?: Record<string, any>; // When field is empty (unfocused)
1164
+ cardIcon?: Record<string, any>; // Card brand icon (card_number only)
1165
+ dropdownIcon?: Record<string, any>; // Dropdown arrow icon
1166
+ dropdown?: Record<string, any>; // Dropdown container
1167
+ dropdownListItem?: Record<string, any>; // Each dropdown option
1168
+ global?: Record<string, any>; // Applied to the iframe root element
1169
+ }
1170
+ ```
882
1171
 
883
- - **Deprecated Reason:** This function has been renamed to `saveCustomerCard` to better align with its purpose. The method's usage has also been updated.
884
- - **Alternative:** Use the `saveCustomerCard` method and update your implementation to reflect the changes.
1172
+ **Per-field keys in `IStyles`:**
885
1173
 
886
- ### `deleteCustomerCard`
1174
+ | Key | Field |
1175
+ |-----|-------|
1176
+ | `cardholderName` | Cardholder name field |
1177
+ | `cardNumber` | Card number field |
1178
+ | `cvv` | CVV field |
1179
+ | `expirationMonth` | Expiration month field |
1180
+ | `expirationYear` | Expiration year field |
887
1181
 
888
- - **Deprecated Reason:** This function has been renamed to `removeCustomerCard` to better align with its purpose. The method's usage has also been updated.
889
- - **Alternative:** Use the `removeCustomerCard` method and update your implementation to reflect the changes.
1182
+ **Example highlight CVV with a different color scheme:**
890
1183
 
891
- ### `getActiveAPMs`
1184
+ ```typescript
1185
+ customization: {
1186
+ styles: {
1187
+ cvv: {
1188
+ base: { borderColor: '#8e44ad', backgroundColor: '#faf5ff' },
1189
+ focus: { borderColor: '#6c3483', boxShadow: '0 0 0 3px rgba(108,52,131,0.2)' },
1190
+ invalid: { borderColor: '#e74c3c', color: '#e74c3c' },
1191
+ complete: { borderColor: '#27ae60' },
1192
+ },
1193
+ },
1194
+ }
1195
+ ```
892
1196
 
893
- - **Deprecated Reason:** This function has been renamed to `getCustomerPaymentMethods` to better align with its purpose. The method's usage has also been updated.
894
- - **Alternative:** Use the `getCustomerPaymentMethods` method and update your implementation to reflect the changes.
1197
+ **Example full per-field override:**
895
1198
 
896
- ### Raw card fields in `payment()` and `saveCustomerCard()`
1199
+ ```typescript
1200
+ customization: {
1201
+ styles: {
1202
+ cardNumber: {
1203
+ base: { letterSpacing: '2px', fontFamily: '"Courier New", monospace' },
1204
+ cardIcon: { width: '32px', height: '20px' },
1205
+ },
1206
+ expirationMonth: {
1207
+ base: { textAlign: 'center' },
1208
+ },
1209
+ expirationYear: {
1210
+ base: { textAlign: 'center' },
1211
+ },
1212
+ },
1213
+ }
1214
+ ```
897
1215
 
898
- - **Deprecated Reason:** Passing raw card values (card number, CVV, etc.) directly to `payment()` or `saveCustomerCard()` is no longer supported. Card data must be collected via Skyflow Elements using `mountCardFields()`.
899
- - **Alternative:** Call `mountCardFields()` with the required fields, then call `payment()` or `saveCustomerCard()` without card arguments.
1216
+ ---
900
1217
 
901
- ### `getSkyflowTokens`
1218
+ ### 11.3 Labels & placeholders
902
1219
 
903
- - **Deprecated Reason:** Card registration and checkout are now automatically handled during the payment process or through card management methods, making this method unnecessary.
1220
+ ```typescript
1221
+ interface IFormLabels {
1222
+ name?: string; // Label for cardholder name field
1223
+ card_number?: string; // Label for card number field
1224
+ cvv?: string; // Label for CVV field
1225
+ expiry_date?: string; // Shared expiry label (if shown as one field)
1226
+ expiration_month?: string; // Label for expiration month field
1227
+ expiration_year?: string; // Label for expiration year field
1228
+ }
904
1229
 
905
- ### `getOpenpayDeviceSessionID`
1230
+ interface IFormPlaceholder {
1231
+ name?: string; // Placeholder for cardholder name
1232
+ card_number?: string; // Placeholder for card number
1233
+ cvv?: string; // Placeholder for CVV
1234
+ expiration_month?: string; // Placeholder for expiration month
1235
+ expiration_year?: string; // Placeholder for expiration year
1236
+ }
1237
+ ```
906
1238
 
907
- - **Deprecated Reason:** It is no longer necessary to use this method is now automatically handled during the payment process.
1239
+ **Example:**
908
1240
 
1241
+ ```typescript
1242
+ customization: {
1243
+ labels: {
1244
+ name: 'Cardholder Name',
1245
+ card_number: 'Card Number',
1246
+ cvv: 'Security Code (CVV)',
1247
+ expiration_month: 'Month',
1248
+ expiration_year: 'Year',
1249
+ },
1250
+ placeholders: {
1251
+ name: 'John Doe',
1252
+ card_number: '4111 1111 1111 1111',
1253
+ cvv: '•••',
1254
+ expiration_month: 'MM',
1255
+ expiration_year: 'YY',
1256
+ },
1257
+ }
1258
+ ```
1259
+
1260
+ ---
1261
+
1262
+ ## 12. Deprecated API
1263
+
1264
+ <details>
1265
+ <summary>Deprecated API — click to expand</summary>
1266
+
1267
+ ### Deprecated constructor properties
1268
+
1269
+ | Property | Replacement | Notes |
1270
+ |----------|-------------|-------|
1271
+ | `apiKeyTonder` | `apiKey` | Renamed for clarity |
1272
+ | `baseUrlTonder` | `mode` | Replaced by environment enum (`'stage'` \| `'production'` \| ...) |
1273
+ | `signal` | (removed) | AbortController signal is no longer needed |
1274
+
1275
+ ### Deprecated methods
909
1276
 
910
- ## Notes
1277
+ | Deprecated Method | Use Instead | Notes |
1278
+ |------------------|-------------|-------|
1279
+ | `getBusiness()` | (auto-handled) | No longer needed |
1280
+ | `customerRegister(email)` | (auto-handled) | No longer needed |
1281
+ | `createOrder(items)` | `payment()` | Replaced by unified `payment()` |
1282
+ | `createPayment(items)` | `payment()` | Replaced by unified `payment()` |
1283
+ | `startCheckoutRouter(data)` | `payment()` | Replaced by unified `payment()` |
1284
+ | `startCheckoutRouterFull(data)` | `payment()` | Replaced by unified `payment()` |
1285
+ | `registerCustomerCard(secureToken, customerToken, data)` | `saveCustomerCard()` | Signature changed; call `mountCardFields()` first |
1286
+ | `deleteCustomerCard(customerToken, skyflowId)` | `removeCustomerCard(skyflowId)` | Signature simplified |
1287
+ | `getActiveAPMs()` | `getCustomerPaymentMethods()` | Renamed |
1288
+ | `getSkyflowTokens(...)` | (auto-handled) | No longer needed |
1289
+ | `getOpenpayDeviceSessionID(...)` | (auto-handled) | No longer needed |
911
1290
 
912
- ### General
1291
+ ### Deprecated data patterns
913
1292
 
914
- - Replace `apiKey`, `mode`, `returnUrl` with your actual values.
915
- - Remember to use the `configureCheckout` function after creating an instance of `LiteCheckout`. This ensures that functions such as payment processing, saving cards, deleting cards, and others work correctly.
1293
+ **Raw card fields in `payment()` / `saveCustomerCard()`**
916
1294
 
1295
+ Passing raw card values (card number, CVV, etc.) directly to these methods is no longer supported. Card data must be collected via `mountCardFields()` first:
917
1296
 
918
- ## License
1297
+ ```typescript
1298
+ // ❌ Deprecated
1299
+ await liteCheckout.payment({ ..., card: { card_number: '4111...', cvv: '123', ... } });
1300
+
1301
+ // ✅ Current
1302
+ await liteCheckout.mountCardFields({ fields: ['cardholder_name', 'card_number', 'expiration_month', 'expiration_year', 'cvv'] });
1303
+ // user fills in the form
1304
+ await liteCheckout.payment({ customer, cart, currency });
1305
+ ```
1306
+
1307
+ **`returnUrl` in `IProcessPaymentRequest`**
1308
+
1309
+ Set `returnUrl` on the constructor instead of per-payment:
1310
+
1311
+ ```typescript
1312
+ // ❌ Deprecated
1313
+ await liteCheckout.payment({ ..., returnUrl: 'https://myapp.com/done' });
1314
+
1315
+ // ✅ Current — set on constructor
1316
+ new LiteCheckout({ apiKey, mode, returnUrl: 'https://myapp.com/done' });
1317
+ ```
1318
+
1319
+ </details>
919
1320
 
920
- [MIT](https://choosealicense.com/licenses/mit/)