@tonder.io/ionic-full-sdk 0.0.62 → 1.0.0-beta.develop.fcbbea8

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,537 +1,552 @@
1
- # Tonder Ionic Full SDK
1
+ # @tonder.io/ionic-full-sdk
2
2
 
3
- Tonder SDK helps to integrate the services Tonder offers in your own mobile app
3
+ PCI DSS–compliant payment SDK for Ionic and Angular.
4
+ Card data is collected through **Skyflow secure iframes** — raw card values never touch your application code.
4
5
 
5
6
  ## Table of Contents
6
7
 
7
- 1. [Installation](#installation)
8
- 2. [Usage](#usage)
9
- 3. [Configuration Options](#configuration-options)
10
- 4. [Card On File](#card-on-file)
11
- 5. [Styling InlineCheckout](#styling-inlinecheckout)
12
- 6. [Mobile Settings](#mobile-settings)
13
- 7. [Payment Data Structure](#payment-data-structure)
14
- 8. [API Reference](#api-reference)
15
- 9. [Error Handling](#error-handling)
16
- 10. [Events](#events)
17
- 11. [Examples](#examples)
18
- 12. [Deprecated Functions](#deprecated-functions)
19
- 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. [Processing Payments](#5-processing-payments)
16
+ - [5.1 Standard payment](#51-standard-payment)
17
+ - [5.2 Enrollment — saveCard()](#52-enrollment--savecard)
18
+ - [5.3 APM config (Mercado Pago)](#53-apm-config-mercado-pago)
19
+ - [5.4 Payment response reference](#54-payment-response-reference)
20
+ 6. [3DS Handling](#6-3ds-handling)
21
+ 7. [Managing Saved Cards](#7-managing-saved-cards)
22
+ - [7.1 Save a card](#71-save-a-card)
23
+ - [7.2 Card On File (subscription_id)](#72-card-on-file-subscription_id)
24
+ 8. [Error Handling](#8-error-handling)
25
+ - [8.1 Error structure](#81-error-structure)
26
+ - [8.2 Error code reference](#82-error-code-reference)
27
+ 9. [Customization & Styling](#9-customization--styling)
28
+ - [9.1 Global form styles — cardForm](#91-global-form-styles--cardform)
29
+ - [9.2 Per-field overrides](#92-per-field-overrides)
30
+ - [9.3 Labels & placeholders](#93-labels--placeholders)
31
+ - [9.4 CSS container classes](#94-css-container-classes)
32
+ 10. [Mobile Settings](#10-mobile-settings)
33
+ 11. [API Reference](#11-api-reference)
34
+ 12. [Deprecated API](#12-deprecated-api)
35
+ 13. [License](#13-license)
36
+
37
+ ---
38
+
39
+ ## 1. Quick Start
40
+
41
+ Get a working payment form in under 5 minutes. This example shows the minimum required setup for an Angular component. See [Section 4](#4-initialization-sequence) for the full step-by-step explanation.
42
+
43
+ **Angular template:**
20
44
 
21
- ## Installation
22
-
23
- You can install using NPM
45
+ ```html
46
+ <!-- Host element — the checkout renders inside this div -->
47
+ <div id="tonder-checkout"></div>
24
48
 
25
- ```bash
26
- npm i @tonder.io/ionic-full-sdk
49
+ <button (click)="pay()" [disabled]="loading">
50
+ {{ loading ? 'Processing…' : 'Pay' }}
51
+ </button>
27
52
  ```
28
53
 
29
- Add dependencies to the root of the app (index.html) only if you are going to use Openpay as the payment processor
54
+ **Angular component:**
30
55
 
31
- ```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
- ```
56
+ ```typescript
57
+ import { Component, OnInit, OnDestroy } from '@angular/core';
58
+ import { InlineCheckout } from '@tonder.io/ionic-full-sdk';
35
59
 
36
- ## Usage
60
+ @Component({ selector: 'app-checkout', templateUrl: './checkout.component.html' })
61
+ export class CheckoutComponent implements OnInit, OnDestroy {
62
+ private inlineCheckout!: InlineCheckout;
63
+ loading = false;
37
64
 
38
- InlineCheckout provides a pre-built, customizable checkout interface.
65
+ async ngOnInit() {
66
+ // Step 1 — Create instance
67
+ this.inlineCheckout = new InlineCheckout({
68
+ apiKey: 'YOUR_PUBLIC_API_KEY',
69
+ mode: 'stage',
70
+ returnUrl: `${window.location.origin}/checkout`,
71
+ });
39
72
 
40
- ### HTML Setup
73
+ // Step 2 — Fetch secure token from YOUR backend
74
+ // Your backend calls POST https://stage.tonder.io/api/secure-token/ with the secret key
75
+ // and returns { access: string } to the client. Never expose the secret key on the frontend.
76
+ const { access } = await fetch('/api/tonder-secure-token', { method: 'POST' })
77
+ .then(r => r.json());
41
78
 
42
- ```html
43
- <div>
44
- <h1>Checkout</h1>
45
- <!-- You have to add an entry point with the ID 'tonder-checkout' -->
46
- <div id="tonder-checkout"></div>
47
- </div>
48
- ```
79
+ // Step 3 — Configure with customer + token
80
+ this.inlineCheckout.configureCheckout({
81
+ customer: { email: 'user@example.com' },
82
+ secureToken: access,
83
+ });
49
84
 
50
- ### Import InlineCheckout class
85
+ // Step 4 — Render the checkout form
86
+ await this.inlineCheckout.injectCheckout();
51
87
 
52
- ```javascript
53
- import { InlineCheckout } from "@tonder.io/ionic-full-sdk";
54
- ```
88
+ // Step 5 — Check for a returning 3DS redirect
89
+ await this.inlineCheckout.verify3dsTransaction();
90
+ }
55
91
 
56
- ### Create instance
92
+ ngOnDestroy() {
93
+ this.inlineCheckout.removeCheckout();
94
+ }
57
95
 
58
- ```javascript
59
- const inlineCheckout = new InlineCheckout({
60
- apiKey,
61
- returnUrl,
62
- renderPaymentButton: false // true, if you need render default button of tonder
63
- });
96
+ async pay() {
97
+ this.loading = true;
98
+ try {
99
+ const response = await this.inlineCheckout.payment({
100
+ customer: { firstName: 'John', lastName: 'Doe', email: 'user@example.com', phone: '+525512345678' },
101
+ cart: { total: 100, items: [{ name: 'Product', description: 'Desc', quantity: 1, price_unit: 100, discount: 0, taxes: 0, product_reference: 'SKU-001', amount_total: 100 }] },
102
+ currency: 'MXN',
103
+ });
104
+ console.log('Transaction status:', response.transaction_status);
105
+ } finally {
106
+ this.loading = false;
107
+ }
108
+ }
109
+ }
110
+ ```
64
111
 
65
- // The configureCheckout function allows you to set initial information,
66
- // such as the customer's email, which is used to retrieve a list of saved cards or the secure token required to save a card.
67
- // You can refer to the Customer interface for the complete object structure that can be passed.
68
- inlineCheckout.configureCheckout({
69
- customer: {
70
- email: "example@email.com"
71
- },
72
- secureToken: "e89eb18.."
73
- });
112
+ > **Security note:** `YOUR_SECRET_API_KEY` is a server-side credential. Fetch the secure token from your own backend and return only the `access` value to the client.
74
113
 
75
- // Inject the checkout into the DOM
76
- inlineCheckout.injectCheckout();
114
+ ---
77
115
 
78
- // To verify a 3ds transaction you can use the following method
79
- // It should be called after the injectCheckout method
80
- // The response status will be one of the following
81
- // ['Declined', 'Cancelled', 'Failed', 'Success', 'Pending', 'Authorized']
116
+ ## 2. Installation
82
117
 
83
- inlineCheckout.verify3dsTransaction().then((response) => {
84
- console.log("Verify 3ds response", response);
85
- });
118
+ ```bash
119
+ npm install @tonder.io/ionic-full-sdk
120
+ # or
121
+ yarn add @tonder.io/ionic-full-sdk
86
122
  ```
87
123
 
88
- ```javascript
89
- // Process a payment
90
- const response = await inlineCheckout.payment(checkoutData);
124
+ ```typescript
125
+ import { InlineCheckout } from '@tonder.io/ionic-full-sdk';
91
126
  ```
92
127
 
93
- ## Configuration Options
94
-
95
- | Property | Type | Required | Description |
96
- |--------------------------------------------|----------|:--------:|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
97
- | mode | string | | Environment mode. Options: 'stage', 'production', 'sandbox'. Default: 'stage' |
98
- | apiKey | string | X | You can take this from you Tonder Dashboard |
99
- | returnUrl | string | | url where the checkout form is mounted (3ds) |
100
- | styles | object | | Custom styles for the checkout interface |
101
- | containerId | string | | If require a custom checkout container id, default value "tonder-checkout" |
102
- | collectorIds | object | | If require a custom div containers ids, default value `{"cardsListContainer": "cardsListContainer", "holderName": "collectCardholderName", "cardNumber": "collectCardNumber", "expirationMonth": "collectExpirationMonth", "expirationYear": "collectExpirationYear", "cvv": "collectCvv", "tonderPayButton": "tonderPayButton", "msgError": "msgError", "msgNotification": "msgNotification"}` |
103
- | callBack | function | | Callback function invoqued after the payment process end successfully |
104
- | isOpenpaySandbox | boolean | | Define if openpay work on sandbox, default value true |
105
- | isEnrollmentCard | boolean | | Use the SDK as an enrollment card. |
106
- | customization | object | | Object to customize the checkout behavior and UI. Default value `{saveCards: {showSaved: true, showSaveCardOption: true, autoSave: false}, paymentButton: {show: false, showAmount: true, text: 'Pagar'}}` |
107
- | customization.saveCards.showSaved | boolean | | Show saved cards in the checkout UI. Default value: `true` |
108
- | customization.saveCards.showSaveCardOption | object | | Show the option to save the card for future payments. Default value: `true` |
109
- | customization.saveCards.autoSave | object | | Automatically save the card without showing the option to the user. Default value: `false` |
110
- | customization.paymentButton.show | boolean | | Show or hide the payment button. Default value: `false` |
111
- | customization.paymentButton.text | string | | Custom text for the payment button. Default value: `Pagar` |
112
- | customization.paymentButton.showAmount | boolean | | Show or hide the amount in the payment button. Default value: `true` |
113
- | events | IEvents | No | Event handlers for card form input fields (onChange, onFocus, onBlur) |
114
- > **Important Note about SaveCard functionality**:
115
- > To properly implement the SaveCard feature, you must use a SecureToken. For detailed implementation instructions and best practices, please refer to our official documentation on [How to use SecureToken for secure card saving](https://docs.tonder.io/integration/sdks/secure-token#how-to-use-securetoken-for-secure-card-saving).
116
-
117
- ## Card On File
118
-
119
- Card On File is handled automatically by the SDK when enabled for your merchant account.
120
-
121
- - When Card On File is enabled, the SDK saves cards automatically to create a subscription and hides the "Save card" checkbox.
122
- - `customization.saveCards.autoSave` applies only when Card On File is disabled.
123
- - CVV handling for saved cards is automatic in `ionic-full`; the UI only prompts for CVV when required.
124
- - If Card On File is not enabled for your merchant, contact Tonder support.
125
-
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 saved card (from the list) and have the user add it again.
129
- 2) Run a payment flow with that card; 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.
131
-
132
- ## Styling InlineCheckout
133
-
134
- You can customize the appearance of InlineCheckout by passing a `styles` object:
135
-
136
- ```javascript
137
- const customStyles = {
138
- inputStyles: {
139
- base: {
140
- border: "1px solid #e0e0e0",
141
- padding: "10px 7px",
142
- borderRadius: "5px",
143
- color: "#1d1d1d",
144
- marginTop: "2px",
145
- backgroundColor: "white",
146
- fontFamily: '"Inter", sans-serif',
147
- fontSize: "16px",
148
- "&::placeholder": {
149
- color: "#ccc",
128
+
129
+ ---
130
+
131
+ ## 3. Constructor & Configuration
132
+
133
+ ### 3.1 Options reference
134
+
135
+ Pass these options when constructing `new InlineCheckout({ ... })`:
136
+
137
+ | Property | Type | Required | Default | Description |
138
+ |---|---|:---:|---|---|
139
+ | `apiKey` | `string` || | Public API key from the Tonder Dashboard |
140
+ | `mode` | `'stage' \| 'production'` | | `'stage'` | Environment. `'production'` routes to the production Tonder API **and** activates the Skyflow PROD vault — tokens created in one environment are not valid in another |
141
+ | `returnUrl` | `string` | | | URL the browser returns to after a 3DS redirect (see [Section 6](#6-3ds-handling)) |
142
+ | `styles` | `ICardStyles` | | | **Deprecated** use `customization.styles` instead (see [Section 9](#9-customization--styling)) |
143
+ | `containerId` | `string` | | `'tonder-checkout'` | `id` of the host DOM element the checkout renders inside |
144
+ | `collectorIds` | `CollectorIds` | | (built-in defaults) | Override internal iframe container IDs. Only needed when running multiple checkout instances on the same page |
145
+ | `callBack` | `(response?) => void` | | | Called after a successful payment |
146
+ | `renderPaymentButton` | `boolean` | | `false` | Show the built-in Tonder pay button inside the checkout |
147
+ | `renderSaveCardButton` | `boolean` | | `false` | Show the built-in save-card button inside the checkout |
148
+ | `customization` | `IInlineCustomizationOptions` | | | UI and behavior overrides (see [Section 3.2](#32-customization-options)) |
149
+ | `events` | `IEvents` | | — | Event handlers for card form inputs (see [Section 3.3](#33-form-events)) |
150
+ | `isEnrollmentCard` | `boolean` | | `false` | Use the SDK as enrollment-only (card saving without payment) |
151
+
152
+ ---
153
+
154
+ ### 3.2 Customization options
155
+
156
+ `IInlineCustomizationOptions` controls built-in UI behavior and card field styles:
157
+
158
+ | Property | Type | Default | Description |
159
+ |---|---|---|---|
160
+ | `styles` | `ICardStyles` | — | Card field styles. See [Section 9](#9-customization--styling) |
161
+ | `saveCards.showSaved` | `boolean` | `true` | Show the saved-cards list inside the checkout |
162
+ | `saveCards.showSaveCardOption` | `boolean` | `true` | Show the "Save this card" checkbox |
163
+ | `saveCards.autoSave` | `boolean` | `false` | Automatically save the card without showing the checkbox |
164
+ | `paymentButton.show` | `boolean` | `false` | Show the built-in pay button |
165
+ | `paymentButton.text` | `string` | `'Pagar'` | Label for the built-in pay button |
166
+ | `paymentButton.showAmount` | `boolean` | `true` | Show the transaction amount inside the pay button |
167
+
168
+ **Example:**
169
+
170
+ ```typescript
171
+ new InlineCheckout({
172
+ apiKey: 'YOUR_KEY',
173
+ customization: {
174
+ styles: {
175
+ cardForm: {
176
+ inputStyles: { base: { border: '1px solid #d1d1d6', borderRadius: '8px' } },
150
177
  },
151
178
  },
152
- cardIcon: {
153
- position: "absolute",
154
- left: "6px",
155
- bottom: "calc(50% - 12px)",
179
+ saveCards: {
180
+ showSaved: true,
181
+ showSaveCardOption: true,
182
+ autoSave: false,
156
183
  },
157
- complete: {
158
- color: "#4caf50",
159
- },
160
- empty: {},
161
- focus: {},
162
- invalid: {
163
- border: "1px solid #f44336",
164
- },
165
- global: {
166
- "@import":
167
- 'url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;700&display=swap")',
184
+ paymentButton: {
185
+ show: true,
186
+ text: 'Complete Payment',
187
+ showAmount: true,
168
188
  },
169
189
  },
170
- labelStyles: {
171
- base: {
172
- fontSize: "12px",
173
- fontWeight: "500",
174
- fontFamily: '"Inter", sans-serif',
190
+ });
191
+ ```
192
+
193
+ > **Important:** To use the save-card feature you must provide a `secureToken` via `configureCheckout()`. See the [Tonder SecureToken guide](https://docs.tonder.io/integration/sdks/secure-token#how-to-use-securetoken-for-secure-card-saving).
194
+
195
+ ---
196
+
197
+ ### 3.3 Form events
198
+
199
+ Subscribe to input events on individual card fields via the `events` option.
200
+
201
+ **Available event objects:**
202
+
203
+ | Event key | Field |
204
+ |---|---|
205
+ | `cardHolderEvents` | Cardholder name |
206
+ | `cardNumberEvents` | Card number |
207
+ | `cvvEvents` | CVV |
208
+ | `monthEvents` | Expiration month |
209
+ | `yearEvents` | Expiration year |
210
+
211
+ Each event object supports `onChange`, `onFocus`, and `onBlur` callbacks. All three receive an `IEventSecureInput` payload:
212
+
213
+ | Property | Type | Description |
214
+ |---|---|---|
215
+ | `elementType` | `string` | Field identifier (`'CARDHOLDER_NAME'`, `'CARD_NUMBER'`, `'CVV'`, `'EXPIRATION_MONTH'`, `'EXPIRATION_YEAR'`) |
216
+ | `isEmpty` | `boolean` | `true` when the field contains no input |
217
+ | `isFocused` | `boolean` | `true` when the field has keyboard focus |
218
+ | `isValid` | `boolean` | `true` when the value passes all validation rules |
219
+ | `value` | `string` | Current field value — see availability table below |
220
+
221
+ **`value` availability by environment:**
222
+
223
+ | Field | `mode: 'stage'` | `mode: 'production'` |
224
+ |---|---|---|
225
+ | `cardholder_name` | Full value | Full value |
226
+ | `card_number` | Full value | `''` (masked by Skyflow) |
227
+ | `cvv` | Full value | `''` (masked by Skyflow) |
228
+ | `expiration_month` | Full value | Full value |
229
+ | `expiration_year` | Full value | Full value |
230
+
231
+ **Example:**
232
+
233
+ ```typescript
234
+ new InlineCheckout({
235
+ apiKey: 'YOUR_KEY',
236
+ events: {
237
+ cardHolderEvents: {
238
+ onChange: (e) => console.log('Holder name changed — valid:', e.isValid),
239
+ onBlur: (e) => { if (!e.isValid) showError('Invalid cardholder name'); },
175
240
  },
176
- },
177
- errorTextStyles: {
178
- base: {
179
- fontSize: "12px",
180
- fontWeight: "500",
181
- color: "#f44336",
182
- fontFamily: '"Inter", sans-serif',
241
+ cardNumberEvents: {
242
+ onChange: (e) => console.log('Card number value:', e.value),
243
+ onFocus: (e) => clearErrors(),
244
+ },
245
+ cvvEvents: {
246
+ onBlur: (e) => { if (e.isEmpty) showError('CVV is required'); },
183
247
  },
184
248
  },
185
- labels: {
186
- nameLabel: "Card Holder Name",
187
- cardLabel: "Card Number",
188
- cvvLabel: "CVC/CVV",
189
- expiryDateLabel: "Expiration Date",
190
- },
191
- placeholders: {
192
- namePlaceholder: "Name as it appears on the card",
193
- cardPlaceholder: "1234 1234 1234 1234",
194
- cvvPlaceholder: "3-4 digits",
195
- expiryMonthPlaceholder: "MM",
196
- expiryYearPlaceholder: "YY",
197
- },
198
- };
249
+ });
199
250
  ```
200
251
 
201
- <font size="3">The styles param is related to the style of the inputs inside the checkout form, to customize the checkout container and the cards list you can use global styles on base to this classes</font>
252
+ ---
202
253
 
203
- ```css
204
- @import url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;700&display=swap");
254
+ ## 4. Initialization Sequence
205
255
 
206
- .container-tonder {
207
- background-color: #f9f9f9;
208
- margin: 0 auto;
209
- padding: 30px 10px 30px 10px;
210
- overflow: hidden;
211
- transition: max-height 0.5s ease-out;
212
- max-width: 600px;
213
- border: solid 1px #e3e3e3;
214
- }
256
+ Follow these steps on every page load that hosts the checkout:
215
257
 
216
- .collect-row {
217
- display: flex;
218
- justify-content: space-between;
219
- width: 100%;
220
- }
258
+ **1. Create the instance**
221
259
 
222
- .collect-row > :first-child {
223
- min-width: 120px;
224
- }
260
+ ```typescript
261
+ this.inlineCheckout = new InlineCheckout({
262
+ apiKey: 'YOUR_PUBLIC_API_KEY',
263
+ mode: 'stage', // 'production' in production — activates Skyflow PROD vault
264
+ returnUrl: `${window.location.origin}/checkout`,
265
+ });
266
+ ```
225
267
 
226
- .expiration-year {
227
- padding-top: 25px;
228
- }
268
+ **2. Fetch a secure token from your backend**
269
+ **Environment URLs:**
229
270
 
230
- .empty-div {
231
- height: 80px;
232
- margin-top: 2px;
233
- margin-bottom: 4px;
234
- margin-left: 10px;
235
- margin-right: 10px;
236
- }
271
+ | `mode` | Base URL |
272
+ |--------|----------|
273
+ | `'stage'` | `https://stage.tonder.io` |
274
+ | `'production'` | `https://app.tonder.io` |
237
275
 
238
- .error-container {
239
- color: red;
240
- background-color: #ffdbdb;
241
- margin-bottom: 13px;
242
- font-size: 80%;
243
- padding: 8px 10px;
244
- border-radius: 10px;
245
- text-align: left;
246
- }
276
+ ```typescript
277
+ // Your backend endpoint calls POST https://stage.tonder.io/api/secure-token/
278
+ // with Authorization: Token YOUR_SECRET_API_KEY and returns { access: string }
279
+ const { access } = await fetch('/api/tonder-secure-token', { method: 'POST' })
280
+ .then(r => r.json());
281
+ ```
247
282
 
248
- .message-container {
249
- color: green;
250
- background-color: #90ee90;
251
- margin-bottom: 13px;
252
- font-size: 80%;
253
- padding: 8px 10px;
254
- border-radius: 10px;
255
- text-align: left;
256
- }
283
+ **3. Configure with customer data and token**
257
284
 
258
- .pay-button {
259
- font-size: 16px;
260
- font-weight: bold;
261
- min-height: 2.3rem;
262
- border-radius: 0.5rem;
263
- cursor: pointer;
264
- width: 100%;
265
- padding: 1rem;
266
- text-align: center;
267
- border: none;
268
- background-color: #000;
269
- color: #fff;
270
- margin-bottom: 10px;
271
- display: none;
272
- }
285
+ ```typescript
286
+ this.inlineCheckout.configureCheckout({
287
+ customer: { email: 'user@example.com' },
288
+ secureToken: access,
289
+ // cart: { total: 100, items: [...] }, // set here or pass to payment()
290
+ // currency: 'MXN',
291
+ // order_reference: 'ORD-001',
292
+ // metadata: { internal_id: 'abc' },
293
+ });
294
+ ```
273
295
 
274
- .pay-button:disabled,
275
- pay-button[disabled] {
276
- background-color: #b9b9b9;
277
- }
296
+ **4. Render the checkout**
278
297
 
279
- .lds-dual-ring {
280
- display: inline-block;
281
- width: 14px;
282
- height: 14px;
283
- }
298
+ ```typescript
299
+ await this.inlineCheckout.injectCheckout();
300
+ ```
284
301
 
285
- .lds-dual-ring:after {
286
- content: " ";
287
- display: block;
288
- width: 14px;
289
- height: 14px;
290
- border-radius: 50%;
291
- border: 6px solid #fff;
292
- border-color: #fff transparent #fff transparent;
293
- animation: lds-dual-ring 1.2s linear infinite;
294
- }
302
+ **5. Handle a returning 3DS redirect**
295
303
 
296
- @keyframes lds-dual-ring {
297
- 0% {
298
- transform: rotate(0deg);
299
- }
300
- 100% {
301
- transform: rotate(360deg);
304
+ Call `verify3dsTransaction()` on every page load. It resolves immediately if this is not a 3DS return:
305
+
306
+ ```typescript
307
+ const tdsResult = await this.inlineCheckout.verify3dsTransaction();
308
+ if (tdsResult) {
309
+ const status = (tdsResult as any).transaction_status;
310
+ if (status === 'Success') {
311
+ // Navigate to confirmation page
312
+ } else {
313
+ // Show error to user
302
314
  }
303
315
  }
316
+ ```
304
317
 
305
- @media screen and (max-width: 600px) {
306
- .payment_method_zplit {
307
- font-size: 16px;
308
- width: 100%;
309
- }
318
+ **6. Trigger payment from your button**
310
319
 
311
- .payment_method_zplit label img {
312
- display: none;
313
- }
314
- }
320
+ ```typescript
321
+ const response = await this.inlineCheckout.payment({ ...paymentData });
322
+ console.log(response.transaction_status); // 'Success' | 'Pending' | 'Declined' | 'Failed'
323
+ ```
315
324
 
316
- .cards-list-container {
317
- display: flex;
318
- flex-direction: column;
319
- padding: 0px 10px 0px 10px;
320
- gap: 33% 20px;
321
- }
325
+ ---
322
326
 
323
- .checkbox label {
324
- margin-left: 10px;
325
- font-size: 12px;
326
- font-weight: 500;
327
- color: #1d1d1d;
328
- font-family: "Inter", sans-serif;
329
- }
327
+ ## 5. Processing Payments
330
328
 
331
- .checkbox {
332
- margin-top: 10px;
333
- margin-bottom: 10px;
334
- width: 100%;
335
- text-align: left;
336
- }
329
+ ### 5.1 Standard payment
337
330
 
338
- .pay-new-card {
339
- display: flex;
340
- justify-content: start;
341
- align-items: center;
342
- color: #1d1d1d;
343
- gap: 33% 20px;
344
- margin-top: 10px;
345
- margin-bottom: 10px;
346
- padding-left: 10px;
347
- padding-right: 10px;
348
- width: 90%;
349
- }
331
+ ```typescript
332
+ const response = await this.inlineCheckout.payment({
333
+ customer: {
334
+ firstName: 'John',
335
+ lastName: 'Doe',
336
+ country: 'MX',
337
+ street: '123 Main St',
338
+ city: 'Mexico City',
339
+ state: 'CDMX',
340
+ postCode: '06600',
341
+ email: 'john.doe@example.com',
342
+ phone: '+525512345678',
343
+ },
344
+ cart: {
345
+ total: 100,
346
+ items: [
347
+ {
348
+ name: 'Product Name',
349
+ description: 'Product description',
350
+ quantity: 1,
351
+ price_unit: 100,
352
+ discount: 0,
353
+ taxes: 0,
354
+ product_reference: 'SKU-001',
355
+ amount_total: 100,
356
+ },
357
+ ],
358
+ },
359
+ currency: 'MXN',
360
+ metadata: { order_id: 'ORDER-123' },
361
+ // apm_config: {} // Optional — only for APMs like Mercado Pago (see Section 5.3)
362
+ });
363
+ ```
350
364
 
351
- .pay-new-card .card-number {
352
- font-size: 16px;
353
- font-family: "Inter", sans-serif;
354
- }
365
+ ---
355
366
 
356
- .card-image {
357
- width: 27px;
358
- height: 20px;
359
- text-align: left;
360
- }
367
+ ### 5.2 Enrollment — `saveCard()`
361
368
 
362
- .card-item-label {
363
- display: flex;
364
- justify-content: start;
365
- align-items: center;
366
- color: #1d1d1d;
367
- gap: 33% 20px;
368
- margin-top: 10px;
369
- margin-bottom: 10px;
370
- padding-left: 10px;
371
- padding-right: 10px;
372
- width: 90%;
373
- }
369
+ Use `isEnrollmentCard: true` to run the SDK as a card-saving form without triggering a payment.
374
370
 
375
- .card_selected {
376
- width: 10%;
377
- }
371
+ ```typescript
372
+ this.inlineCheckout = new InlineCheckout({
373
+ apiKey: 'YOUR_KEY',
374
+ isEnrollmentCard: true,
375
+ });
378
376
 
379
- .card-item {
380
- display: flex;
381
- justify-content: start;
382
- align-items: center;
383
- gap: 33% 20px;
384
- }
377
+ // Configure with customer and secureToken (required for saving cards)
378
+ this.inlineCheckout.configureCheckout({
379
+ customer: { email: 'user@example.com' },
380
+ secureToken: access,
381
+ });
385
382
 
386
- .card-item .card-number {
387
- font-size: 16px;
388
- font-family: "Inter", sans-serif;
389
- }
383
+ await this.inlineCheckout.injectCheckout();
390
384
 
391
- .card-item .card-expiration {
392
- font-size: 16px;
393
- font-family: "Inter", sans-serif;
394
- }
385
+ // Later — save the card entered by the user
386
+ const skyflowId = await this.inlineCheckout.saveCard();
387
+ console.log('Saved card skyflow_id:', skyflowId);
395
388
  ```
396
389
 
390
+ ---
397
391
 
398
- ## Mobile settings
392
+ ### 5.3 APM config (Mercado Pago)
399
393
 
400
- <font size="3">If you are deploying to Android, edit your AndroidManifest.xml file to add the Internet permission.</font>
394
+ Pass `apm_config` inside `payment()` when using Mercado Pago or other APMs that require additional configuration.
401
395
 
402
- ```xml
403
- <!-- Required to fetch data from the internet. -->
404
- <uses-permission android:name="android.permission.INTERNET" />
396
+ <details>
397
+ <summary>APM Config Fields Mercado Pago</summary>
398
+
399
+ | Field | Type | Description |
400
+ |---|---|---|
401
+ | `binary_mode` | `boolean` | If `true`, payment must be approved or rejected immediately (no pending) |
402
+ | `additional_info` | `string` | Extra info shown during checkout and in payment details |
403
+ | `back_urls.success` | `string` | Redirect URL after successful payment |
404
+ | `back_urls.pending` | `string` | Redirect URL after pending payment |
405
+ | `back_urls.failure` | `string` | Redirect URL after failed/cancelled payment |
406
+ | `auto_return` | `'approved' \| 'all'` | Enables automatic redirection after payment completion |
407
+ | `payment_methods.excluded_payment_methods[].id` | `string` | Payment method ID to exclude (e.g. `'visa'`) |
408
+ | `payment_methods.excluded_payment_types[].id` | `string` | Payment type ID to exclude (e.g. `'ticket'`) |
409
+ | `payment_methods.default_payment_method_id` | `string` | Default payment method (e.g. `'master'`) |
410
+ | `payment_methods.installments` | `number` | Maximum number of installments allowed |
411
+ | `payment_methods.default_installments` | `number` | Default number of installments suggested |
412
+ | `expires` | `boolean` | Whether the preference has an expiration |
413
+ | `expiration_date_from` | `string` (ISO 8601) | Start of validity period |
414
+ | `expiration_date_to` | `string` (ISO 8601) | End of validity period |
415
+ | `statement_descriptor` | `string` | Text on payer's card statement (max 16 characters) |
416
+ | `marketplace` | `string` | Marketplace identifier (default: `'NONE'`) |
417
+ | `marketplace_fee` | `number` | Fee to collect as marketplace commission |
418
+ | `differential_pricing.id` | `number` | Differential pricing strategy ID |
419
+ | `shipments.mode` | `'custom' \| 'me2' \| 'not_specified'` | Shipping mode |
420
+ | `shipments.local_pickup` | `boolean` | Enable local branch pickup |
421
+ | `shipments.cost` | `number` | Shipping cost (custom mode only) |
422
+ | `shipments.free_shipping` | `boolean` | Free shipping flag (custom mode only) |
423
+ | `tracks[].type` | `'google_ad' \| 'facebook_ad'` | Ad tracker type |
424
+ | `tracks[].values.conversion_id` | `string` | Google Ads conversion ID |
425
+ | `tracks[].values.pixel_id` | `string` | Facebook Pixel ID |
426
+
427
+ </details>
428
+
429
+ ---
430
+
431
+ ### 5.4 Payment response reference
432
+
433
+ ```typescript
434
+ interface IStartCheckoutResponse {
435
+ status: string;
436
+ message: string;
437
+ transaction_status: string; // 'Success' | 'Pending' | 'Declined' | 'Failed'
438
+ transaction_id: number;
439
+ payment_id: number;
440
+ checkout_id: string;
441
+ is_route_finished: boolean;
442
+ provider: string;
443
+ psp_response: Record<string, any>; // Raw response from the payment processor
444
+ }
405
445
  ```
406
446
 
407
- ## Payment Data Structure
447
+ ---
408
448
 
409
- When calling the `payment` method, use the following data structure:
449
+ ## 6. 3DS Handling
410
450
 
411
- ### Field Descriptions
451
+ When a payment requires 3DS authentication, the SDK handles the challenge automatically. Two display modes are available, controlled by `redirectOnComplete` inside `customization`:
412
452
 
413
- - **customer**: Object containing the customer's personal information to be registered in the transaction.
453
+ | `redirectOnComplete` | Challenge display | User experience |
454
+ |---|---|---|
455
+ | `true` (default) | Full-page redirect to bank's 3DS page | User leaves the app; returns to `returnUrl` after completing auth |
456
+ | `false` | Rendered inside the checkout iframe | User stays in the app until the challenge resolves |
414
457
 
415
- - **cart**: Object containing the total amount and an array of items to be registered in the Tonder order.
458
+ > **Note:** Unlike `@tonder.io/ionic-lite-sdk`, you do **not** need to add any iframe element or CSS to your template. `injectCheckout()` renders the 3DS iframe automatically as part of the checkout HTML.
416
459
 
417
- - **total**: The total amount of the transaction.
418
- - **items**: An array of objects, each representing a product or service in the order.
419
- - name: name of the product
420
- - price_unit: valid float string with the price of the product
421
- - quantity: valid integer string with the quantity of this product
460
+ ### `redirectOnComplete: true` (default full-page redirect)
422
461
 
423
- - **currency**: String representing the currency code for the transaction (e.g., "MXN" for Mexican Peso).
462
+ Set `returnUrl` in the constructor. Call `verify3dsTransaction()` on every page load — the SDK detects whether this load is a 3DS return and handles it automatically.
424
463
 
425
- - **metadata**: Object for including any additional information about the transaction. This can be used for internal references or tracking.
426
- - **apm_config**: (Optional) Configuration object for APM-specific options. Only applicable when using alternative payment methods like Mercado Pago.
427
- <details>
428
- <summary>APM Config Fields - Mercado Pago</summary>
429
-
430
- | **Field** | **Type** | **Description** |
431
- |-------------------------------------|--------------------------------------------|---------------------------------------------------------------------------|
432
- | `binary_mode` | `boolean` | If `true`, payment must be approved or rejected immediately (no pending). |
433
- | `additional_info` | `string` | Extra info shown during checkout and in payment details. |
434
- | `back_urls` | `object` | URLs to redirect the user after payment. |
435
- | └─ `success` | `string` | Redirect URL after successful payment. |
436
- | └─ `pending` | `string` | Redirect URL after pending payment. |
437
- | └─ `failure` | `string` | Redirect URL after failed/canceled payment. |
438
- | `auto_return` | `"approved"` \| `"all"` | Enables auto redirection after payment completion. |
439
- | `payment_methods` | `object` | Payment method restrictions and preferences. |
440
- | └─ `excluded_payment_methods[]` | `array` | List of payment methods to exclude. |
441
- | └─ `excluded_payment_methods[].id` | `string` | ID of payment method to exclude (e.g., "visa"). |
442
- | └─ `excluded_payment_types[]` | `array` | List of payment types to exclude. |
443
- | └─ `excluded_payment_types[].id` | `string` | ID of payment type to exclude (e.g., "ticket"). |
444
- | └─ `default_payment_method_id` | `string` | Default payment method (e.g., "master"). |
445
- | └─ `installments` | `number` | Max number of installments allowed. |
446
- | └─ `default_installments` | `number` | Default number of installments suggested. |
447
- | `expires` | `boolean` | Whether the preference has expiration. |
448
- | `expiration_date_from` | `string` (ISO 8601) | Start of validity period (e.g. `"2025-01-01T12:00:00-05:00"`). |
449
- | `expiration_date_to` | `string` (ISO 8601) | End of validity period. |
450
- | `differential_pricing` | `object` | Configuration for differential pricing. |
451
- | └─ `id` | `number` | ID of the differential pricing strategy. |
452
- | `marketplace` | `string` | Marketplace identifier (default: "NONE"). |
453
- | `marketplace_fee` | `number` | Fee to collect as marketplace commission. |
454
- | `tracks[]` | `array` | Ad tracking configurations. |
455
- | └─ `type` | `"google_ad"` \| `"facebook_ad"` | Type of tracker. |
456
- | └─ `values.conversion_id` | `string` | Google Ads conversion ID. |
457
- | └─ `values.conversion_label` | `string` | Google Ads label. |
458
- | └─ `values.pixel_id` | `string` | Facebook Pixel ID. |
459
- | `statement_descriptor` | `string` | Text on payer’s card statement (max 16 characters). |
460
- | `shipments` | `object` | Shipping configuration. |
461
- | └─ `mode` | `"custom"` \| `"me2"` \| `"not_specified"` | Type of shipping mode. |
462
- | └─ `local_pickup` | `boolean` | Enable pickup at local branch (for `me2`). |
463
- | └─ `dimensions` | `string` | Package dimensions (e.g. `10x10x10,500`). |
464
- | └─ `default_shipping_method` | `number` | Default shipping method (for `me2`). |
465
- | └─ `free_methods[]` | `array` | Shipping methods offered for free (for `me2`). |
466
- | └─ `free_methods[].id` | `number` | ID of free shipping method. |
467
- | └─ `cost` | `number` | Shipping cost (only for `custom` mode). |
468
- | └─ `free_shipping` | `boolean` | If `true`, shipping is free (`custom` only). |
469
- | └─ `receiver_address` | `object` | Shipping address. |
470
- | └─ `receiver_address.zip_code` | `string` | ZIP or postal code. |
471
- | └─ `receiver_address.street_name` | `string` | Street name. |
472
- | └─ `receiver_address.street_number` | `number` | Street number. |
473
- | └─ `receiver_address.city_name` | `string` | City name. |
474
- | └─ `receiver_address.state_name` | `string` | State name. |
475
- | └─ `receiver_address.country_name` | `string` | Country name. |
476
- | └─ `receiver_address.floor` | `string` | Floor (optional). |
477
- | └─ `receiver_address.apartment` | `string` | Apartment or unit (optional). |
478
- </details>
464
+ ```typescript
465
+ const tdsResult = await this.inlineCheckout.verify3dsTransaction();
466
+ if (tdsResult) {
467
+ const status = (tdsResult as any).transaction_status;
468
+ // 'Success' | 'Pending' | 'Declined' | 'Cancelled' | 'Failed' | 'Authorized'
469
+ }
470
+ ```
479
471
 
480
- ```javascript
481
- const paymentData = {
482
- customer: {
483
- firstName: "John",
484
- lastName: "Doe",
485
- country: "USA",
486
- address: "123 Main St",
487
- city: "Anytown",
488
- state: "CA",
489
- postCode: "12345",
490
- email: "john.doe@example.com",
491
- phone: "1234567890",
492
- identification:{
493
- type: "CPF",
494
- number: "19119119100"
495
- }
496
- },
497
- cart: {
498
- total: "100.00",
499
- items: [
500
- {
501
- description: "Product description",
502
- quantity: 1,
503
- price_unit: "100.00",
504
- discount: "0.00",
505
- taxes: "0.00",
506
- product_reference: "PROD123",
507
- name: "Product Name",
508
- amount_total: "100.00",
509
- },
510
- ],
511
- },
512
- currency: "MXN",
513
- metadata: {
514
- order_id: "ORDER123",
472
+ ---
473
+
474
+ ### `redirectOnComplete: false` (iframe mode — user stays in app)
475
+
476
+ Recommended for **Ionic / mobile WebViews** where a full-page redirect would break the navigation flow. Set the option in `customization` — no additional HTML or CSS needed:
477
+
478
+ ```typescript
479
+ this.inlineCheckout = new InlineCheckout({
480
+ apiKey: 'YOUR_PUBLIC_API_KEY',
481
+ mode: 'production',
482
+ customization: {
483
+ redirectOnComplete: false,
515
484
  },
516
- // apm_config: {} // Optional, only for APMs like Mercado Pago, Oxxo Pay
485
+ });
486
+ ```
487
+
488
+ In this mode the `payment()` promise resolves directly when the 3DS challenge completes — `verify3dsTransaction()` is not required.
489
+
490
+ ---
491
+
492
+ ## 7. Managing Saved Cards
493
+
494
+ ### 7.1 Save a card
495
+
496
+ `saveCard()` tokenizes the card data entered in the form and saves it to the customer's account. It returns the `skyflow_id` string.
497
+
498
+ ```typescript
499
+ saveCard(): Promise<string>
500
+ ```
517
501
 
518
- };
502
+ **Prerequisites:**
503
+ - The SDK must be initialized with `isEnrollmentCard: true` **or** the checkout must have the save-card UI visible
504
+ - A valid `secureToken` must be set via `configureCheckout()`
505
+
506
+ ```typescript
507
+ // Get a secure token for saving cards
508
+ const { access } = await fetch('/api/tonder-secure-token', {
509
+ method: 'POST',
510
+ }).then(r => r.json());
511
+
512
+ this.inlineCheckout.configureCheckout({
513
+ customer: { email: 'user@example.com' },
514
+ secureToken: token,
515
+ });
516
+
517
+ const skyflowId = await this.inlineCheckout.saveCard();
518
+ console.log('Card saved with skyflow_id:', skyflowId);
519
519
  ```
520
520
 
521
- ## API Reference
522
- - `configureCheckout(data)`: Set initial checkout data
523
- - `injectCheckout()`: Initialize the checkout
524
- - `payment(data)`: Process a payment
525
- - `verify3dsTransaction()`: Verify a 3DS transaction
526
- - `saveCard()`: Save a new card. This is useful when using sdk as an enrollment card `isEnrollmentCard`
527
- - `removeCheckout()`: Removes the checkout from the DOM and cleans up associated resources.
528
- - `setPaymentData(data)`: Set the payment data, such as customer, cart, and metadata. This is useful when using the default Tonder payment button `renderPaymentButton`.
529
521
 
530
- ## Error Handling
522
+ ---
523
+
524
+ ### 7.2 Card On File (`subscription_id`)
531
525
 
532
- Public SDK methods that fail due to API/SDK execution return an `AppError` (with `name: "TonderError"`).
526
+ Card On File is a feature that Tonder activates on your merchant account. When active, saved cards receive a `subscription_id` these cards do **not** require CVV entry on subsequent payments. The SDK handles this automatically in the checkout UI.
533
527
 
534
- ### Error structure
528
+ **What happens under the hood:**
529
+
530
+ | `subscription_id` present | CVV prompt shown | Behavior |
531
+ |---|:---:|---|
532
+ | Yes | No | Payment proceeds directly without CVV |
533
+ | No | Yes | SDK shows CVV entry before processing payment |
534
+
535
+ **Existing cards without `subscription_id`:**
536
+ Cards saved before Card On File was enabled may not have `subscription_id`. Options:
537
+ 1. Remove the saved card and ask the user to re-add it
538
+ 2. Run a payment with the card — the SDK creates a subscription and updates the card (note: this may generate a new `skyflow_id`)
539
+ 3. Contact Tonder support to reset saved cards for a user
540
+
541
+ > If Card On File is not enabled for your merchant, contact Tonder support.
542
+
543
+ ---
544
+
545
+ ## 8. Error Handling
546
+
547
+ Public SDK methods that fail return an `AppError` object with `name: 'TonderError'`.
548
+
549
+ ### 8.1 Error structure
535
550
 
536
551
  ```json
537
552
  {
@@ -548,402 +563,356 @@ Public SDK methods that fail due to API/SDK execution return an `AppError` (with
548
563
  }
549
564
  ```
550
565
 
551
- Notes:
552
- - `statusCode` comes from HTTP response when available; otherwise defaults to `500`.
553
- - `details.systemError` comes from backend error code when available; otherwise defaults to `APP_INTERNAL_001`.
554
- - In card-on-file decline scenarios, UI rendering (`showError`) displays: `Transaccion declinada. Verique los datos de su tarjeta.`
566
+ - `statusCode` comes from the HTTP response when available; defaults to `500`
567
+ - `details.systemError` comes from the backend error code when available; defaults to `APP_INTERNAL_001`
555
568
 
556
- ### Public method error mapping
557
-
558
- | Method | Returned `error.code` |
559
- |---|---|
560
- | `payment(data)` | `PAYMENT_PROCESS_ERROR` or `CARD_ON_FILE_DECLINED` |
561
- | `saveCard()` | `SAVE_CARD_ERROR` or `CARD_ON_FILE_DECLINED` |
562
-
563
-
564
- ## Events
565
- The SDK provides event handling capabilities for card form input fields, supporting both Full/Enrollment SDK implementations.
566
-
567
- ### Event Types
568
- Each event object supports:
569
- - `onChange`: Called when input value changes
570
- - `onFocus`: Called when input receives focus
571
- - `onBlur`: Called when input loses focus
572
- <details>
573
- <summary>View Interface</summary>
569
+ **Usage:**
574
570
 
575
571
  ```typescript
576
- export interface IEvents {
577
- onChange?: (event: IEventSecureInput) => void;
578
- onFocus?: (event: IEventSecureInput) => void;
579
- onBlur?: (event: IEventSecureInput) => void;
572
+ try {
573
+ const response = await this.inlineCheckout.payment(data);
574
+ } catch (error: any) {
575
+ if (error.name === 'TonderError') {
576
+ console.error(`[${error.code}] ${error.message}`);
577
+ }
580
578
  }
581
579
  ```
582
- </details>
583
-
584
- ### Input event properties
585
580
 
586
- | Property | Type | Description |
587
- |-------------|---------|-------------------------------------------------------------------------------------------------------------|
588
- | elementType | string | Type of input element (e.g. 'CARDHOLDER_NAME', 'CARD_NUMBER', 'EXPIRATION_YEAR', 'EXPIRATION_MONTH', 'CVV') |
589
- | isEmpty | boolean | Whether the input field has a value |
590
- | isFocused | boolean | Whether the input field currently has focus |
591
- | isValid | boolean | Whether the input value passes validation rules |
581
+ ---
592
582
 
593
- <details>
594
- <summary>View Interface</summary>
583
+ ### 8.2 Error code reference
595
584
 
596
- ```typescript
597
- export interface IEventSecureInput {
598
- elementType: string;
599
- isEmpty: boolean;
600
- isFocused: boolean;
601
- isValid: boolean;
602
- }
603
- ```
604
- </details>
585
+ | Method | Possible `error.code` |
586
+ |---|---|
587
+ | `payment()` | `PAYMENT_PROCESS_ERROR` \| `CARD_ON_FILE_DECLINED` |
588
+ | `saveCard()` | `SAVE_CARD_ERROR` \| `CARD_ON_FILE_DECLINED` |
605
589
 
590
+ ---
606
591
 
607
- ### Full/Enrollment SDK Events
608
- Events are configured during SDK initialization.
592
+ ## 9. Customization & Styling
609
593
 
610
- #### Available Events
611
- | Event Object | Description |
612
- |------------------|-----------------------------------|
613
- | cardHolderEvents | Events for cardholder name input |
614
- | cardNumberEvents | Events for card number input |
615
- | cvvEvents | Events for CVV input |
616
- | monthEvents | Events for expiration month input |
617
- | yearEvents | Events for expiration year input |
594
+ ### 9.1 Global form styles — `cardForm`
618
595
 
619
- <details>
620
- <summary>View Interface</summary>
596
+ Pass styles inside `customization.styles` using `ICardStyles`. Use `cardForm` for styles applied to all fields:
621
597
 
622
598
  ```typescript
623
- export interface IEvents {
624
- cardHolderEvents?: IEvents;
625
- cardNumberEvents?: IEvents;
626
- cvvEvents?: IEvents;
627
- monthEvents?: IEvents;
628
- yearEvents?: IEvents;
629
- }
599
+ new InlineCheckout({
600
+ apiKey: 'YOUR_KEY',
601
+ customization: {
602
+ styles: {
603
+ // Show the card brand icon inside the card number field (default: true)
604
+ enableCardIcon: true,
605
+
606
+ // Global styles — applied to all fields as a baseline
607
+ cardForm: {
608
+ inputStyles: {
609
+ base: {
610
+ fontFamily: '"Inter", sans-serif',
611
+ fontSize: '16px',
612
+ color: '#1d1d1d',
613
+ border: '1px solid #e0e0e0',
614
+ borderRadius: '5px',
615
+ padding: '10px 7px',
616
+ marginTop: '2px',
617
+ backgroundColor: 'white',
618
+ '&::placeholder': { color: '#ccc' },
619
+ },
620
+ focus: {
621
+ borderColor: '#6200ee',
622
+ boxShadow: '0 0 0 3px rgba(98, 0, 238, 0.15)',
623
+ outline: 'none',
624
+ },
625
+ complete: { borderColor: '#4caf50' },
626
+ invalid: { border: '1px solid #f44336' },
627
+ cardIcon: {
628
+ position: 'absolute',
629
+ left: '6px',
630
+ bottom: 'calc(50% - 12px)',
631
+ },
632
+ global: {
633
+ '@import': 'url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;700&display=swap")',
634
+ },
635
+ },
636
+ labelStyles: {
637
+ base: { fontSize: '12px', fontWeight: '500', fontFamily: '"Inter", sans-serif' },
638
+ },
639
+ errorStyles: {
640
+ base: { color: '#f44336', fontSize: '12px', fontWeight: '500' },
641
+ },
642
+ },
643
+ },
644
+ },
645
+ });
630
646
  ```
631
- </details>
632
647
 
633
- #### Example
648
+ **`ICardStyles` shape:**
649
+
634
650
  ```typescript
635
- const inlineCheckout = new InlineCheckout({
636
- events: {
637
- cardHolderEvents: {
638
- onChange: (event) => {
639
- console.log('Card holder change event', event);
640
- },
641
- onFocus: (event) => {
642
- console.log('Card holder focus event', event);
643
- },
644
- onBlur: (event) => {
645
- console.log('Card holder blur event', event);
646
- }
647
- },
648
- cvvEvents: {
649
- onChange: (event) => {
650
- console.log('Cvv change event', event);
651
- },
652
- onFocus: (event) => {
653
- console.log('Cvv focus event', event);
654
- },
655
- onBlur: (event) => {
656
- console.log('Cvv blur event', event);
657
- }
658
- }
659
- }
660
- });
651
+ {
652
+ enableCardIcon?: boolean, // default: true — show card brand icon in card number field
653
+
654
+ cardForm?: ICardFieldStyles, // Global — lowest priority (all fields)
655
+ cardholderName?: ICardFieldStyles,
656
+ cardNumber?: ICardFieldStyles,
657
+ cvv?: ICardFieldStyles,
658
+ expirationMonth?: ICardFieldStyles,
659
+ expirationYear?: ICardFieldStyles,
660
+
661
+ labels?: {
662
+ nameLabel?: string;
663
+ cardLabel?: string;
664
+ cvvLabel?: string;
665
+ expiryDateLabel?: string;
666
+ };
667
+ placeholders?: {
668
+ namePlaceholder?: string;
669
+ cardPlaceholder?: string;
670
+ cvvPlaceholder?: string;
671
+ expiryMonthPlaceholder?: string;
672
+ expiryYearPlaceholder?: string;
673
+ };
674
+ }
675
+
676
+ // ICardFieldStyles:
677
+ {
678
+ inputStyles?: {
679
+ base?: Record<string, any>;
680
+ focus?: Record<string, any>;
681
+ complete?: Record<string, any>;
682
+ invalid?: Record<string, any>;
683
+ empty?: Record<string, any>;
684
+ cardIcon?: Record<string, any>; // Card brand icon position (card_number only)
685
+ global?: Record<string, any>; // CSS @import / global rules
686
+ };
687
+ labelStyles?: { base?: Record<string, any> };
688
+ errorStyles?: { base?: Record<string, any> }; // developer-facing key
689
+ }
661
690
  ```
662
691
 
663
- ## Examples
692
+ > **Backward compatibility:** Passing `styles` at the **root** of `InlineCheckout` options is still supported but **deprecated** — move it to `customization.styles`. See [Section 12](#12-deprecated-api).
664
693
 
665
- Here are examples of how to implement Tonder SDK:
694
+ ---
666
695
 
667
- ### Ionic Angular - Checkout
696
+ ### 9.2 Per-field overrides
668
697
 
669
- For Angular, we recommend using a service to manage the Tonder instance:
698
+ Per-field keys have higher priority than `cardForm`. Only the properties you specify are overridden — everything else falls back to `cardForm` or the SDK defaults.
699
+
700
+ **Priority order: per-field → cardForm → SDK defaults**
670
701
 
671
702
  ```typescript
672
- // tonder.service.ts
673
- import { Injectable } from "@angular/core";
674
- import { InlineCheckout } from "@tonder.io/ionic-full-sdk";
675
- import {IInlineCheckout} from "@tonder.io/ionic-full-sdk/dist/types/inlineCheckout";
676
-
677
- @Injectable({
678
- providedIn: "root",
679
- })
680
- export class TonderService {
681
- private inlineCheckout: IInlineCheckout;
682
-
683
- constructor(@Inject(Object) private sdkParameters: IInlineCheckoutOptions) {
684
- this.initializeInlineCheckout();
685
- }
703
+ new InlineCheckout({
704
+ apiKey: 'YOUR_KEY',
705
+ customization: {
706
+ styles: {
707
+ // Baseline for all fields
708
+ cardForm: {
709
+ inputStyles: {
710
+ base: { border: '1px solid #e0e0e0', borderRadius: '5px', padding: '10px 7px' },
711
+ },
712
+ },
686
713
 
687
- private initializeInlineCheckout(): void {
688
- this.inlineCheckout = new InlineCheckout({ ...this.sdkParameters });
689
- }
714
+ // Override only the card number field
715
+ cardNumber: {
716
+ inputStyles: {
717
+ base: { letterSpacing: '2px' },
718
+ invalid: { borderColor: '#e74c3c', color: '#e74c3c' },
719
+ },
720
+ },
690
721
 
691
- configureCheckout(customerData: IConfigureCheckout): void {
692
- return this.inlineCheckout.configureCheckout({ ...customerData });
693
- }
722
+ // Override only the CVV field
723
+ cvv: {
724
+ inputStyles: {
725
+ base: { backgroundColor: '#faf5ff' },
726
+ },
727
+ errorStyles: {
728
+ base: { color: '#9b59b6' },
729
+ },
730
+ },
731
+ },
732
+ },
733
+ });
734
+ ```
694
735
 
695
- async injectCheckout(): Promise<void> {
696
- return await this.inlineCheckout.injectCheckout();
697
- }
736
+ Available field keys: `cardholderName`, `cardNumber`, `cvv`, `expirationMonth`, `expirationYear`.
698
737
 
699
- verify3dsTransaction(): Promise<ITransaction | IStartCheckoutResponse | void> {
700
- return this.inlineCheckout.verify3dsTransaction();
701
- }
738
+ ---
702
739
 
703
- payment(
704
- checkoutData: IProcessPaymentRequest
705
- ): Promise<IStartCheckoutResponse> {
706
- return this.inlineCheckout.payment(checkoutData);
707
- }
708
-
709
- removeCheckout(): void {
710
- return this.inlineCheckout.removeCheckout();
711
- }
712
- }
713
- ```
740
+ ### 9.3 Labels & placeholders
741
+
742
+ Customize field labels and placeholder text:
714
743
 
715
744
  ```typescript
716
- // checkout.component.ts
717
- import { Component, OnInit, OnDestroy } from "@angular/core";
718
- import { TonderService } from "./tonder.service";
719
-
720
- @Component({
721
- selector: "app-tonder-checkout",
722
- template: `
723
- <div id="tonder-checkout"></div>
724
- <button (click)="pay()" [disabled]="loading">
725
- {{ loading ? "Processing..." : "Pay" }}
726
- </button>
727
- `,
728
- providers: [
729
- {
730
- provide: TonderInlineService,
731
- // Initialization of the Tonder Inline SDK.
732
- // Note: Replace these credentials with your own in development/production.
733
- useFactory: () =>
734
- new TonderInlineService({
735
- apiKey: "11e3d3c3e95e0eaabbcae61ebad34ee5f93c3d27",
736
- returnUrl: "http://localhost:8100/tabs/tab1",
737
- mode: "stage",
738
- }),
745
+ new InlineCheckout({
746
+ apiKey: 'YOUR_KEY',
747
+ customization: {
748
+ styles: {
749
+ labels: {
750
+ nameLabel: 'Cardholder Name',
751
+ cardLabel: 'Card Number',
752
+ cvvLabel: 'CVV',
753
+ expiryDateLabel: 'Expiration Date',
754
+ },
755
+ placeholders: {
756
+ namePlaceholder: 'Name as it appears on the card',
757
+ cardPlaceholder: '1234 1234 1234 1234',
758
+ cvvPlaceholder: '3-4 digits',
759
+ expiryMonthPlaceholder: 'MM',
760
+ expiryYearPlaceholder: 'YY',
761
+ },
739
762
  },
740
- ],
741
- })
763
+ },
764
+ });
765
+ ```
742
766
 
743
- export class TonderCheckoutComponent implements OnInit, OnDestroy {
744
- loading = false;
745
- checkoutData: IProcessPaymentRequest;
746
-
747
- constructor(private tonderService: TonderService) {
748
- this.checkoutData = {
749
- customer: {
750
- firstName: "Jhon",
751
- lastName: "Doe",
752
- email: "john.c.calhoun@examplepetstore.com",
753
- phone: "+58452258525"
754
- },
755
- cart: {
756
- total: 25,
757
- items: [
758
- {
759
- description: "Test product description",
760
- quantity: 1,
761
- price_unit: 25,
762
- discount: 1,
763
- taxes: 12,
764
- product_reference: 89456123,
765
- name: "Test product",
766
- amount_total: 25
767
- }
768
- ]
769
- },
770
- metadata: {},
771
- currency: "MXN"
772
- }
773
- }
767
+ ---
774
768
 
775
- ngOnInit() {
776
- this.initCheckout();
777
- }
769
+ ### 9.4 CSS container classes
778
770
 
779
- ngOnDestroy() {
780
- // Clear the checkout upon destroying the component.
781
- this.tonderService.removeCheckout();
782
- }
771
+ The SDK generates the checkout HTML with the following CSS classes. Use these in your global styles to customize the checkout container and card list layout:
783
772
 
784
- async initCheckout() {
785
- this.tonderService.configureCheckout({
786
- customer: { email: "example@email.com" },
787
- });
788
- await this.tonderService.injectCheckout();
789
- this.tonderService.verify3dsTransaction().then((response) => {
790
- console.log("Verify 3ds response", response);
791
- });
792
- }
773
+ ```css
774
+ @import url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;700&display=swap");
793
775
 
794
- async pay() {
795
- this.loading = true;
796
- try {
797
- const response = await this.tonderService.payment(this.checkoutData);
798
- console.log("Payment successful:", response);
799
- alert("Payment successful");
800
- } catch (error) {
801
- console.error("Payment failed:", error);
802
- alert("Payment failed");
803
- } finally {
804
- this.loading = false;
805
- }
806
- }
776
+ .container-tonder {
777
+ background-color: #f9f9f9;
778
+ margin: 0 auto;
779
+ padding: 30px 10px;
780
+ overflow: hidden;
781
+ transition: max-height 0.5s ease-out;
782
+ max-width: 600px;
783
+ border: solid 1px #e3e3e3;
807
784
  }
808
- ```
809
-
810
785
 
811
- ### Ionic Angular - Enrollment Card
786
+ .collect-row {
787
+ display: flex;
788
+ justify-content: space-between;
789
+ width: 100%;
790
+ }
812
791
 
813
- For Angular, we recommend using a service to manage the Tonder instance:
792
+ .collect-row > :first-child {
793
+ min-width: 120px;
794
+ }
814
795
 
815
- ```typescript
816
- // tonder.service.ts
817
- import { Injectable } from "@angular/core";
818
- import { InlineCheckout } from "@tonder.io/ionic-full-sdk";
819
- import {IInlineCheckout} from "@tonder.io/ionic-full-sdk/dist/types/inlineCheckout";
820
-
821
- @Injectable({
822
- providedIn: "root",
823
- })
824
- export class TonderService {
825
- private inlineCheckout: IInlineCheckout;
826
-
827
- constructor(@Inject(Object) private sdkParameters: IInlineCheckoutOptions) {
828
- this.initializeInlineCheckout();
829
- }
796
+ .expiration-year {
797
+ padding-top: 25px;
798
+ }
830
799
 
831
- private initializeInlineCheckout(): void {
832
- this.inlineCheckout = new InlineCheckout({ ...this.sdkParameters });
833
- }
800
+ .empty-div {
801
+ height: 80px;
802
+ margin: 2px 10px 4px;
803
+ }
834
804
 
835
- configureCheckout(customerData: IConfigureCheckout): void {
836
- return this.inlineCheckout.configureCheckout({ ...customerData });
837
- }
805
+ .error-container {
806
+ color: red;
807
+ background-color: #ffdbdb;
808
+ margin-bottom: 13px;
809
+ font-size: 80%;
810
+ padding: 8px 10px;
811
+ border-radius: 10px;
812
+ text-align: left;
813
+ }
838
814
 
839
- async injectCheckout(): Promise<void> {
840
- return await this.inlineCheckout.injectCheckout();
841
- }
815
+ .message-container {
816
+ color: green;
817
+ background-color: #90ee90;
818
+ margin-bottom: 13px;
819
+ font-size: 80%;
820
+ padding: 8px 10px;
821
+ border-radius: 10px;
822
+ text-align: left;
823
+ }
842
824
 
843
- async saveCard(): Promise<string> {
844
- return await this.inlineCheckout.saveCard();
845
- }
846
-
847
- removeCheckout(): void {
848
- return this.inlineCheckout.removeCheckout();
849
- }
825
+ .pay-button {
826
+ font-size: 16px;
827
+ font-weight: bold;
828
+ min-height: 2.3rem;
829
+ border-radius: 0.5rem;
830
+ cursor: pointer;
831
+ width: 100%;
832
+ padding: 1rem;
833
+ border: none;
834
+ background-color: #000;
835
+ color: #fff;
836
+ margin-bottom: 10px;
837
+ display: none;
850
838
  }
851
- ```
852
839
 
853
- ```typescript
854
- // enrollment.component.ts
855
- import { Component, OnInit, OnDestroy } from "@angular/core";
856
- import { TonderService } from "./tonder.service";
857
-
858
- @Component({
859
- selector: "app-tonder-enrollment",
860
- template: `
861
- <div id="container">
862
- <form id="payment-form">
863
- <div id="tonder-checkout-enrollment"></div>
864
- </form>
865
- <button class="external-payment-button" (click)="onSave($event)">Guardar</button>
866
- </div>
867
- `,
868
- providers: [
869
- {
870
- provide: TonderInlineService,
871
- // Initialization of the Tonder Inline SDK.
872
- // Note: Replace these credentials with your own in development/production.
873
- useFactory: () =>
874
- new TonderInlineService({
875
- apiKey: "11e3d3c3e95e0eaabbcae61ebad34ee5f93c3d27",
876
- returnUrl: "http://localhost:8100/tabs/tab1",
877
- mode: "stage",
878
- }),
879
- },
880
- ],
881
- })
840
+ .pay-button:disabled {
841
+ background-color: #b9b9b9;
842
+ }
882
843
 
883
- export class TonderCheckoutComponent implements OnInit, OnDestroy {
884
- loading = false;
885
- customerData: IProcessPaymentRequest;
886
-
887
- constructor(private tonderService: TonderService) {
888
- this.customerData = {
889
- customer: {
890
- firstName: "Pedro",
891
- lastName: "Perez",
892
- country: "Finlandia",
893
- street: "The street",
894
- city: "The city",
895
- state: "The state",
896
- postCode: "98746",
897
- email: "jhon.doe@example.com",
898
- phone: "+58 4169855522"
899
- }
900
- }
901
- }
844
+ .cards-list-container {
845
+ display: flex;
846
+ flex-direction: column;
847
+ padding: 0 10px;
848
+ gap: 33% 20px;
849
+ }
902
850
 
903
- ngOnInit() {
904
- this.initCheckout();
905
- }
851
+ .card-item {
852
+ display: flex;
853
+ justify-content: start;
854
+ align-items: center;
855
+ gap: 33% 20px;
856
+ }
906
857
 
907
- ngOnDestroy() {
908
- // Clear the checkout upon destroying the component.
909
- this.tonderService.removeCheckout();
910
- }
858
+ .card-item .card-number,
859
+ .card-item .card-expiration {
860
+ font-size: 16px;
861
+ font-family: "Inter", sans-serif;
862
+ }
911
863
 
912
- async initCheckout() {
913
- this.tonderService.configureCheckout({
914
- customer: { email: "example@email.com" },
915
- });
916
- await this.tonderService.injectCheckout();
917
- }
864
+ .checkbox {
865
+ margin: 10px 0;
866
+ width: 100%;
867
+ text-align: left;
868
+ }
918
869
 
919
- async onSave(event: any) {
920
- await this.tonderService.saveCard()
921
- }
870
+ .checkbox label {
871
+ margin-left: 10px;
872
+ font-size: 12px;
873
+ font-weight: 500;
874
+ color: #1d1d1d;
875
+ font-family: "Inter", sans-serif;
922
876
  }
923
877
  ```
924
878
 
879
+ ---
925
880
 
926
- ## Deprecated Functions
881
+ ## 10. Mobile Settings
927
882
 
928
- The following functions have been deprecated and should no longer be used. Consider using the recommended alternatives:
883
+ **Android:** Add the Internet permission to your `AndroidManifest.xml`:
929
884
 
930
- ### `setCustomerEmail`
885
+ ```xml
886
+ <!-- Required to fetch data from the internet -->
887
+ <uses-permission android:name="android.permission.INTERNET" />
888
+ ```
931
889
 
932
- - **Deprecated Reason:** This function have been replaced by the `configureCheckout` function.
933
- - **Alternative:** Use the `configureCheckout` function.
890
+ ---
934
891
 
935
- ### `setCartTotal`
892
+ ## 11. API Reference
936
893
 
937
- - **Deprecated Reason:** It is no longer necessary to use this method is now automatically handled during the payment process.
894
+ | Method | Returns | Description |
895
+ |---|---|---|
896
+ | `configureCheckout(data)` | `void` | Set customer, `secureToken`, cart, currency, and metadata |
897
+ | `injectCheckout()` | `Promise<void>` | Render the checkout form into the DOM |
898
+ | `payment(data)` | `Promise<IStartCheckoutResponse>` | Process a payment |
899
+ | `verify3dsTransaction()` | `Promise<ITransaction \| IStartCheckoutResponse \| void>` | Verify a 3DS result on page return; resolves immediately if not a 3DS return |
900
+ | `saveCard()` | `Promise<string>` | Save the card entered in the form; returns `skyflow_id` |
901
+ | `removeCheckout()` | `void` | Remove the checkout from the DOM and clean up resources |
938
902
 
903
+ ---
939
904
 
940
- ## Notes
905
+ ## 12. Deprecated API
941
906
 
942
- ### General
907
+ | Deprecated | Alternative |
908
+ |---|---|
909
+ | `new InlineCheckout({ styles: { ... } })` | `new InlineCheckout({ customization: { styles: { ... } } })` |
910
+ | `setCustomerEmail(email)` | `configureCheckout({ customer: { email } })` |
911
+ | `setCartTotal(total)` | `configureCheckout({ cart: { total } })` |
912
+ | `setPaymentData(data)` | `configureCheckout(data)` |
943
913
 
944
- - Replace `apiKey`, `mode`, `returnUrl` with your actual values.
945
- - Remember to use the `configureCheckout` function after creating an instance of `InlineCheckout`. This ensures that functions such as payment processing, saving cards, deleting cards, and others work correctly.
914
+ ---
946
915
 
947
- ## License
916
+ ## 13. License
948
917
 
949
- [MIT](https://choosealicense.com/licenses/mit/)
918
+ Copyright © Tonder. All rights reserved.