tonder-web-sdk 1.16.15 → 2.0.0

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,1059 +1,1493 @@
1
- # Tonder SDK
1
+ # tonder-web-sdk
2
2
 
3
- Tonder SDK helps to integrate the services Tonder offers in your own website
3
+ PCI DSS–compliant payment SDK for web applications.
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
- - [InlineCheckout](#inlinecheckout)
10
- - [LiteCheckout](#litecheckout)
11
- 3. [Configuration Options](#configuration)
12
- - [Inline Options](#inline-options)
13
- - [Lite Options](#lite-options)
14
- 4. [Styling InlineCheckout](#styling-inlinecheckout)
15
- 5. [Payment Data Structure](#payment-data-structure)
16
- 6. [Field Validation Functions](#field-validation-functions)
17
- 7. [HMAC Signature Validation](#hmac-signature-validation)
18
- 8. [API Reference](#api-reference)
19
- 9. [Examples](#examples)
20
- 10. [License](#license)
8
+ 1. [Quick Start](#1-quick-start)
9
+ 2. [Installation](#2-installation)
10
+ 3. [Choosing an Integration](#3-choosing-an-integration)
11
+ 4. [Constructor & Configuration](#4-constructor--configuration)
12
+ - [4.1 InlineCheckout options](#41-inlinecheckout-options)
13
+ - [4.2 LiteInlineCheckout options](#42-liteinlinecheckout-options)
14
+ - [4.3 Secure token](#43-secure-token)
15
+ - [4.4 Form events](#44-form-events-icardvents)
16
+ 5. [Initialization Sequence](#5-initialization-sequence)
17
+ 6. [Collecting Card Data — `mountCardFields`](#6-collecting-card-data--mountcardfields-liteinlinecheckout)
18
+ - [6.1 New-card form (all 5 fields)](#61-new-card-form-all-5-fields)
19
+ - [6.2 Saved-card CVV only](#62-saved-card-cvv-only)
20
+ - [6.3 Unmounting fields](#63-unmounting-fields)
21
+ 7. [Processing Payments](#7-processing-payments)
22
+ - [7.1 New card](#71-new-card-payment)
23
+ - [7.2 Pay with a saved card](#72-pay-with-a-saved-card)
24
+ - [7.3 Alternative Payment Method (APM)](#73-alternative-payment-method-apm)
25
+ - [7.4 Payment response reference](#74-payment-response-reference)
26
+ 8. [3DS Handling](#8-3ds-handling)
27
+ 9. [Managing Saved Cards](#9-managing-saved-cards-liteinlinecheckout)
28
+ - [9.1 List saved cards](#91-list-saved-cards)
29
+ - [9.2 Save a new card (enrollment)](#92-save-a-new-card-enrollment)
30
+ - [9.3 Remove a card](#93-remove-a-card)
31
+ 10. [Revealing Card Data — `revealCardFields`](#10-revealing-card-data--revealcardfields-liteinlinecheckout)
32
+ 11. [Error Handling](#11-error-handling)
33
+ - [11.1 Error structure](#111-error-structure)
34
+ - [11.2 Error code reference](#112-error-code-reference)
35
+ 12. [Customization & Styling](#12-customization--styling)
36
+ - [12.1 InlineCheckout styles](#121-inlinecheckout-styles)
37
+ - [12.2 LiteInlineCheckout styles](#122-liteinlinecheckout-styles)
38
+ - [12.3 Labels & placeholders](#123-labels--placeholders-liteinlinecheckout)
39
+ - [12.4 InlineCheckout UI customization](#124-inlinecheckout-ui-customization)
40
+ 13. [Framework Examples](#13-framework-examples)
41
+ - [13.1 Vanilla JS — InlineCheckout](#131-vanilla-js--inlinecheckout)
42
+ - [13.2 Vanilla JS — LiteInlineCheckout](#132-vanilla-js--liteinlinecheckout)
43
+ - [13.3 React / Next.js](#133-react--nextjs)
44
+ - [13.4 Angular](#134-angular)
45
+ 14. [HMAC Signature Validation](#14-hmac-signature-validation)
46
+ 15. [Deprecated API](#15-deprecated-api)
47
+ 16. [License](#16-license)
48
+
49
+ ---
50
+
51
+ ## 1. Quick Start
52
+
53
+ Get a working payment form in under 5 minutes using **InlineCheckout** (pre-built UI):
21
54
 
22
- ## Installation
55
+ ```html
56
+ <!-- index.html -->
57
+ <script src="https://js.skyflow.com/v1/index.js"></script>
58
+
59
+ <!-- Checkout mounts here -->
60
+ <div id="tonder-checkout"></div>
61
+ ```
62
+
63
+ ```js
64
+ import { InlineCheckout } from "tonder-web-sdk";
65
+
66
+ const checkout = new InlineCheckout({
67
+ apiKey: "YOUR_PUBLIC_API_KEY",
68
+ mode: "stage",
69
+ returnUrl: `${window.location.origin}/checkout`,
70
+ });
71
+
72
+ // Fetch a secure token from your backend (see Section 4.3)
73
+ const { access } = await fetch("/api/tonder-secure-token", { method: "POST" }).then((r) =>
74
+ r.json()
75
+ );
76
+
77
+ checkout.configureCheckout({
78
+ customer: { email: "user@example.com" },
79
+ secureToken: access,
80
+ });
81
+
82
+ await checkout.injectCheckout();
83
+
84
+ // Handle returning from a 3DS redirect
85
+ const tdsResult = await checkout.verify3dsTransaction();
86
+ if (tdsResult) {
87
+ console.log("3DS result:", tdsResult.transaction_status);
88
+ }
89
+ ```
90
+
91
+ ```js
92
+ // Trigger payment from your own Pay button
93
+ const response = await checkout.payment({
94
+ customer: { firstName: "John", lastName: "Doe", email: "user@example.com" },
95
+ cart: { total: 100, items: [{ name: "Product A", description: "...", quantity: 1, price_unit: 100, discount: 0, taxes: 0, product_reference: "SKU-001", amount_total: 100 }] },
96
+ currency: "MXN",
97
+ });
98
+ console.log("Transaction status:", response.transaction_status);
99
+ ```
100
+
101
+ > **Security note:** `YOUR_SECRET_API_KEY` is a server-side credential. In production, fetch the secure token from your own backend and return only the `access` value to the client. See [Section 4.3](#43-secure-token).
102
+
103
+ ---
104
+
105
+ ## 2. Installation
23
106
 
24
- You can install the Tonder SDK using NPM or by including it via a script tag.
107
+ Choose **one** of the following installation methods:
25
108
 
26
- ### NPM Installation
109
+ ### Option A — NPM (recommended for bundlers)
27
110
 
28
111
  ```bash
29
- npm i tonder-web-sdk
112
+ npm install tonder-web-sdk
113
+ # or
114
+ yarn add tonder-web-sdk
30
115
  ```
31
116
 
32
- ### Script Tag Installation
117
+ ```js
118
+ import { InlineCheckout } from "tonder-web-sdk";
119
+ ```
120
+
121
+ ### Option B — Script Tag (no bundler)
122
+
123
+ Choose the URL that matches your environment:
33
124
 
34
125
  ```html
35
- <script src="https://zplit-prod.s3.amazonaws.com/v1/bundle.min.js"></script>
126
+ <!-- Stage -->
127
+ <script src="https://zplit-stage.s3.amazonaws.com/v2/bundle.min.js"></script>
128
+
129
+ <!-- Production -->
130
+ <script src="https://zplit-prod.s3.amazonaws.com/v2/bundle.min.js"></script>
36
131
  ```
37
132
 
38
- ### Dependencies
133
+ Both URLs expose `window.TonderSdk`. Use one at a time:
134
+
135
+ ```js
136
+ // InlineCheckout
137
+ const { InlineCheckout } = window.TonderSdk;
138
+
139
+ // or LiteInlineCheckout
140
+ const { LiteInlineCheckout } = window.TonderSdk;
141
+ ```
39
142
 
40
- Add dependencies to the root of the app (index.html)
143
+ ### Required Dependencies
144
+
145
+ Add these scripts to the `<head>` of every page that uses the SDK:
41
146
 
42
147
  ```html
43
148
  <script src="https://js.skyflow.com/v1/index.js"></script>
44
- <script src="https://openpay.s3.amazonaws.com/openpay.v1.min.js"></script>
45
- <script src="https://openpay.s3.amazonaws.com/openpay-data.v1.min.js"></script>
46
149
  ```
47
150
 
48
- ## Usage
151
+ ---
49
152
 
50
- ### InlineCheckout
153
+ ## 3. Choosing an Integration
51
154
 
52
- InlineCheckout provides a pre-built, customizable checkout interface.
155
+ | | `InlineCheckout` | `LiteInlineCheckout` |
156
+ |---|---|---|
157
+ | **UI** | Pre-built, fully styled checkout UI | Headless — you own the markup |
158
+ | **Card collection** | Automatically renders Skyflow iframes | You call `mountCardFields()` into your own `<div>` |
159
+ | **Saved cards** | Built-in saved-card list UI | Fetch with `getCustomerCards()`, render yourself |
160
+ | **APMs** | Built-in APM selector UI | Fetch with `getCustomerPaymentMethods()`, render yourself |
161
+ | **Customization** | `customization` options (show/hide sections, dark mode) | Full control over layout and styling |
162
+ | **Best for** | Drop-in checkout with minimal code | Fully custom checkout experiences |
53
163
 
54
- #### HTML Setup
164
+ ---
55
165
 
56
- You need to have an element where the inline checkout will be mounted. This should be a DIV element with the ID "tonder-checkout":
166
+ ## 4. Constructor & Configuration
57
167
 
58
- ```html
59
- <div>
60
- <h1>Checkout</h1>
61
- <div id="tonder-checkout"></div>
62
- </div>
168
+ ### 4.1 InlineCheckout options
169
+
170
+ ```js
171
+ import { InlineCheckout } from "tonder-web-sdk";
172
+
173
+ const checkout = new InlineCheckout(options);
63
174
  ```
64
175
 
65
- #### JavaScript Implementation
176
+ | Property | Type | Required | Default | Description |
177
+ |----------|------|----------|---------|-------------|
178
+ | `apiKey` | `string` | **Required** | — | Public API key from the Tonder Dashboard |
179
+ | `returnUrl` | `string` | **Required for 3DS** | — | URL the browser returns to after a 3DS redirect |
180
+ | `mode` | `'stage' \| 'production'` | Optional | `'stage'` | Target environment |
181
+ | `styles` | `ICardStyles` | Optional | `undefined` | Card field styles — see [Section 12.1](#121-inlinecheckout-styles) |
182
+ | `customization` | `CustomizationOptions` | Optional | `undefined` | UI controls (show/hide sections, dark mode) — see [Section 12.4](#124-inlinecheckout-ui-customization) |
183
+ | `callbacks` | `IInlineCallbacks` | Optional | `undefined` | `onCancel` callback |
184
+ | `events` | `ICardEvents` | Optional | `undefined` | Per-field `onChange` / `onFocus` / `onBlur` — see [Section 4.4](#44-form-events-icardvents) |
185
+ | `signatures` | `{ transaction?: string; customer?: string }` | Optional | `undefined` | HMAC signatures — see [Section 14](#14-hmac-signature-validation) |
186
+
187
+ **`IInlineCallbacks`:**
188
+
189
+ | Callback | Parameters | Description |
190
+ |----------|------------|-------------|
191
+ | `onCancel` | none | Called when the user clicks the cancel button |
192
+
193
+ **Environment URLs:**
66
194
 
67
- ```javascript
68
- import { InlineCheckout } from "tonder-web-sdk"; // Not required if using script tag
195
+ | `mode` | Base URL |
196
+ |--------|----------|
197
+ | `'stage'` | `https://stage.tonder.io` |
198
+ | `'production'` | `https://app.tonder.io` |
199
+
200
+ ---
201
+
202
+ ### 4.2 LiteInlineCheckout options
203
+
204
+ ```js
205
+ import { LiteInlineCheckout } from "tonder-web-sdk";
206
+
207
+ const liteCheckout = new LiteInlineCheckout(options);
69
208
  ```
70
209
 
71
- ```javascript
72
- // if using script tag, it should be initialized like this
73
- // new TonderSdk.InlineCheckout
74
- const inlineCheckout = new InlineCheckout({
75
- apiKey: "your-api-key",
76
- returnUrl: "https://your-website.com/checkout",
77
- styles: customStyles, // Optional, see Styling section
78
- signatures: {
79
- transaction: "nA6nQXxQ....=", // Optional HMAC signature for transaction
80
- customer: "2EVYDI0H5l5v4....=" // Optional HMAC signature for card-related ops
81
- }
82
- });
210
+ | Property | Type | Required | Default | Description |
211
+ |----------|------|----------|---------|-------------|
212
+ | `apiKey` | `string` | **Required** | — | Public API key from the Tonder Dashboard |
213
+ | `returnUrl` | `string` | **Required for 3DS** | — | URL the browser returns to after a 3DS redirect |
214
+ | `mode` | `'stage' \| 'production'` | Optional | `'stage'` | Target environment |
215
+ | `customization` | `ILiteCustomization` | Optional | `undefined` | Styles, labels, and placeholders — see [Section 12.2](#122-liteinlinecheckout-styles) |
216
+ | `events` | `ICardEvents` | Optional | `undefined` | Per-field `onChange` / `onFocus` / `onBlur` — see [Section 4.4](#44-form-events-icardvents) |
217
+ | `signatures` | `{ transaction?: string; customer?: string }` | Optional | `undefined` | HMAC signatures — see [Section 14](#14-hmac-signature-validation) |
83
218
 
84
- // The configureCheckout function allows you to set initial information,
85
- // such as the customer's email, which is used to retrieve a list of saved cards.
86
- inlineCheckout.configureCheckout({ customer: { email: "example@email.com" } });
219
+ ---
87
220
 
88
- // Inject the checkout into the DOM
89
- inlineCheckout.injectCheckout();
221
+ ### 4.3 Secure token
90
222
 
91
- // To verify a 3ds transaction you can use the following method
92
- // It should be called after the injectCheckout method
93
- // The response status will be one of the following
94
- // ['Declined', 'Cancelled', 'Failed', 'Success', 'Pending', 'Authorized']
223
+ The secure token is a short-lived credential required for `configureCheckout`. Fetch it from Tonder's API using your **secret API key**:
95
224
 
96
- inlineCheckout.verify3dsTransaction().then((response) => {
97
- console.log("Verify 3ds response", response);
98
- });
225
+ ```js
226
+ // Base URL by mode: stage → https://stage.tonder.io, production → https://app.tonder.io
227
+ const baseUrl = "https://stage.tonder.io";
228
+
229
+ const { access } = await fetch(`${baseUrl}/api/secure-token/`, {
230
+ method: "POST",
231
+ headers: {
232
+ Authorization: "Token YOUR_SECRET_API_KEY",
233
+ "Content-Type": "application/json",
234
+ },
235
+ }).then((r) => r.json());
236
+ ```
237
+
238
+ > **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.
239
+
240
+ ---
241
+
242
+ ### 4.4 Form events (`ICardEvents`)
243
+
244
+ Register callbacks to react to field state changes. Works the same way for both `InlineCheckout` and `LiteInlineCheckout`.
245
+
246
+ ```js
247
+ const events = {
248
+ cardNumberEvents: {
249
+ onChange: ({ isValid, isEmpty }) => {
250
+ setCardValid(isValid);
251
+ },
252
+ onBlur: ({ isValid, isEmpty }) => {
253
+ setShowCardError(!isValid && !isEmpty);
254
+ },
255
+ },
256
+ cvvEvents: {
257
+ onChange: ({ isValid }) => {
258
+ setCvvValid(isValid);
259
+ },
260
+ },
261
+ };
262
+
263
+ new InlineCheckout({ ..., events });
264
+ // or
265
+ new LiteInlineCheckout({ ..., events });
99
266
  ```
100
267
 
101
- ```javascript
102
- // Process a payment
103
- const response = await inlineCheckout.payment(checkoutData);
268
+ **`ICardEvents` shape:**
269
+
270
+ ```js
271
+ {
272
+ cardHolderEvents?: { onChange?, onFocus?, onBlur? },
273
+ cardNumberEvents?: { onChange?, onFocus?, onBlur? },
274
+ cvvEvents?: { onChange?, onFocus?, onBlur? },
275
+ monthEvents?: { onChange?, onFocus?, onBlur? },
276
+ yearEvents?: { onChange?, onFocus?, onBlur? },
277
+ }
104
278
  ```
105
279
 
106
- ### LiteCheckout
280
+ **Event payload (`ICardEventPayload`):**
281
+
282
+ | Field | Type | Description |
283
+ |-------|------|-------------|
284
+ | `elementType` | `string` | Field name (e.g. `'CARD_NUMBER'`, `'CVV'`) |
285
+ | `isEmpty` | `boolean` | `true` when the field has no input |
286
+ | `isFocused` | `boolean` | `true` when the field currently has focus |
287
+ | `isValid` | `boolean` | `true` when the field passes validation |
288
+ | `value` | `string` | See PCI note below |
107
289
 
108
- LiteCheckout allows you to build a custom checkout interface using Tonder's core functionality.
290
+ > **PCI note:** In `production`, `card_number` returns a **partially masked** value (first 8 digits for non-AMEX, first 6 for AMEX, rest masked). All other fields — including `cvv` — return `value: ''`. In `stage`/`development`, all fields return the actual value. Use `isValid` and `isEmpty` for UI state logic — never depend on `value` for business logic in production.
109
291
 
110
- ```javascript
111
- import { LiteCheckout } from "tonder-web-sdk";
292
+ ---
293
+
294
+ ## 5. Initialization Sequence
295
+
296
+ Both checkouts must be initialized in this exact order:
297
+
298
+ ```
299
+ 1. new InlineCheckout(options) / new LiteInlineCheckout(options)
300
+
301
+ 2. Fetch secure token from YOUR backend → { access: string }
302
+
303
+ 3. configureCheckout({ customer, secureToken, ...optional })
304
+
305
+ 4. await injectCheckout()
306
+
307
+ 5. result = await verify3dsTransaction()
308
+ ↓ if result → handle 3DS return; if void → continue ↓
309
+ 6. (LiteInlineCheckout only) await mountCardFields(...)
112
310
  ```
113
311
 
114
- ```javascript
115
- const liteCheckout = new LiteCheckout({
116
- apiKey: "your-api-key", // Your api key getted from Tonder Dashboard
117
- returnUrl: "http://your-website.com/checkout",
118
- signatures: {
119
- transaction: "nA6nQXxQ....=", // Optional HMAC signature for transaction
120
- customer: "2EVYDI0H5l5v4....=" // Optional HMAC signature for card-related ops
121
- }
312
+ ### `configureCheckout(data)`
313
+
314
+ Sets the customer identity and secure token. You can also pass payment defaults here — any field provided again in `payment()` overrides them.
315
+
316
+ ```js
317
+ checkout.configureCheckout({
318
+ customer: { email: "user@example.com" }, // Required
319
+ secureToken: access, // Required — from the token fetch
320
+ // Optional payment defaults:
321
+ // cart: { total: 100, items: [...] },
322
+ // currency: "MXN",
323
+ // metadata: { order_id: "ORD-001" },
122
324
  });
325
+ ```
326
+
327
+ | Field | Type | Required | Description |
328
+ |-------|------|----------|-------------|
329
+ | `customer` | `{ email: string }` or full `ICustomer` | **Required** | Customer identity |
330
+ | `secureToken` | `string` | **Required** | Short-lived token from `/api/secure-token/` |
331
+ | `cart` | `{ total, items }` | Optional | Can be set here or overridden in `payment()` |
332
+ | `currency` | `string` | Optional | ISO currency code (e.g. `'MXN'`) |
333
+ | `metadata` | `Record<string, any>` | Optional | Reporting fields (see [Section 7.1](#71-new-card-payment)) |
123
334
 
124
- // The configureCheckout function allows you to set initial information,
125
- // such as the customer's email, which is used to retrieve a list of saved cards.
126
- inlineCheckout.configureCheckout({ customer: { email: "example@email.com" } });
335
+ ### `injectCheckout()`
127
336
 
128
- // Initialize the checkout
129
- await liteCheckout.injectCheckout();
337
+ Initializes the checkout session. Must be **awaited** before calling `mountCardFields` or `payment`.
338
+
339
+ ```js
340
+ await checkout.injectCheckout();
130
341
  ```
131
342
 
132
- ```javascript
133
- // Retrieve customer's saved cards
134
- const cards = await liteCheckout.getCustomerCards();
343
+ ### `verify3dsTransaction()`
344
+
345
+ Call this on **every page load**. When the page is loaded as a return from a 3DS redirect, it verifies the transaction and resolves with the result. On a normal page load it resolves with `void`.
346
+
347
+ ```js
348
+ const tdsResult = await checkout.verify3dsTransaction();
349
+
350
+ if (tdsResult) {
351
+ // Returning from 3DS
352
+ if (tdsResult.transaction_status === "Success") {
353
+ // Navigate to order confirmation
354
+ } else {
355
+ // Show error to the user
356
+ }
357
+ return; // Do not mount card fields — payment flow already completed
358
+ }
359
+
360
+ // Normal page load — continue initialization
361
+ // (LiteInlineCheckout: proceed to mountCardFields)
135
362
  ```
136
363
 
137
- ```javascript
138
- // Save a new card
139
- const newCard = await liteCheckout.saveCustomerCard(cardData);
364
+ ---
365
+
366
+ ## 6. Collecting Card Data — `mountCardFields` (LiteInlineCheckout)
367
+
368
+ Mounts Skyflow secure iframes into your `<div>` containers. Card values are captured inside the iframe and **never pass through your application code**.
369
+
370
+ > **Prerequisite:** Container `<div>` elements must exist in the DOM before calling `mountCardFields()`.
371
+ > Must be called after `injectCheckout()` resolves.
372
+
373
+ ```js
374
+ // IMountCardFieldsRequest
375
+ {
376
+ fields: Array<CardField | { field: CardField, container_id?: string }>,
377
+ card_id?: string, // Omit for new card; skyflow_id for saved-card CVV
378
+ unmount_context?: 'all' | 'current' | 'none' | string, // default: 'all'
379
+ }
380
+
381
+ // CardField = 'cardholder_name' | 'card_number' | 'expiration_month' | 'expiration_year' | 'cvv'
140
382
  ```
141
383
 
142
- ```javascript
143
- // Remove a saved card
144
- await liteCheckout.removeCustomerCard(cardId);
384
+ ---
385
+
386
+ ### 6.1 New-card form (all 5 fields)
387
+
388
+ **Default container IDs** (used when no `container_id` is provided):
389
+
390
+ | Field | Default Container ID |
391
+ |-------|---------------------|
392
+ | `cardholder_name` | `#collect_cardholder_name` |
393
+ | `card_number` | `#collect_card_number` |
394
+ | `expiration_month` | `#collect_expiration_month` |
395
+ | `expiration_year` | `#collect_expiration_year` |
396
+ | `cvv` | `#collect_cvv` |
397
+
398
+ **HTML:**
399
+ ```html
400
+ <div id="collect_cardholder_name"></div>
401
+ <div id="collect_card_number"></div>
402
+ <div id="collect_expiration_month"></div>
403
+ <div id="collect_expiration_year"></div>
404
+ <div id="collect_cvv"></div>
145
405
  ```
146
406
 
147
- ```javascript
148
- // Get available payment methods
149
- const paymentMethods = await liteCheckout.getCustomerPaymentMethods();
407
+ **Shorthand (string array):**
408
+ ```js
409
+ await liteCheckout.mountCardFields({
410
+ fields: ["cardholder_name", "card_number", "expiration_month", "expiration_year", "cvv"],
411
+ });
150
412
  ```
151
413
 
152
- ```javascript
153
- // Process a payment
154
- const paymentResponse = await liteCheckout.payment(paymentData);
414
+ **Custom container IDs:**
415
+ ```js
416
+ await liteCheckout.mountCardFields({
417
+ fields: [
418
+ { field: "cardholder_name", container_id: "#my-name" },
419
+ { field: "card_number", container_id: "#my-card-number" },
420
+ { field: "expiration_month", container_id: "#my-month" },
421
+ { field: "expiration_year", container_id: "#my-year" },
422
+ { field: "cvv", container_id: "#my-cvv" },
423
+ ],
424
+ });
155
425
  ```
156
426
 
157
- ```javascript
158
- // Verify a 3DS transaction
159
- const verificationResult = await liteCheckout.verify3dsTransaction();
427
+ > **Container sizing:** Skyflow iframes require explicit dimensions on their containers. Add at minimum `display: block; width: 100%; height: 40px` (or your desired height) to each container `<div>`.
428
+
429
+ ---
430
+
431
+ ### 6.2 Saved-card CVV only
432
+
433
+ For saved-card payments, mount only the CVV field for the selected card. The default container ID becomes `#collect_cvv_<skyflow_id>`.
434
+
435
+ ```html
436
+ <!-- Use the card's skyflow_id as part of the container ID -->
437
+ <div id="collect_cvv_abc123"></div>
160
438
  ```
161
439
 
162
- ## Configuration
163
- ### Inline Options
164
-
165
- | Property | Type | Required | Description | Default | Description |
166
- |:-------------:|:--------------------:|----------|-----------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------------------------------------------------------------------:|
167
- | mode | string | Yes | Environment mode for the SDK | stage | Environment mode. Options: 'stage', 'production', 'sandbox'. Default: 'stage' |
168
- | apiKey | string | Yes | Your Tonder Public API key | | Your API key from the Tonder Dashboard |
169
- | returnUrl | string | Yes | URL for 3DS redirect completion | | URL where the checkout form is mounted (used for 3DS) |
170
- | styles | object | No | | | (InlineCheckout only) Custom styles for the checkout interface |
171
- | customization | CustomizationOptions | No | UI customization options | `{displayMode: 'light',saveCards: {showSaveCardOption: false,showSaved: false,autoSave: false,},paymentButton: {show: false,text: "Pagar",showAmount: true,},cancelButton: {show: false,text: "Cancelar",},paymentMethods: {show: true,},cardForm: {show: true,},showMessages: true,}` | Object to customize the checkout behavior and UI. |
172
- | callbacks | IInlineCallbacks | No | Payment process callback functions |
173
- | signatures | object | No | HMAC signatures for transaction and customer fields |
174
- <details>
175
- <summary>View Interface Definition</summary>
440
+ ```js
441
+ await liteCheckout.mountCardFields({
442
+ fields: ["cvv"],
443
+ card_id: "abc123", // card.fields.skyflow_id
444
+ });
445
+ ```
176
446
 
177
- ```typescript
178
- interface IInlineCheckoutBaseOptions {
179
- mode?: "production" | "sandbox" | "stage" | "development";
180
- apiKey: string;
181
- returnUrl: string;
182
- callBack?: (response: any) => void;
183
- signatures?:{
184
- transaction?: string;
185
- customer?: string;
186
- }
187
- }
188
- interface IInlineCheckoutOptions extends IInlineCheckoutBaseOptions {
189
- styles?: Record<string, string>;
190
- customization?: CustomizationOptions;
191
- callbacks?: IInlineCallbacks;
447
+ **Conditional CVV mount pattern:**
448
+ ```js
449
+ function handleSelectCard(card) {
450
+ liteCheckout.mountCardFields({
451
+ fields: ["cvv"],
452
+ card_id: card.fields.skyflow_id,
453
+ });
192
454
  }
193
455
  ```
194
- </details>
195
456
 
196
- ### Inline Callbacks Structure
457
+ ---
197
458
 
198
- | Callback | Required | Parameters | Description | Return |
199
- |------------|----------|------------|--------------------------------------------|---------------|
200
- | `onCancel` | No | none | Called when user clicked the cancel button | Promise<void> |
201
- <details>
202
- <summary>View Interface Definition</summary>
459
+ ### 6.3 Unmounting fields
203
460
 
204
- ```typescript
205
- interface IInlineCallbacks extends IBaseCallback {
206
- onCancel?: () => Promise<void>;
207
- }
208
- ```
209
- </details>
461
+ `mountCardFields()` automatically unmounts previously mounted fields before mounting new ones — you don't need to call `unmountCardFields()` manually when switching cards.
210
462
 
211
- ### Inline Customization Options
212
-
213
- | Option | Type | Default | Description |
214
- |------------------------------|---------|------------|-------------------------------------------------------------------------------------------|
215
- | **saveCards** |
216
- | saveCards.showSaveCardOption | boolean | true | Shows a checkbox allowing users to choose whether to save their card for future purchases |
217
- | saveCards.showSaved | boolean | true | Displays a list of previously saved cards for the customer |
218
- | saveCards.autoSave | boolean | false | Automatically saves the card without showing the save option to the user |
219
- | **paymentButton** |
220
- | paymentButton.show | boolean | true | Controls the visibility of the payment button |
221
- | paymentButton.text | string | 'Pagar' | Custom text to display on the payment button |
222
- | paymentButton.showAmount | boolean | true | Shows the payment amount on the button (e.g., "Pay $100") |
223
- | **cancelButton** |
224
- | cancelButton.show | boolean | true | Controls the visibility of the cancel button |
225
- | cancelButton.text | string | 'Cancelar' | Custom text to display on the cancel button |
226
- | **paymentMethods** |
227
- | paymentMethods.show | boolean | true | Controls the visibility of alternative payment methods section |
228
- | **cardForm** |
229
- | cardForm.show | boolean | true | Controls the visibility of the card input form |
230
- | **General** |
231
- | showMessages | boolean | true | Controls the visibility of error and success messages |
232
- | displayMode | string | light | Controls the display mode light or dark |
463
+ The `unmount_context` parameter controls what gets cleared before new fields mount:
233
464
 
234
- <details>
235
- <summary>View Interface Definition</summary>
465
+ | `unmount_context` | Behavior |
466
+ |---|---|
467
+ | `'all'` (default) | Unmounts all mounted fields across all contexts |
468
+ | `'current'` | Unmounts only the current context (new-card or the active saved-card CVV) |
469
+ | `'none'` | Does not unmount anything before mounting |
236
470
 
237
- ```typescript
238
- export type CustomizationOptions = {
239
- displayMode?: "light" | "dark";
240
- saveCards?: {
241
- showSaveCardOption?: boolean;
242
- showSaved?: boolean;
243
- autoSave?: boolean;
244
- };
245
- paymentButton?: {
246
- show?: boolean;
247
- text?: string;
248
- showAmount?: boolean;
249
- };
250
- cancelButton?: {
251
- show?: boolean;
252
- text?: string;
253
- };
254
- paymentMethods?: {
255
- show?: boolean;
256
- };
257
- cardForm?: {
258
- show?: boolean;
259
- };
260
- showMessages?: boolean;
261
- };
471
+ The only time you need to call `unmountCardFields()` directly is when **navigating away** from the checkout:
472
+
473
+ ```js
474
+ // Cleanup when leaving the checkout page
475
+ liteCheckout.unmountCardFields();
476
+ // Or remove the whole checkout:
477
+ // checkout.removeCheckout(); (InlineCheckout)
262
478
  ```
263
- </details>
264
479
 
265
- ## Lite Options
266
-
267
- | Property | Type | Required | Description | Default | Description |
268
- |:----------:|:------:|----------|---------------------------------|---------|:-----------------------------------------------------------------------------:|
269
- | mode | string | Yes | Environment mode for the SDK | stage | Environment mode. Options: 'stage', 'production', 'sandbox'. Default: 'stage' |
270
- | apiKey | string | Yes | Your Tonder Public API key | | Your API key from the Tonder Dashboard |
271
- | returnUrl | string | Yes | URL for 3DS redirect completion | | URL where the checkout form is mounted (used for 3DS) |
272
- | signatures | object | No | HMAC signatures | | Provide transaction/customer HMAC if your merchant configuration requires it. |
273
-
274
-
275
- ## Styling InlineCheckout
276
-
277
- You can customize the appearance of InlineCheckout by passing a `styles` object:
278
-
279
- ```javascript
280
- const customStyles = {
281
- inputStyles: {
282
- base: {
283
- border: "1px solid #e0e0e0",
284
- padding: "10px 7px",
285
- borderRadius: "5px",
286
- color: "#1d1d1d",
287
- marginTop: "2px",
288
- backgroundColor: "white",
289
- fontFamily: '"Inter", sans-serif',
290
- fontSize: "16px",
291
- "&::placeholder": {
292
- color: "#ccc",
293
- },
294
- },
295
- cardIcon: {
296
- position: "absolute",
297
- left: "6px",
298
- bottom: "calc(50% - 12px)",
299
- },
300
- complete: {
301
- color: "#4caf50",
302
- },
303
- empty: {},
304
- focus: {},
305
- invalid: {
306
- border: "1px solid #f44336",
307
- },
308
- global: {
309
- "@import":
310
- 'url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;700&display=swap")',
311
- },
312
- },
313
- labelStyles: {
314
- base: {
315
- fontSize: "12px",
316
- fontWeight: "500",
317
- fontFamily: '"Inter", sans-serif',
480
+ > **Important:** Never show the new-card form (all 5 fields) and a saved-card CVV field simultaneously. Only one active CVV input per saved card at a time.
481
+
482
+ ---
483
+
484
+ ## 7. Processing Payments
485
+
486
+ ### 7.1 New card payment
487
+
488
+ **LiteInlineCheckout prerequisite:** Mount all 5 card fields ([Section 6.1](#61-new-card-form-all-5-fields)) and let the user fill them in before calling `payment()`.
489
+
490
+ ```js
491
+ try {
492
+ const response = await checkout.payment({
493
+ customer: {
494
+ firstName: "John",
495
+ lastName: "Doe",
496
+ email: "john@example.com",
497
+ phone: "+1 555 0100",
498
+ country: "MX",
499
+ city: "CDMX",
500
+ street: "123 Main St",
501
+ state: "CMX",
502
+ postCode: "06600",
318
503
  },
319
- },
320
- errorTextStyles: {
321
- base: {
322
- fontSize: "12px",
323
- fontWeight: "500",
324
- color: "#f44336",
325
- fontFamily: '"Inter", sans-serif',
504
+ cart: {
505
+ total: 150,
506
+ items: [
507
+ {
508
+ name: "Product A",
509
+ description: "Product description",
510
+ quantity: 1,
511
+ price_unit: 150,
512
+ discount: 0,
513
+ taxes: 0,
514
+ product_reference: "SKU-001",
515
+ amount_total: 150,
516
+ },
517
+ ],
326
518
  },
327
- },
328
- labels: {
329
- nameLabel: "Card Holder Name",
330
- cardLabel: "Card Number",
331
- cvvLabel: "CVC/CVV",
332
- expiryDateLabel: "Expiration Date",
333
- },
334
- placeholders: {
335
- namePlaceholder: "Name as it appears on the card",
336
- cardPlaceholder: "1234 1234 1234 1234",
337
- cvvPlaceholder: "3-4 digits",
338
- expiryMonthPlaceholder: "MM",
339
- expiryYearPlaceholder: "YY",
340
- },
341
- };
519
+ currency: "MXN",
520
+ order_reference: "ORD-001", // Recommended — shown in Tonder dashboard & exports
521
+ metadata: { order_id: "ORD-001" }, // Recommended — reporting fields
522
+ });
523
+
524
+ console.log("Transaction status:", response.transaction_status);
525
+ } catch (error) {
526
+ console.error(error.message, error.detail);
527
+ }
342
528
  ```
343
529
 
344
- ## Payment Data Structure
530
+ **`IProcessPaymentRequest` fields:**
531
+
532
+ | Field | Type | Required | Description |
533
+ |-------|------|----------|-------------|
534
+ | `customer` | `ICustomer` | **Required** | Customer information |
535
+ | `cart` | `{ total, items }` | **Required** | Order total and line items |
536
+ | `currency` | `string` | Optional | ISO currency code (e.g. `'MXN'`) |
537
+ | `order_reference` | `string` | Recommended | Your internal order ID — shown in Tonder dashboard filters and exports |
538
+ | `metadata` | `Record<string, any>` | Recommended | Reporting fields (see below) |
539
+ | `payment_method` | `string` | Optional | APM identifier (e.g. `'Spei'`) — omit for card payments |
540
+ | `card` | `string` | Optional | **LiteInlineCheckout only** — `skyflow_id` of a saved card; omit to pay with mounted card fields |
541
+ | `apm_config` | `object` | Optional | APM-specific config (e.g. Mercado Pago preferences) |
542
+
543
+ **`ICustomer`:**
544
+ ```js
545
+ {
546
+ firstName: "John", // Required
547
+ lastName: "Doe", // Required
548
+ email: "john@example.com", // Required
549
+ phone: "+1 555 0100",
550
+ country: "MX",
551
+ street: "123 Main St",
552
+ city: "CDMX",
553
+ state: "CMX",
554
+ postCode: "06600",
555
+ address: "123 Main St, CDMX",
556
+ identification: { type: "CPF", number: "19119119100" },
557
+ }
558
+ ```
345
559
 
346
- When calling the `payment` method, use the following data structure:
560
+ **`IItem`:**
561
+ ```js
562
+ {
563
+ name: "Product A",
564
+ description: "Product description",
565
+ quantity: 1,
566
+ price_unit: 150,
567
+ discount: 0,
568
+ taxes: 0,
569
+ product_reference: "SKU-001",
570
+ amount_total: 150,
571
+ }
572
+ ```
347
573
 
348
- ### Field Descriptions
574
+ **Metadata for reporting:**
349
575
 
350
- - **customer**: Object containing the customer's personal information to be registered in the transaction.
576
+ | Field | Report column | Description |
577
+ |-------|---------------|-------------|
578
+ | `order_reference` | Business Transaction ID | Merchant's internal order ID |
579
+ | `metadata.order_id` | Business Transaction ID | Takes precedence over `order_reference` when both are provided |
580
+ | `metadata.customer_email` | Customer Email | Overrides the email shown in reports |
581
+ | `metadata.customer_id` | Customer ID | Your internal customer identifier |
582
+ | `metadata.business_user` | Business User | Internal user or system that initiated the payment |
583
+ | `metadata.operation_date` | Operation Date | Business operation date for reconciliation |
351
584
 
352
- - **cart**: Object containing the total amount and an array of items to be registered in the Tonder order.
585
+ ---
353
586
 
354
- - **total**: The total amount of the transaction.
355
- - **items**: An array of objects, each representing a product or service in the order.
356
- - name: name of the product
357
- - price_unit: valid float string with the price of the product
358
- - quantity: valid integer string with the quantity of this product
587
+ ### 7.2 Pay with a saved card
359
588
 
360
- - **currency**: String representing the currency code for the transaction (e.g., "MXN" for Mexican Peso).
589
+ **LiteInlineCheckout prerequisite:** Mount the CVV field for the selected card ([Section 6.2](#62-saved-card-cvv-only)) before calling `payment()`.
361
590
 
362
- - **metadata**: Object for including any additional information about the transaction. This can be used for internal references or tracking.
591
+ ```js
592
+ // Pass the card's skyflow_id as the `card` field
593
+ const response = await liteCheckout.payment({
594
+ customer: { email: "user@example.com" },
595
+ cart: { total: 100, items: [...] },
596
+ currency: "MXN",
597
+ card: selectedCard.fields.skyflow_id,
598
+ });
599
+ ```
363
600
 
364
- - **card**: (for LiteCheckout) Object containing card information. This is used differently depending on whether it's a new card or a saved card:
601
+ For `InlineCheckout`, saved-card selection is handled automatically by the built-in UI.
365
602
 
366
- - For a new card: Include `card_number`, `cvv`, `expiration_month`, `expiration_year`, and `cardholder_name`.
367
- - For a saved card: Include only the `skyflow_id` of the saved card.
368
- - This is only used when not paying with a payment_method.
603
+ ---
369
604
 
370
- - **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.
605
+ ### 7.3 Alternative Payment Method (APM)
371
606
 
372
- - **apm_config**: (Optional) Configuration object for APM-specific options. Only applicable when using alternative payment methods like Mercado Pago.
373
- <details>
374
- <summary>APM Config Fields</summary>
375
-
376
- | **Field** | **Type** | **Description** |
377
- |-------------------------------------|--------------------------------------------|---------------------------------------------------------------------------|
378
- | `binary_mode` | `boolean` | If `true`, payment must be approved or rejected immediately (no pending). |
379
- | `additional_info` | `string` | Extra info shown during checkout and in payment details. |
380
- | `back_urls` | `object` | URLs to redirect the user after payment. |
381
- | └─ `success` | `string` | Redirect URL after successful payment. |
382
- | └─ `pending` | `string` | Redirect URL after pending payment. |
383
- | └─ `failure` | `string` | Redirect URL after failed/canceled payment. |
384
- | `auto_return` | `"approved"` \| `"all"` | Enables auto redirection after payment completion. |
385
- | `payment_methods` | `object` | Payment method restrictions and preferences. |
386
- | └─ `excluded_payment_methods[]` | `array` | List of payment methods to exclude. |
387
- | └─ `excluded_payment_methods[].id` | `string` | ID of payment method to exclude (e.g., "visa"). |
388
- | └─ `excluded_payment_types[]` | `array` | List of payment types to exclude. |
389
- | └─ `excluded_payment_types[].id` | `string` | ID of payment type to exclude (e.g., "ticket"). |
390
- | └─ `default_payment_method_id` | `string` | Default payment method (e.g., "master"). |
391
- | └─ `installments` | `number` | Max number of installments allowed. |
392
- | └─ `default_installments` | `number` | Default number of installments suggested. |
393
- | `expires` | `boolean` | Whether the preference has expiration. |
394
- | `expiration_date_from` | `string` (ISO 8601) | Start of validity period (e.g. `"2025-01-01T12:00:00-05:00"`). |
395
- | `expiration_date_to` | `string` (ISO 8601) | End of validity period. |
396
- | `differential_pricing` | `object` | Configuration for differential pricing. |
397
- | └─ `id` | `number` | ID of the differential pricing strategy. |
398
- | `marketplace` | `string` | Marketplace identifier (default: "NONE"). |
399
- | `marketplace_fee` | `number` | Fee to collect as marketplace commission. |
400
- | `tracks[]` | `array` | Ad tracking configurations. |
401
- | └─ `type` | `"google_ad"` \| `"facebook_ad"` | Type of tracker. |
402
- | └─ `values.conversion_id` | `string` | Google Ads conversion ID. |
403
- | └─ `values.conversion_label` | `string` | Google Ads label. |
404
- | └─ `values.pixel_id` | `string` | Facebook Pixel ID. |
405
- | `statement_descriptor` | `string` | Text on payer’s card statement (max 16 characters). |
406
- | `shipments` | `object` | Shipping configuration. |
407
- | └─ `mode` | `"custom"` \| `"me2"` \| `"not_specified"` | Type of shipping mode. |
408
- | └─ `local_pickup` | `boolean` | Enable pickup at local branch (for `me2`). |
409
- | └─ `dimensions` | `string` | Package dimensions (e.g. `10x10x10,500`). |
410
- | └─ `default_shipping_method` | `number` | Default shipping method (for `me2`). |
411
- | └─ `free_methods[]` | `array` | Shipping methods offered for free (for `me2`). |
412
- | └─ `free_methods[].id` | `number` | ID of free shipping method. |
413
- | └─ `cost` | `number` | Shipping cost (only for `custom` mode). |
414
- | └─ `free_shipping` | `boolean` | If `true`, shipping is free (`custom` only). |
415
- | └─ `receiver_address` | `object` | Shipping address. |
416
- | └─ `receiver_address.zip_code` | `string` | ZIP or postal code. |
417
- | └─ `receiver_address.street_name` | `string` | Street name. |
418
- | └─ `receiver_address.street_number` | `number` | Street number. |
419
- | └─ `receiver_address.city_name` | `string` | City name. |
420
- | └─ `receiver_address.state_name` | `string` | State name. |
421
- | └─ `receiver_address.country_name` | `string` | Country name. |
422
- | └─ `receiver_address.floor` | `string` | Floor (optional). |
423
- | └─ `receiver_address.apartment` | `string` | Apartment or unit (optional). |
424
- </details>
607
+ No `mountCardFields()` call is needed for APM payments.
425
608
 
426
- Note: The exact fields required may vary depending on whether you're using InlineCheckout or LiteCheckout, and the specific payment method being used.
427
-
428
-
429
- ```javascript
430
- const paymentData = {
431
- customer: {
432
- firstName: "John",
433
- lastName: "Doe",
434
- country: "USA",
435
- address: "123 Main St",
436
- city: "Anytown",
437
- state: "CA",
438
- postCode: "12345",
439
- email: "john.doe@example.com",
440
- phone: "1234567890",
441
- identification:{
442
- type: "CPF",
443
- number: "19119119100"
444
- }
445
- },
446
- cart: {
447
- total: "100.00",
448
- items: [
449
- {
450
- description: "Product description",
451
- quantity: 1,
452
- price_unit: "100.00",
453
- discount: "0.00",
454
- taxes: "0.00",
455
- product_reference: "PROD123",
456
- name: "Product Name",
457
- amount_total: "100.00",
458
- },
459
- ],
460
- },
609
+ ```js
610
+ // 1. Fetch available APMs
611
+ const apms = await liteCheckout.getCustomerPaymentMethods();
612
+ // IPaymentMethod: { id, payment_method, priority, category, icon, label }
613
+
614
+ // 2. User selects an APM
615
+
616
+ // 3. Pay
617
+ const response = await liteCheckout.payment({
618
+ customer: { email: "user@example.com" },
619
+ cart: { total: 100, items: [...] },
461
620
  currency: "MXN",
462
- metadata: {
463
- order_id: "ORDER123",
464
- },
465
- // For Lite checkout
466
- card: {
467
- // For a new card:
468
- card_number: "4111111111111111",
469
- cvv: "123",
470
- expiration_month: "12",
471
- expiration_year: "25",
472
- cardholder_name: "John Doe",
473
- // Or for a saved card:
474
- skyflow_id: "saved-card-id",
621
+ payment_method: selectedApm.payment_method, // e.g. 'Spei'
622
+ });
623
+ ```
624
+
625
+ **Mercado Pago with `apm_config`:**
626
+ ```js
627
+ const response = await liteCheckout.payment({
628
+ ...paymentData,
629
+ payment_method: "MercadoPago",
630
+ apm_config: {
631
+ back_urls: {
632
+ success: "https://myapp.com/success",
633
+ pending: "https://myapp.com/pending",
634
+ failure: "https://myapp.com/failure",
635
+ },
636
+ auto_return: "approved",
475
637
  },
476
- // For Lite checkout
477
- payment_method: "Spei",
478
- };
638
+ });
479
639
  ```
480
640
 
481
- ## Field Validation Functions
641
+ <details>
642
+ <summary>Full Mercado Pago <code>apm_config</code> fields</summary>
643
+
644
+ | Field | Type | Description |
645
+ |-------|------|-------------|
646
+ | `binary_mode` | `boolean` | If `true`, payment must be approved or rejected immediately (no pending) |
647
+ | `additional_info` | `string` | Extra info shown during checkout |
648
+ | `back_urls.success` | `string` | Redirect URL after successful payment |
649
+ | `back_urls.pending` | `string` | Redirect URL after pending payment |
650
+ | `back_urls.failure` | `string` | Redirect URL after failed/canceled payment |
651
+ | `auto_return` | `'approved' \| 'all'` | Enable auto-redirect after payment completion |
652
+ | `payment_methods.excluded_payment_methods[].id` | `string` | Payment method to exclude (e.g. `'visa'`) |
653
+ | `payment_methods.excluded_payment_types[].id` | `string` | Payment type to exclude (e.g. `'ticket'`) |
654
+ | `payment_methods.default_payment_method_id` | `string` | Default payment method |
655
+ | `payment_methods.installments` | `number` | Max installments allowed |
656
+ | `payment_methods.default_installments` | `number` | Default installments suggested |
657
+ | `expires` | `boolean` | Whether the preference has an expiration |
658
+ | `expiration_date_from` | `string` (ISO 8601) | Start of validity period |
659
+ | `expiration_date_to` | `string` (ISO 8601) | End of validity period |
660
+ | `statement_descriptor` | `string` | Text on payer's card statement (max 16 chars) |
661
+ | `marketplace` | `string` | Marketplace identifier (default: `'NONE'`) |
662
+ | `marketplace_fee` | `number` | Fee to collect as marketplace commission |
663
+ | `tracks[].type` | `'google_ad' \| 'facebook_ad'` | Ad tracker type |
664
+ | `tracks[].values.conversion_id` | `string` | Google Ads conversion ID |
665
+ | `tracks[].values.pixel_id` | `string` | Facebook Pixel ID |
666
+ | `shipments.mode` | `'custom' \| 'me2' \| 'not_specified'` | Shipping mode |
667
+ | `shipments.cost` | `number` | Shipping cost (custom mode only) |
668
+ | `shipments.free_shipping` | `boolean` | Free shipping flag (custom mode only) |
669
+ | `differential_pricing.id` | `number` | Differential pricing strategy ID |
482
670
 
483
- For LiteCheckout implementations, the SDK provides validation functions to ensure the integrity of card data before submitting:
671
+ </details>
484
672
 
485
- - `validateCardNumber(cardNumber)`: Validates the card number using the Luhn algorithm.
486
- - `validateCardholderName(name)`: Checks if the cardholder name is valid.
487
- - `validateCVV(cvv)`: Ensures the CVV is in the correct format.
488
- - `validateExpirationDate(expirationDate)`: Validates the expiration date in MM/YY format.
489
- - `validateExpirationMonth(month)`: Checks if the expiration month is valid.
490
- - `validateExpirationYear(year)`: Validates the expiration year.
673
+ ---
674
+
675
+ ### 7.4 Payment response reference
676
+
677
+ ```js
678
+ // IStartCheckoutResponse
679
+ {
680
+ status: string,
681
+ message: string,
682
+ transaction_status: string, // 'Success' | 'Pending' | 'Declined' | 'Failed'
683
+ transaction_id: number,
684
+ payment_id: number,
685
+ checkout_id: string,
686
+ is_route_finished: boolean,
687
+ provider: string,
688
+ psp_response: Record<string, any>, // Raw response from the payment processor
689
+ }
690
+ ```
491
691
 
492
- Example usage:
692
+ > **Note:** 3DS authentication is handled automatically by the SDK. You do not need to handle redirects manually.
493
693
 
494
- ```javascript
495
- import {
496
- validateCardNumber,
497
- validateCardholderName,
498
- validateCVV,
499
- validateExpirationDate,
500
- } from "tonder-web-sdk";
694
+ ---
695
+
696
+ ## 8. 3DS Handling
697
+
698
+ When a payment requires 3DS authentication, the SDK performs a **full-page redirect** to the bank's authentication page. After authentication, the bank sends the user back to `returnUrl`. On that page load, `verify3dsTransaction()` completes the verification.
501
699
 
502
- const cardNumber = "4111111111111111";
503
- const cardholderName = "John Doe";
504
- const cvv = "123";
505
- const expirationDate = "12/25";
506
-
507
- if (
508
- validateCardNumber(cardNumber) &&
509
- validateCardholderName(cardholderName) &&
510
- validateCVV(cvv) &&
511
- validateExpirationDate(expirationDate)
512
- ) {
513
- // Proceed with payment
514
- } else {
515
- // Show error message
700
+ **How it works:**
701
+
702
+ 1. `payment()` detects the 3DS challenge and redirects the browser to the bank's page
703
+ 2. User completes authentication
704
+ 3. Bank redirects back to `returnUrl`
705
+ 4. Your page calls `verify3dsTransaction()` — it detects the 3DS return, verifies the transaction, and returns the result
706
+ 5. If routing is configured and the transaction was declined, it automatically retries with the next payment route
707
+
708
+ **Implementation pattern (call on every page load):**
709
+
710
+ ```js
711
+ await checkout.injectCheckout();
712
+
713
+ const tdsResult = await checkout.verify3dsTransaction();
714
+ if (tdsResult) {
715
+ // Returned from 3DS challenge
716
+ const { transaction_status } = tdsResult;
717
+ if (transaction_status === "Success") {
718
+ showOrderConfirmation();
719
+ } else {
720
+ showError(`Payment ${transaction_status}`);
721
+ }
722
+ return; // Do not proceed to mounting card fields
516
723
  }
517
- ```
518
724
 
519
- ## HMAC Signature Validation
725
+ // Normal page load — mount card fields (LiteInlineCheckout) or let InlineCheckout render
726
+ ```
520
727
 
521
- Tonder supports **HMAC** validation to ensure the data sent from your application to Tonder is not tampered with.
728
+ ---
522
729
 
523
- ### Overview
730
+ ## 9. Managing Saved Cards (LiteInlineCheckout)
524
731
 
525
- - **HMAC**: A cryptographic method used to validate the integrity of transmitted data.
526
- - You receive an **API Secret Key** from Tonder.
527
- - You generate the HMAC signature locally (SHA-256, Base64) based on certain fields.
528
- - Tonder compares your signature with its own calculation.
529
- - If invalid, Tonder returns a **403**.
732
+ ### 9.1 List saved cards
530
733
 
531
- ### Generating the HMAC Signature (JavaScript Example)
532
- > **Important**: This HMAC generation should be done on your **backend** server, not in client-side code, to keep your secret key secure.
533
- ```javascript
534
- const crypto = require('crypto');
734
+ ```js
735
+ const { cards } = await liteCheckout.getCustomerCards();
736
+ // Returns: ICustomerCardsResponse = { user_id: number, cards: ICard[] }
535
737
 
536
- function generateHMAC(secretKey, requestBody) {
537
- // Convert the payload to a JSON string.
538
- // Ensure the fields are in alphabetical order if required by your config.
539
- const dataString = JSON.stringify(requestBody);
738
+ cards.forEach((card) => {
739
+ const lastFour = card.fields.card_number.slice(-4);
740
+ const expiry = `${card.fields.expiration_month}/${card.fields.expiration_year}`;
741
+ console.log(`${card.fields.card_scheme} •••• ${lastFour} — expires ${expiry}`);
742
+ });
743
+ ```
540
744
 
541
- // Create HMAC using SHA-256, then Base64-encode it.
542
- return crypto
543
- .createHmac('sha256', secretKey)
544
- .update(dataString)
545
- .digest('base64');
745
+ **`ICard` shape:**
746
+
747
+ ```js
748
+ {
749
+ fields: {
750
+ skyflow_id: string, // Use this as the card identifier in payment()
751
+ card_number: string, // Masked — e.g. 'XXXX-XXXX-XXXX-4242'
752
+ cardholder_name: string,
753
+ expiration_month: string, // e.g. '12'
754
+ expiration_year: string, // e.g. '25'
755
+ card_scheme: string, // e.g. 'VISA', 'MASTERCARD'
756
+ },
757
+ icon?: string, // URL to card brand image
546
758
  }
547
759
 
548
- // Example usage.
549
- const secretKey = "<MERCHANT_SECRET_KEY>";
550
- const requestBody = {
551
- customer: {
552
- email: "user@example.com",
553
- firstName: "John",
554
- lastName: "Doe",
555
- },
556
- currency: "mxn",
557
- cart: {
558
- total: 100,
559
- items: [
560
- {
561
- amount_total: 100,
562
- description: "Sample Item",
563
- },
564
- ],
565
- },
566
- };
760
+ ---
567
761
 
568
- const signature = generateHMAC(secretKey, requestBody);
569
- console.log("Generated HMAC:", signature);
570
- ```
762
+ ### 9.2 Save a new card (enrollment)
571
763
 
572
- ### Providing the Signature in the SDK
764
+ **Prerequisites:** Mount all 5 card fields (`mountCardFields()` with no `card_id`) and let the user fill them in.
573
765
 
574
- If using the Tonder SDK, include your generated signature in the `signatures` field:
766
+ ```js
767
+ // 1. Mount all 5 fields (Section 6.1)
768
+ await liteCheckout.mountCardFields({
769
+ fields: ["cardholder_name", "card_number", "expiration_month", "expiration_year", "cvv"],
770
+ });
575
771
 
576
- ```javascript
577
- const inlineCheckout = new InlineCheckout({
578
- mode: "stage",
579
- apiKey: "<YOUR_API_KEY>",
580
- returnUrl: "https://your-website.com/checkout",
581
- signatures: {
582
- transaction: "<Base64 HMAC>", // For payment
583
- customer: "<Base64 HMAC>" // For card ops
584
- }
772
+ // 2. User fills in the card form...
773
+
774
+ // 3. Save the card
775
+ const saved = await liteCheckout.saveCustomerCard();
776
+ console.log("Saved card skyflow_id:", saved.skyflow_id);
777
+ // Returns: { skyflow_id: string, user_id: number }
778
+
779
+ // 4. Optionally reveal the saved card data (Section 10)
780
+ await liteCheckout.revealCardFields({
781
+ fields: ["card_number", "cardholder_name", "expiration_month", "expiration_year"],
585
782
  });
586
783
  ```
587
784
 
588
- The SDK will handle attaching the signature to outgoing requests. If the signature does not match Tonder’s validation, the request will be rejected.
785
+ ---
589
786
 
590
- ### Providing the Signature in Direct Calls (Without the SDK)
787
+ ### 9.3 Remove a card
591
788
 
592
- If you call Tonder’s REST endpoints directly, add headers:
789
+ ```js
790
+ await liteCheckout.removeCustomerCard(card.fields.skyflow_id);
593
791
 
594
- - `X-Signature-Transaction: <Base64 HMAC>`
595
- - `X-Client-Source: <merchant>-sdk` (the agreed-upon source)
792
+ // Refresh the card list
793
+ const { cards } = await liteCheckout.getCustomerCards();
794
+ ```
596
795
 
597
- ### Important Notes
796
+ ---
598
797
 
599
- - Always ensure your JSON structure matches what Tonder expects.
600
- - If you are required to sign specific fields, confirm you’re only signing those fields **in alphabetical order**.
601
- - A mismatch results in a **403**.
798
+ ## 10. Revealing Card Data `revealCardFields` (LiteInlineCheckout)
602
799
 
800
+ After a successful `saveCustomerCard()` or `payment()` with a new card, use `revealCardFields()` to display card data in your UI through secure iframes — raw values are never exposed to your code.
603
801
 
604
- ## API Reference
802
+ > **When to call:** Only after a successful `saveCustomerCard()` or new-card `payment()`.
605
803
 
606
- ### InlineCheckout Methods
804
+ **Default container IDs and redaction:**
607
805
 
608
- - `configureCheckout(data)`: Set initial checkout data
609
- - `injectCheckout()`: Inject the checkout interface into the DOM
610
- - `payment(data)`: Process a payment
611
- - `verify3dsTransaction()`: Verify a 3DS transaction
806
+ | Field | Default Container ID | Redaction |
807
+ |-------|---------------------|-----------|
808
+ | `card_number` | `#reveal_card_number` | `MASKED` — e.g. `4111 11•• •••• 1234` |
809
+ | `cardholder_name` | `#reveal_cardholder_name` | `PLAIN_TEXT` |
810
+ | `expiration_month` | `#reveal_expiration_month` | `PLAIN_TEXT` |
811
+ | `expiration_year` | `#reveal_expiration_year` | `PLAIN_TEXT` |
612
812
 
613
- ### LiteCheckout Methods
813
+ > **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.
614
814
 
615
- - `configureCheckout(data)`: Set initial checkout data
616
- - `injectCheckout()`: Initialize the checkout
617
- - `getCustomerCards()`: Retrieve saved cards
618
- - `saveCustomerCard(cardData)`: Save a new card
619
- - `removeCustomerCard(cardId)`: Remove a saved card
620
- - `getCustomerPaymentMethods()`: Get available payment methods
621
- - `payment(data)`: Process a payment
622
- - `verify3dsTransaction()`: Verify a 3DS transaction
815
+ > **Container sizing:** Reveal iframes also need explicit dimensions on their containers (e.g. `display: block; width: 100%; height: 26px`).
623
816
 
624
- ## Examples
817
+ **Example 1 — Shorthand:**
818
+ ```html
819
+ <div id="reveal_cardholder_name"></div>
820
+ <div id="reveal_card_number"></div>
821
+ <div id="reveal_expiration_month"></div>
822
+ <div id="reveal_expiration_year"></div>
823
+ ```
824
+ ```js
825
+ await liteCheckout.saveCustomerCard();
625
826
 
626
- Here are examples of how to implement Tonder SDK in various JavaScript frameworks:
827
+ await liteCheckout.revealCardFields({
828
+ fields: ["cardholder_name", "card_number", "expiration_month", "expiration_year"],
829
+ });
830
+ ```
627
831
 
628
- ### Vanilla JavaScript
832
+ **Example 2 — With `altText`, `label`, and per-field options:**
833
+ ```js
834
+ await liteCheckout.revealCardFields({
835
+ fields: [
836
+ { field: "card_number", altText: "•••• •••• •••• ••••", label: "Card Number" },
837
+ { field: "cardholder_name", altText: "Loading…", label: "Cardholder" },
838
+ { field: "expiration_month", label: "Month" },
839
+ { field: "expiration_year", label: "Year" },
840
+ ],
841
+ });
842
+ ```
629
843
 
630
- #### HTML Setup
844
+ **Example 3 — Custom styles (e.g. overlay on a card graphic):**
845
+ ```js
846
+ await liteCheckout.revealCardFields({
847
+ fields: [
848
+ { field: "card_number", container_id: "#my-card-number-display" },
849
+ { field: "cardholder_name", container_id: "#my-name-display" },
850
+ { field: "expiration_month", container_id: "#my-month-display" },
851
+ { field: "expiration_year", container_id: "#my-year-display" },
852
+ ],
853
+ styles: {
854
+ inputStyles: {
855
+ base: {
856
+ color: "#ffffff",
857
+ fontFamily: '"Courier New", monospace',
858
+ fontSize: "16px",
859
+ background: "transparent",
860
+ border: "none",
861
+ letterSpacing: "2px",
862
+ },
863
+ },
864
+ },
865
+ });
866
+ ```
631
867
 
632
- ```html
633
- <!doctype html>
634
- <html lang="en">
635
- <head>
636
- <meta charset="UTF-8" />
637
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
638
- <title>Tonder Checkout Example</title>
639
- <script src="https://js.skyflow.com/v1/index.js"></script>
640
- <script src="https://openpay.s3.amazonaws.com/openpay.v1.min.js"></script>
641
- <script src="https://openpay.s3.amazonaws.com/openpay-data.v1.min.js"></script>
642
- <!-- Only if not use npm package -->
643
- <script src="https://zplit-prod.s3.amazonaws.com/v1/bundle.min.js"></script>
644
- </head>
645
- <body>
646
- <div id="tonder-checkout"></div>
647
- <button id="pay-button">Pay Now</button>
868
+ **`IRevealCardFieldsRequest` types:**
869
+
870
+ ```js
871
+ // fields: Array of shorthand strings or per-field objects
872
+ {
873
+ field: 'card_number' | 'cardholder_name' | 'expiration_month' | 'expiration_year',
874
+ container_id?: string, // default: #reveal_<field>
875
+ altText?: string, // Placeholder shown before reveal resolves
876
+ label?: string, // Label rendered above the field
877
+ styles?: { // Per-field style override
878
+ inputStyles?: { base?, copyIcon?, global? },
879
+ labelStyles?: { base?, global? },
880
+ errorTextStyles?: { base?, global? },
881
+ },
882
+ }
648
883
 
649
- <script src="your-script.js"></script>
650
- </body>
651
- </html>
884
+ // Top-level styles apply to all fields unless overridden per-field
885
+ styles?: {
886
+ inputStyles?: { base?, copyIcon?, global? },
887
+ labelStyles?: { base?, global? },
888
+ errorTextStyles?: { base?, global? },
889
+ }
652
890
  ```
653
891
 
654
- #### InlineCheckout Example (your-script.js)
892
+ > **Style variants:** Reveal elements support only `base`, `copyIcon`, and `global` variants — not `focus`, `complete`, `invalid`, or `empty` (those apply to collect fields only).
655
893
 
656
- ```javascript
657
- import { InlineCheckout } from "tonder-web-sdk";
894
+ ---
658
895
 
659
- const apiKey = "your-api-key";
660
- const returnUrl = "http://your-website.com/checkout";
896
+ ## 11. Error Handling
661
897
 
662
- const inlineCheckout = new InlineCheckout({
663
- mode: "development",
664
- apiKey,
665
- returnUrl,
666
- styles: customStyles, // Define your custom styles here
667
- });
898
+ ### 11.1 Error structure
668
899
 
669
- inlineCheckout.configureCheckout({ customer: { email: "example@email.com" } });
670
- inlineCheckout.injectCheckout();
900
+ When a public SDK method fails, it throws a plain error object with the following shape:
671
901
 
672
- inlineCheckout.verify3dsTransaction().then((response) => {
673
- console.log("Verify 3ds response", response);
674
- });
902
+ ```js
903
+ {
904
+ status: "error",
905
+ code: 400, // HTTP status code as number
906
+ message: "...", // Human-readable description
907
+ detail: "..." | { ... }, // Additional context from the server
908
+ }
909
+ ```
675
910
 
676
- document
677
- .getElementById("pay-button")
678
- .addEventListener("click", async function () {
679
- try {
680
- const response = await inlineCheckout.payment(checkoutData);
681
- console.log("Payment response:", response);
682
- alert("Payment successful");
683
- } catch (error) {
684
- console.error("Payment error:", error.details);
685
- alert("Payment failed");
686
- }
687
- });
911
+ **Catch pattern:**
912
+ ```js
913
+ try {
914
+ const response = await liteCheckout.payment(data);
915
+ console.log("Transaction status:", response.transaction_status);
916
+ } catch (error) {
917
+ console.error(`[${error.code}] ${error.message}`);
918
+ // Use error.detail for server response details
919
+ }
688
920
  ```
689
921
 
922
+ For `InlineCheckout.payment()`, the error is a standard `Error` instance with an additional `.details` property:
923
+ ```js
924
+ try {
925
+ await checkout.payment(data);
926
+ } catch (error) {
927
+ console.error(error.message);
928
+ console.error(error.details); // IStartCheckoutErrorResponse from the server
929
+ }
930
+ ```
690
931
 
691
- #### InlineCheckout with default Tonder Payment button Example (your-script.js)
692
- > 💡 **Note:** It is important to send all payment data (customer, cart, metadata, etc) when configuring the checkout; this is necessary when using Tonder's default payment button.
932
+ ---
693
933
 
694
- ```javascript
695
- import { InlineCheckout } from "tonder-web-sdk";
934
+ ### 11.2 Error code reference
696
935
 
697
- const apiKey = "your-api-key";
698
- const returnUrl = "http://your-website.com/checkout";
936
+ | `code` | HTTP Status | Thrown by | When |
937
+ |--------|-------------|-----------|------|
938
+ | `400` | 400 | `payment()`, `saveCustomerCard()` | Validation error or bad request |
939
+ | `401` | 401 | Any method | Invalid or missing API key |
940
+ | `403` | 403 | Any method | HMAC signature mismatch |
941
+ | `404` | 404 | `getCustomerCards()`, `removeCustomerCard()` | Customer or card not found |
942
+ | `500` | 500 | Any method | Unexpected server error |
699
943
 
700
- const inlineCheckout = new InlineCheckout({
701
- mode: "development",
702
- apiKey,
703
- returnUrl,
704
- styles: customStyles,
705
- customization: {
706
- paymentButton: {
707
- show: true
708
- }
709
- }, // activate default Tonder Payment button
710
- callBack: (response) => {
711
- console.log('Payment response', response)
712
- }
713
- });
944
+ ---
714
945
 
715
- // It is important to send all payment data (customer, cart, metadata, etc) when configuring the checkout; this is necessary when using Tonder's default payment button.
716
- inlineCheckout.configureCheckout(
717
- {
718
- customer: { email: "example@email.com" },
719
- currency: "mxn",
720
- cart: {
721
- total: 399,
722
- items: [
723
- {
724
- description: "Black T-Shirt",
725
- quantity: 1,
726
- price_unit: 1,
727
- discount: 0,
728
- taxes: 0,
729
- product_reference: 1,
730
- name: "T-Shirt",
731
- amount_total: 399,
732
- },
733
- ],
946
+ ## 12. Customization & Styling
947
+
948
+ ### 12.1 InlineCheckout styles
949
+
950
+ Pass styles inside `customization.styles`. Uses `ICardStyles` with optional per-field overrides.
951
+
952
+ ```js
953
+ new InlineCheckout({
954
+ apiKey: "YOUR_KEY",
955
+ returnUrl: "https://myapp.com/checkout",
956
+ customization: {
957
+ styles: {
958
+ // Show the card brand icon inside the card number field (default: true)
959
+ enableCardIcon: true,
960
+
961
+ // Global styles applied to all fields
962
+ cardForm: {
963
+ inputStyles: {
964
+ base: {
965
+ fontFamily: "Inter, sans-serif",
966
+ fontSize: "14px",
967
+ color: "#1d1d1f",
968
+ border: "1px solid #d1d1d6",
969
+ borderRadius: "8px",
970
+ padding: "10px 12px",
971
+ backgroundColor: "#ffffff",
972
+ },
973
+ focus: {
974
+ borderColor: "#6200ee",
975
+ boxShadow: "0 0 0 3px rgba(98, 0, 238, 0.15)",
976
+ outline: "none",
977
+ },
978
+ complete: { borderColor: "#34c759" },
979
+ invalid: { borderColor: "#ff3b30", color: "#ff3b30" },
980
+ cardIcon: { position: "absolute", left: "6px", bottom: "calc(50% - 12px)" },
981
+ global: {
982
+ "@import": 'url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500&display=swap")',
983
+ },
734
984
  },
735
- metadata: {}, // Optional
736
- order_reference: "" // Optional
737
- }
738
- );
739
- inlineCheckout.injectCheckout();
985
+ labelStyles: {
986
+ base: { fontSize: "12px", fontWeight: "500", color: "#6e6e73", marginBottom: "4px" },
987
+ },
988
+ errorStyles: {
989
+ base: { color: "#ff3b30", fontSize: "11px", marginTop: "4px" },
990
+ },
991
+ },
740
992
 
741
- inlineCheckout.verify3dsTransaction().then((response) => {
742
- console.log("Verify 3ds response", response);
993
+ // Per-field overrides — take priority over cardForm
994
+ cardNumber: {
995
+ inputStyles: {
996
+ base: { letterSpacing: "2px" },
997
+ },
998
+ },
999
+ cvv: {
1000
+ inputStyles: {
1001
+ base: { backgroundColor: "#faf5ff" },
1002
+ invalid: { borderColor: "#e74c3c" },
1003
+ },
1004
+ },
1005
+ },
1006
+ },
743
1007
  });
744
1008
  ```
745
1009
 
1010
+ **`ICardStyles` shape:**
1011
+
1012
+ ```js
1013
+ {
1014
+ enableCardIcon?: boolean, // default: true
1015
+ cardForm?: ICardFormStyles, // Global styles for all fields
1016
+ cardholderName?: ICardFormStyles,
1017
+ cardNumber?: ICardFormStyles,
1018
+ cvv?: ICardFormStyles,
1019
+ expirationMonth?: ICardFormStyles,
1020
+ expirationYear?: ICardFormStyles,
1021
+ }
746
1022
 
747
- #### LiteCheckout Example (your-script.js)
1023
+ // ICardFormStyles:
1024
+ {
1025
+ inputStyles?: {
1026
+ base?, focus?, complete?, invalid?, empty?,
1027
+ cardIcon?, // Card brand icon position (card_number only)
1028
+ global?, // CSS @import or global rules
1029
+ },
1030
+ labelStyles?: { base? },
1031
+ errorStyles?: { base? },
1032
+ }
1033
+ ```
748
1034
 
749
- ```javascript
750
- import { LiteInlineCheckout } from "tonder-web-sdk";
1035
+ > **Backward compatibility:** Passing `styles` at the **root** of `InlineCheckout` options is still supported but **deprecated** — move it to `customization.styles`. If both are provided, `customization.styles` takes precedence. Passing a flat `{ inputStyles, labelStyles, errorTextStyles }` object is also still supported but deprecated — wrap it in `{ cardForm: { ... } }`. See [Section 15](#15-deprecated-api).
751
1036
 
752
- const apiKey = "your-api-key";
753
- const returnUrl = "http://your-website.com/checkout";
1037
+ ---
754
1038
 
755
- const liteCheckout = new LiteInlineCheckout({
756
- mode: "development",
757
- apiKey,
758
- returnUrl,
759
- });
1039
+ ### 12.2 LiteInlineCheckout styles
760
1040
 
761
- liteCheckout.configureCheckout({ customer: { email: "example@email.com" } });
762
- liteCheckout.injectCheckout();
1041
+ Pass styles inside `customization.styles`. Uses the same `ICardStyles` format:
763
1042
 
764
- liteCheckout.verify3dsTransaction().then((response) => {
765
- console.log("Verify 3ds response", response);
1043
+ ```js
1044
+ new LiteInlineCheckout({
1045
+ apiKey: "YOUR_KEY",
1046
+ returnUrl: "https://myapp.com/checkout",
1047
+ customization: {
1048
+ styles: {
1049
+ enableCardIcon: true,
1050
+ cardForm: {
1051
+ inputStyles: {
1052
+ base: {
1053
+ border: "1px solid #d1d1d6",
1054
+ borderRadius: "8px",
1055
+ padding: "10px 12px",
1056
+ },
1057
+ focus: {
1058
+ borderColor: "#6200ee",
1059
+ boxShadow: "0 0 0 3px rgba(98, 0, 238, 0.15)",
1060
+ },
1061
+ complete: { borderColor: "#34c759" },
1062
+ invalid: { borderColor: "#ff3b30" },
1063
+ },
1064
+ labelStyles: {
1065
+ base: { fontSize: "12px", fontWeight: "500", color: "#6e6e73" },
1066
+ },
1067
+ errorStyles: {
1068
+ base: { color: "#ff3b30", fontSize: "11px" },
1069
+ },
1070
+ },
1071
+ // Per-field override
1072
+ cvv: {
1073
+ inputStyles: {
1074
+ base: { borderColor: "#8e44ad", backgroundColor: "#faf5ff" },
1075
+ },
1076
+ labelStyles: {
1077
+ base: { color: "#8e44ad", fontWeight: "600" },
1078
+ },
1079
+ },
1080
+ },
1081
+ },
766
1082
  });
1083
+ ```
767
1084
 
768
- document
769
- .getElementById("lite-payment-form")
770
- .addEventListener("submit", async function (event) {
771
- event.preventDefault();
772
- const cardData = {
773
- card_number: document.getElementById("card-number").value,
774
- cardholder_name: document.getElementById("card-name").value,
775
- expiration_month: document.getElementById("month").value,
776
- expiration_year: document.getElementById("year").value,
777
- cvv: document.getElementById("cvv").value,
778
- };
1085
+ ---
779
1086
 
780
- try {
781
- const paymentData = { ...checkoutData, card: cardData };
782
- const response = await liteCheckout.payment(paymentData);
783
- console.log("Payment response:", response);
784
- alert("Payment successful");
785
- } catch (error) {
786
- console.error("Payment error:", error);
787
- alert("Payment failed");
788
- }
789
- });
1087
+ ### 12.3 Labels & placeholders (LiteInlineCheckout)
1088
+
1089
+ ```js
1090
+ new LiteInlineCheckout({
1091
+ customization: {
1092
+ labels: {
1093
+ cardholder_name: "Name on Card",
1094
+ card_number: "Card Number",
1095
+ cvv: "CVC / CVV",
1096
+ expiration_month: "Month",
1097
+ expiration_year: "Year",
1098
+ },
1099
+ placeholders: {
1100
+ cardholder_name: "Name as it appears on the card",
1101
+ card_number: "1234 5678 9012 3456",
1102
+ cvv: "3–4 digits",
1103
+ expiration_month: "MM",
1104
+ expiration_year: "YY",
1105
+ },
1106
+ },
1107
+ });
790
1108
  ```
791
1109
 
792
- ### React or Nextjs
1110
+ ---
793
1111
 
794
- For React or Nextjs applications, we recommend using a context provider to manage the Tonder instance across your application.
1112
+ ### 12.4 InlineCheckout UI customization
795
1113
 
796
- First, create a TonderProvider:
1114
+ Control visibility and behavior of the pre-built checkout UI:
797
1115
 
798
- ```jsx
799
- // TonderProvider.jsx
800
- "use client"; // only for Nextjs
801
- import React, { createContext, useContext, useState, useEffect } from "react";
802
- import { InlineCheckout } from "tonder-web-sdk";
1116
+ ```js
1117
+ new InlineCheckout({
1118
+ customization: {
1119
+ displayMode: "light", // 'light' | 'dark'
1120
+ saveCards: {
1121
+ showSaveCardOption: true, // Show "Save card" checkbox
1122
+ showSaved: true, // Show list of saved cards
1123
+ autoSave: false, // Auto-save without showing the checkbox
1124
+ },
1125
+ paymentButton: {
1126
+ show: true,
1127
+ text: "Pay",
1128
+ showAmount: true, // Show amount on the button, e.g. "Pay $100"
1129
+ },
1130
+ cancelButton: {
1131
+ show: false,
1132
+ text: "Cancel",
1133
+ },
1134
+ paymentMethods: {
1135
+ show: true, // Show APM section
1136
+ },
1137
+ cardForm: {
1138
+ show: true, // Show the card form
1139
+ },
1140
+ showMessages: true, // Show error/success messages
1141
+ },
1142
+ });
1143
+ ```
803
1144
 
804
- const TonderContext = createContext();
1145
+ | Option | Type | Default | Description |
1146
+ |--------|------|---------|-------------|
1147
+ | `displayMode` | `'light' \| 'dark'` | `'light'` | Color scheme |
1148
+ | `saveCards.showSaveCardOption` | `boolean` | `true` | Show "Save this card" checkbox |
1149
+ | `saveCards.showSaved` | `boolean` | `true` | Show saved card list |
1150
+ | `saveCards.autoSave` | `boolean` | `false` | Silently save the card without showing the option |
1151
+ | `paymentButton.show` | `boolean` | `true` | Show the payment button |
1152
+ | `paymentButton.text` | `string` | `'Pagar'` | Payment button label |
1153
+ | `paymentButton.showAmount` | `boolean` | `true` | Append amount to button label |
1154
+ | `cancelButton.show` | `boolean` | `false` | Show the cancel button |
1155
+ | `cancelButton.text` | `string` | `'Cancelar'` | Cancel button label |
1156
+ | `paymentMethods.show` | `boolean` | `true` | Show APM section |
1157
+ | `cardForm.show` | `boolean` | `true` | Show the card form |
1158
+ | `showMessages` | `boolean` | `true` | Show validation and status messages |
805
1159
 
806
- export const useTonder = () => useContext(TonderContext);
1160
+ ---
807
1161
 
808
- export const TonderProvider = ({ children }) => {
809
- const [tonderInstance, setTonderInstance] = useState(null);
1162
+ ## 13. Framework Examples
810
1163
 
811
- useEffect(() => {
812
- const init = async () => {
813
- try {
814
- const inlineCheckout = new InlineCheckout({
815
- mode: "development",
816
- apiKey: "your-api-key",
817
- returnUrl: "http://your-website.com/checkout",
1164
+ ### 13.1 Vanilla JS — InlineCheckout
1165
+
1166
+ ```html
1167
+ <!doctype html>
1168
+ <html lang="en">
1169
+ <head>
1170
+ <script src="https://js.skyflow.com/v1/index.js"></script>
1171
+ <script src="https://zplit-prod.s3.amazonaws.com/v2/bundle.min.js"></script>
1172
+ </head>
1173
+ <body>
1174
+ <div id="tonder-checkout"></div>
1175
+ <button id="pay-btn">Pay</button>
1176
+
1177
+ <script>
1178
+ const { InlineCheckout } = window.TonderSdk;
1179
+
1180
+ const checkout = new InlineCheckout({
1181
+ apiKey: "YOUR_PUBLIC_API_KEY",
1182
+ mode: "stage",
1183
+ returnUrl: window.location.href,
1184
+ customization: {
1185
+ paymentButton: { show: false },
1186
+ },
1187
+ });
1188
+
1189
+ async function init() {
1190
+ // In production, fetch this from your backend
1191
+ const { access } = await fetch("https://stage.tonder.io/api/secure-token/", {
1192
+ method: "POST",
1193
+ headers: { Authorization: "Token YOUR_SECRET_KEY", "Content-Type": "application/json" },
1194
+ }).then((r) => r.json());
1195
+
1196
+ checkout.configureCheckout({
1197
+ customer: { email: "user@example.com" },
1198
+ secureToken: access,
818
1199
  });
819
- setTonderInstance(inlineCheckout);
820
- } catch (error) {
821
- console.error("Error initializing Tonder:", error);
822
- }
823
- };
824
1200
 
825
- init();
826
- }, []);
1201
+ await checkout.injectCheckout();
827
1202
 
828
- return (
829
- <TonderContext.Provider value={tonderInstance}>
830
- {children}
831
- </TonderContext.Provider>
832
- );
833
- };
1203
+ const tdsResult = await checkout.verify3dsTransaction();
1204
+ if (tdsResult) {
1205
+ alert("Payment " + tdsResult.transaction_status);
1206
+ return;
1207
+ }
1208
+ }
1209
+
1210
+ document.getElementById("pay-btn").addEventListener("click", async () => {
1211
+ try {
1212
+ const response = await checkout.payment({
1213
+ customer: { firstName: "John", lastName: "Doe", email: "user@example.com" },
1214
+ cart: { total: 100, items: [{ name: "Item", description: "Desc", quantity: 1, price_unit: 100, discount: 0, taxes: 0, product_reference: "SKU-1", amount_total: 100 }] },
1215
+ currency: "MXN",
1216
+ });
1217
+ alert("Transaction status: " + response.transaction_status);
1218
+ } catch (e) {
1219
+ alert("Error: " + e.message);
1220
+ }
1221
+ });
1222
+
1223
+ init();
1224
+ </script>
1225
+ </body>
1226
+ </html>
834
1227
  ```
835
1228
 
836
- Then, create a TonderCheckout component:
1229
+ ---
837
1230
 
838
- ```jsx
839
- // TonderCheckout.jsx
840
- "use client"; // only for Nextjs
841
- import React, { useEffect, useState } from "react";
842
- import { useTonder } from "./TonderProvider";
1231
+ ### 13.2 Vanilla JS — LiteInlineCheckout
843
1232
 
844
- const TonderCheckout = () => {
845
- const tonder = useTonder();
846
- const [loading, setLoading] = useState(false);
1233
+ This example shows a complete enrollment flow: mount fields → save card → reveal card data.
847
1234
 
848
- useEffect(() => {
849
- if (!tonder) return;
1235
+ ```html
1236
+ <!doctype html>
1237
+ <html lang="en">
1238
+ <head>
1239
+ <script src="https://js.skyflow.com/v1/index.js"></script>
1240
+ <script src="https://zplit-prod.s3.amazonaws.com/v2/bundle.min.js"></script>
1241
+ <style>
1242
+ /* Skyflow iframes need explicit dimensions */
1243
+ .card-field { display: block; width: 100%; height: 44px; margin-bottom: 12px; }
1244
+ .reveal-field { display: block; width: 100%; height: 26px; }
1245
+ </style>
1246
+ </head>
1247
+ <body>
1248
+ <!-- Collect iframes mount here -->
1249
+ <div id="collect_cardholder_name" class="card-field"></div>
1250
+ <div id="collect_card_number" class="card-field"></div>
1251
+ <div id="collect_expiration_month" class="card-field"></div>
1252
+ <div id="collect_expiration_year" class="card-field"></div>
1253
+ <div id="collect_cvv" class="card-field"></div>
1254
+ <button id="save-btn">Save Card</button>
1255
+
1256
+ <!-- Reveal iframes mount here after save -->
1257
+ <div id="card-result" style="display:none">
1258
+ <div id="reveal_cardholder_name" class="reveal-field"></div>
1259
+ <div id="reveal_card_number" class="reveal-field"></div>
1260
+ <div id="reveal_expiration_month" class="reveal-field"></div>
1261
+ <div id="reveal_expiration_year" class="reveal-field"></div>
1262
+ </div>
850
1263
 
851
- tonder.configureCheckout({ customer: { email: "example@email.com" } });
852
- tonder.injectCheckout();
1264
+ <script>
1265
+ const { LiteInlineCheckout } = window.TonderSdk;
853
1266
 
854
- tonder.verify3dsTransaction().then((response) => {
855
- console.log("Verify 3ds response", response);
856
- });
1267
+ const liteCheckout = new LiteInlineCheckout({
1268
+ apiKey: "YOUR_PUBLIC_API_KEY",
1269
+ mode: "stage",
1270
+ returnUrl: window.location.href,
1271
+ });
857
1272
 
858
- return () => {
859
- if (tonder.removeCheckout) {
860
- tonder.removeCheckout();
861
- }
862
- };
863
- }, [tonder]);
1273
+ async function init() {
1274
+ const { access } = await fetch("https://stage.tonder.io/api/secure-token/", {
1275
+ method: "POST",
1276
+ headers: { Authorization: "Token YOUR_SECRET_KEY", "Content-Type": "application/json" },
1277
+ }).then((r) => r.json());
864
1278
 
865
- const handlePayment = async () => {
866
- if (!tonder) return;
867
- setLoading(true);
868
- try {
869
- const response = await tonder.payment(paymentData);
870
- console.log("Payment successful:", response);
871
- alert("Payment successful");
872
- } catch (error) {
873
- console.error("Payment failed:", error);
874
- alert("Payment failed");
875
- } finally {
876
- setLoading(false);
877
- }
878
- };
1279
+ liteCheckout.configureCheckout({
1280
+ customer: { email: "user@example.com" },
1281
+ secureToken: access,
1282
+ });
879
1283
 
880
- return (
881
- <div>
882
- <div id="tonder-checkout"></div>
883
- <button onClick={handlePayment} disabled={loading}>
884
- {loading ? "Processing..." : "Pay Now"}
885
- </button>
886
- </div>
887
- );
888
- };
1284
+ await liteCheckout.injectCheckout();
1285
+
1286
+ const tdsResult = await liteCheckout.verify3dsTransaction();
1287
+ if (tdsResult) {
1288
+ alert("Payment " + tdsResult.transaction_status);
1289
+ return;
1290
+ }
1291
+
1292
+ await liteCheckout.mountCardFields({
1293
+ fields: ["cardholder_name", "card_number", "expiration_month", "expiration_year", "cvv"],
1294
+ });
1295
+ }
889
1296
 
890
- export default TonderCheckout;
1297
+ document.getElementById("save-btn").addEventListener("click", async () => {
1298
+ try {
1299
+ const saved = await liteCheckout.saveCustomerCard();
1300
+ console.log("Saved skyflow_id:", saved.skyflow_id);
1301
+
1302
+ document.getElementById("card-result").style.display = "block";
1303
+
1304
+ await liteCheckout.revealCardFields({
1305
+ fields: ["cardholder_name", "card_number", "expiration_month", "expiration_year"],
1306
+ styles: {
1307
+ inputStyles: {
1308
+ base: { fontSize: "16px", color: "#1d1d1f", border: "none", background: "transparent" },
1309
+ },
1310
+ },
1311
+ });
1312
+ } catch (e) {
1313
+ alert("Error: " + e.message);
1314
+ }
1315
+ });
1316
+
1317
+ init();
1318
+ </script>
1319
+ </body>
1320
+ </html>
891
1321
  ```
892
1322
 
893
- Finally, use it in your checkout component:
1323
+ ---
894
1324
 
895
- ```jsx
896
- "use client";
897
- import { useEffect, useState } from "react";
898
- import { useTonder, TonderProvider } from "./TonderProvider";
899
- import Script from "next/script";
1325
+ ### 13.3 React / Next.js
900
1326
 
901
- export default function TonderCheckout() {
902
- return (
903
- <div id="checkout">
904
- {/*Provider*/}
905
- <TonderProvider>
906
- <CheckoutContent />
907
- </TonderProvider>
908
- </div>
909
- );
910
- }
1327
+ ```jsx
1328
+ import { useEffect, useRef } from "react";
1329
+ import { LiteInlineCheckout } from "tonder-web-sdk";
911
1330
 
912
- function CheckoutContent() {
913
- const tonder = useTonder();
914
- const [loading, setLoading] = useState(false);
1331
+ export default function CheckoutPage() {
1332
+ const checkoutRef = useRef(null);
915
1333
 
916
1334
  useEffect(() => {
917
- if (!tonder) return;
918
- tonder.configureCheckout({ customer: { email: "example@email.com" } });
919
- tonder.injectCheckout();
920
- tonder.verify3dsTransaction().then((response) => {
921
- console.log("Verify 3ds response", response);
1335
+ const liteCheckout = new LiteInlineCheckout({
1336
+ apiKey: process.env.NEXT_PUBLIC_TONDER_API_KEY,
1337
+ mode: "production",
1338
+ returnUrl: `${window.location.origin}/checkout`,
922
1339
  });
923
- return () => {
924
- if (tonder.removeCheckout) {
925
- tonder.removeCheckout();
1340
+ checkoutRef.current = liteCheckout;
1341
+
1342
+ async function init() {
1343
+ // Fetch secure token from your Next.js API route
1344
+ const { access } = await fetch("/api/tonder-token", { method: "POST" }).then((r) => r.json());
1345
+
1346
+ liteCheckout.configureCheckout({
1347
+ customer: { email: "user@example.com" },
1348
+ secureToken: access,
1349
+ });
1350
+
1351
+ await liteCheckout.injectCheckout();
1352
+
1353
+ const tdsResult = await liteCheckout.verify3dsTransaction();
1354
+ if (tdsResult) {
1355
+ if (tdsResult.transaction_status === "Success") {
1356
+ // router.push('/order-confirmation')
1357
+ }
1358
+ return;
926
1359
  }
1360
+
1361
+ await liteCheckout.mountCardFields({
1362
+ fields: ["cardholder_name", "card_number", "expiration_month", "expiration_year", "cvv"],
1363
+ });
1364
+ }
1365
+
1366
+ init();
1367
+
1368
+ return () => {
1369
+ // Cleanup on unmount
1370
+ liteCheckout.unmountCardFields();
927
1371
  };
928
- }, [tonder]);
1372
+ }, []);
929
1373
 
930
- const handlePayment = async () => {
931
- if (!tonder) return;
932
- setLoading(true);
1374
+ async function handlePay() {
933
1375
  try {
934
- const response = await tonder.payment(paymentData);
935
- console.log(response);
936
- alert("Payment successful");
1376
+ const response = await checkoutRef.current.payment({
1377
+ customer: { firstName: "John", lastName: "Doe", email: "user@example.com" },
1378
+ cart: { total: 100, items: [{ name: "Product", description: "...", quantity: 1, price_unit: 100, discount: 0, taxes: 0, product_reference: "SKU-1", amount_total: 100 }] },
1379
+ currency: "MXN",
1380
+ });
1381
+ console.log("Status:", response.transaction_status);
937
1382
  } catch (error) {
938
- console.error(error);
939
- alert("Payment failed");
940
- } finally {
941
- setLoading(false);
1383
+ console.error(error.message, error.detail);
942
1384
  }
943
- };
1385
+ }
944
1386
 
945
1387
  return (
946
1388
  <div>
947
- <div id="tonder-checkout"></div>
948
- <button onClick={handlePayment} disabled={loading}>
949
- {loading ? "Processing..." : "Pay"}
950
- </button>
1389
+ <div id="collect_cardholder_name" style={{ display: "block", width: "100%", height: 44 }} />
1390
+ <div id="collect_card_number" style={{ display: "block", width: "100%", height: 44 }} />
1391
+ <div id="collect_expiration_month" style={{ display: "block", width: "100%", height: 44 }} />
1392
+ <div id="collect_expiration_year" style={{ display: "block", width: "100%", height: 44 }} />
1393
+ <div id="collect_cvv" style={{ display: "block", width: "100%", height: 44 }} />
1394
+ <button onClick={handlePay}>Pay</button>
951
1395
  </div>
952
1396
  );
953
1397
  }
954
1398
  ```
955
1399
 
956
- ### Angular
957
-
958
- For Angular, we recommend using a service to manage the Tonder instance:
959
-
960
- ```typescript
961
- // tonder.service.ts
962
- import { Injectable } from "@angular/core";
963
- import { InlineCheckout } from "tonder-web-sdk";
1400
+ **Next.js API route** (`/pages/api/tonder-token.js`):
1401
+ ```js
1402
+ // This runs on the server safe to use the secret key here
1403
+ export default async function handler(req, res) {
1404
+ const { access } = await fetch("https://app.tonder.io/api/secure-token/", {
1405
+ method: "POST",
1406
+ headers: {
1407
+ Authorization: `Token ${process.env.TONDER_SECRET_KEY}`,
1408
+ "Content-Type": "application/json",
1409
+ },
1410
+ }).then((r) => r.json());
964
1411
 
965
- @Injectable({
966
- providedIn: "root",
967
- })
968
- export class TonderService {
969
- private inlineCheckout: InlineCheckout;
1412
+ res.json({ access });
1413
+ }
1414
+ ```
970
1415
 
971
- constructor(private sdkParameters: IInlineCheckoutOptions) {
972
- this.initializeInlineCheckout();
973
- }
1416
+ ---
974
1417
 
975
- private initializeInlineCheckout(): void {
976
- this.inlineCheckout = new InlineCheckout({ ...this.sdkParameters });
977
- }
1418
+ ### 13.4 Angular
978
1419
 
979
- configureCheckout(customerData: IConfigureCheckout): void {
980
- this.inlineCheckout.configureCheckout({ ...customerData });
981
- }
1420
+ **Component template:**
1421
+ ```html
1422
+ <!-- Secure iframes — card values never touch your code -->
1423
+ <div id="collect_cardholder_name" class="card-field"></div>
1424
+ <div id="collect_card_number" class="card-field"></div>
1425
+ <div id="collect_expiration_month" class="card-field"></div>
1426
+ <div id="collect_expiration_year" class="card-field"></div>
1427
+ <div id="collect_cvv" class="card-field"></div>
1428
+
1429
+ <button (click)="pay()" [disabled]="loading">
1430
+ {{ loading ? 'Processing...' : 'Pay' }}
1431
+ </button>
1432
+ ```
982
1433
 
983
- async injectCheckout(): Promise<void> {
984
- await this.inlineCheckout.injectCheckout();
985
- }
1434
+ **Component class:**
1435
+ ```typescript
1436
+ import { Component, OnInit, OnDestroy } from "@angular/core";
1437
+ import { LiteInlineCheckout } from "tonder-web-sdk";
986
1438
 
987
- verify3dsTransaction(): Promise<ITransaction | void> {
988
- return this.inlineCheckout.verify3dsTransaction();
989
- }
1439
+ @Component({ selector: "app-checkout", templateUrl: "./checkout.component.html" })
1440
+ export class CheckoutComponent implements OnInit, OnDestroy {
1441
+ private liteCheckout!: LiteInlineCheckout;
1442
+ loading = false;
990
1443
 
991
- // Add more functions, for example for lite sdk: get payment methods
1444
+ async ngOnInit() {
1445
+ this.liteCheckout = new LiteInlineCheckout({
1446
+ apiKey: environment.tonderApiKey,
1447
+ mode: "production",
1448
+ returnUrl: `${window.location.origin}/checkout`,
1449
+ });
992
1450
 
993
- // getCustomerPaymentMethods(): Promise<IPaymentMethod[]> {
994
- // return this.liteCheckout.getCustomerPaymentMethods();
995
- // }
996
- }
1451
+ // Fetch from your Angular backend — never expose the secret key on the frontend
1452
+ const { access } = await this.tonderTokenService.getSecureToken().toPromise();
997
1453
 
998
- // checkout.component.ts
999
- import { Component, OnInit, OnDestroy } from "@angular/core";
1000
- import { TonderService } from "./tonder.service";
1454
+ this.liteCheckout.configureCheckout({
1455
+ customer: { email: "user@example.com" },
1456
+ secureToken: access,
1457
+ });
1001
1458
 
1002
- @Component({
1003
- selector: "app-tonder-checkout",
1004
- template: `
1005
- <div id="tonder-checkout"></div>
1006
- <button (click)="pay()" [disabled]="loading">
1007
- {{ loading ? "Processing..." : "Pay" }}
1008
- </button>
1009
- `,
1010
- providers: [
1011
- {
1012
- provide: TonderInlineService,
1013
- // Inicialización del SDK de Tonder Inline
1014
- // Nota: Reemplace estas credenciales con las suyas propias en desarrollo/producción
1015
- useFactory: () =>
1016
- new TonderInlineService({
1017
- apiKey: "11e3d3c3e95e0eaabbcae61ebad34ee5f93c3d27",
1018
- returnUrl: "http://localhost:4200/checkout/payment?tabPayment=0",
1019
- mode: "development",
1020
- }),
1021
- },
1022
- ],
1023
- })
1024
- export class TonderCheckoutComponent implements OnInit, OnDestroy {
1025
- loading = false;
1459
+ await this.liteCheckout.injectCheckout();
1026
1460
 
1027
- constructor(private tonderService: TonderService) {}
1461
+ const tdsResult = await this.liteCheckout.verify3dsTransaction();
1462
+ if (tdsResult) {
1463
+ // Handle 3DS return
1464
+ return;
1465
+ }
1028
1466
 
1029
- ngOnInit() {
1030
- this.initCheckout();
1467
+ await this.liteCheckout.mountCardFields({
1468
+ fields: ["cardholder_name", "card_number", "expiration_month", "expiration_year", "cvv"],
1469
+ });
1031
1470
  }
1032
1471
 
1033
1472
  ngOnDestroy() {
1034
- // Limpieza del checkout al destruir el componente
1035
- this.tonderService.removeCheckout();
1036
- }
1037
-
1038
- async initCheckout() {
1039
- this.tonderService.configureCheckout({
1040
- customer: { email: "example@email.com" },
1041
- });
1042
- await this.tonderService.injectCheckout();
1043
- this.tonderService.verify3dsTransaction().then((response) => {
1044
- console.log("Verify 3ds response", response);
1045
- });
1473
+ this.liteCheckout?.unmountCardFields();
1046
1474
  }
1047
1475
 
1048
1476
  async pay() {
1049
1477
  this.loading = true;
1050
1478
  try {
1051
- const response = await this.tonderService.payment(paymentData);
1052
- console.log("Payment successful:", response);
1053
- alert("Payment successful");
1479
+ const response = await this.liteCheckout.payment({
1480
+ customer: { firstName: "John", lastName: "Doe", email: "user@example.com" },
1481
+ cart: {
1482
+ total: 100,
1483
+ items: [{ name: "Product", description: "Desc", quantity: 1, price_unit: 100, discount: 0, taxes: 0, product_reference: "SKU-1", amount_total: 100 }],
1484
+ },
1485
+ currency: "MXN",
1486
+ order_reference: "ORD-001",
1487
+ });
1488
+ console.log("Transaction status:", response.transaction_status);
1054
1489
  } catch (error) {
1055
- console.error("Payment failed:", error);
1056
- alert("Payment failed");
1490
+ console.error(error.message);
1057
1491
  } finally {
1058
1492
  this.loading = false;
1059
1493
  }
@@ -1061,25 +1495,181 @@ export class TonderCheckoutComponent implements OnInit, OnDestroy {
1061
1495
  }
1062
1496
  ```
1063
1497
 
1064
- ## Notes
1498
+ ---
1065
1499
 
1066
- ### General
1500
+ ## 14. HMAC Signature Validation
1067
1501
 
1068
- - Replace `'your-api-key'`, `'http://your-website.com/checkout'`, `customStyles`, and `paymentData` with your actual values.
1069
- - The `paymentData` should be defined according to your specific requirements.
1502
+ Tonder supports HMAC validation to ensure request data is not tampered with in transit.
1070
1503
 
1071
- ### Script Dependencies
1504
+ ### Overview
1072
1505
 
1073
- For all implementations, ensure you include the necessary scripts:
1506
+ - You receive an **API Secret Key** from Tonder
1507
+ - You generate an HMAC-SHA256 signature (Base64-encoded) from the request body
1508
+ - Tonder compares your signature with its own calculation
1509
+ - A mismatch results in a **403**
1074
1510
 
1075
- ```html
1076
- <script src="https://js.skyflow.com/v1/index.js"></script>
1077
- <script src="https://openpay.s3.amazonaws.com/openpay.v1.min.js"></script>
1078
- <script src="https://openpay.s3.amazonaws.com/openpay-data.v1.min.js"></script>
1079
- <!-- Only if not use npm package -->
1080
- <script src="https://zplit-prod.s3.amazonaws.com/v1/bundle.min.js"></script>
1511
+ > **Important:** HMAC generation must be done on your **backend server**, not in client-side code.
1512
+
1513
+ ### Generating the signature (Node.js)
1514
+
1515
+ ```js
1516
+ const crypto = require("crypto");
1517
+
1518
+ function generateHMAC(secretKey, requestBody) {
1519
+ const dataString = JSON.stringify(requestBody);
1520
+ return crypto.createHmac("sha256", secretKey).update(dataString).digest("base64");
1521
+ }
1522
+
1523
+ const secretKey = "YOUR_SECRET_KEY";
1524
+ const requestBody = {
1525
+ customer: { email: "user@example.com", firstName: "John", lastName: "Doe" },
1526
+ currency: "mxn",
1527
+ cart: { total: 100, items: [{ amount_total: 100, description: "Item" }] },
1528
+ };
1529
+
1530
+ const signature = generateHMAC(secretKey, requestBody);
1531
+ ```
1532
+
1533
+ ### Providing signatures in the SDK
1534
+
1535
+ ```js
1536
+ const checkout = new InlineCheckout({
1537
+ apiKey: "YOUR_PUBLIC_API_KEY",
1538
+ returnUrl: "https://myapp.com/checkout",
1539
+ signatures: {
1540
+ transaction: "<Base64 HMAC>", // For payment requests
1541
+ customer: "<Base64 HMAC>", // For card operations (save, delete)
1542
+ },
1543
+ });
1544
+ ```
1545
+
1546
+ > Always confirm with Tonder which fields must be included in the signed payload and whether alphabetical key ordering is required. A mismatch results in a **403**.
1547
+
1548
+ ---
1549
+
1550
+ ## 15. Deprecated API
1551
+
1552
+ <details>
1553
+ <summary>Show deprecated features</summary>
1554
+
1555
+ ### Raw card data in `payment()` — removed
1556
+
1557
+ Passing raw card fields (`card_number`, `cvv`, etc.) to `payment()` is no longer supported. Card data must be collected through Skyflow secure iframes via `mountCardFields()`.
1558
+
1559
+ ```js
1560
+ // ❌ Removed — no longer works
1561
+ await liteCheckout.payment({
1562
+ card: {
1563
+ card_number: "4111111111111111",
1564
+ cvv: "123",
1565
+ expiration_month: "12",
1566
+ expiration_year: "25",
1567
+ cardholder_name: "John Doe",
1568
+ },
1569
+ // ...
1570
+ });
1571
+
1572
+ // ✅ Current — mount fields first, then pay with no card field (new card)
1573
+ await liteCheckout.mountCardFields({
1574
+ fields: ["cardholder_name", "card_number", "expiration_month", "expiration_year", "cvv"],
1575
+ });
1576
+ await liteCheckout.payment({ customer, cart, currency });
1577
+
1578
+ // ✅ Or pay with a saved card using its skyflow_id
1579
+ await liteCheckout.payment({ customer, cart, currency, card: "skyflow_id_here" });
1580
+ ```
1581
+
1582
+ ### `saveCustomerCard(cardData)` — raw data param removed
1583
+
1584
+ `saveCustomerCard()` no longer accepts a `cardData` argument. Mount all 5 card fields first.
1585
+
1586
+ ```js
1587
+ // ❌ Removed
1588
+ await liteCheckout.saveCustomerCard({ card_number: "...", cvv: "...", ... });
1589
+
1590
+ // ✅ Current
1591
+ await liteCheckout.mountCardFields({
1592
+ fields: ["cardholder_name", "card_number", "expiration_month", "expiration_year", "cvv"],
1593
+ });
1594
+ await liteCheckout.saveCustomerCard();
1081
1595
  ```
1082
1596
 
1083
- ## License
1597
+ ### `InlineCheckout` root-level `styles` — deprecated
1598
+
1599
+ Passing `styles` at the root of `InlineCheckout` options is deprecated. Move it to `customization.styles`.
1600
+ If both are provided, `customization.styles` takes precedence.
1601
+
1602
+ ```js
1603
+ // ⚠️ Deprecated — still works, but will be removed in a future major version
1604
+ new InlineCheckout({
1605
+ styles: { cardForm: { inputStyles: { base: { border: "1px solid #ccc" } } } },
1606
+ });
1607
+
1608
+ // ✅ Current
1609
+ new InlineCheckout({
1610
+ customization: {
1611
+ styles: { cardForm: { inputStyles: { base: { border: "1px solid #ccc" } } } },
1612
+ },
1613
+ });
1614
+ ```
1615
+
1616
+ ### Flat `styles` format in InlineCheckout — deprecated
1617
+
1618
+ Passing a flat `{ inputStyles, labelStyles, errorTextStyles }` object inside `styles` is deprecated.
1619
+ Use `{ cardForm: { ... } }` instead. Note the key rename: `errorTextStyles` → `errorStyles`.
1620
+
1621
+ ```js
1622
+ // ⚠️ Deprecated
1623
+ new InlineCheckout({
1624
+ customization: {
1625
+ styles: {
1626
+ inputStyles: { base: { border: "1px solid #ccc" } },
1627
+ labelStyles: { base: { fontSize: "12px" } },
1628
+ errorTextStyles: { base: { color: "#f44336" } },
1629
+ },
1630
+ },
1631
+ });
1632
+
1633
+ // ✅ Current
1634
+ new InlineCheckout({
1635
+ customization: {
1636
+ styles: {
1637
+ cardForm: {
1638
+ inputStyles: { base: { border: "1px solid #ccc" } },
1639
+ labelStyles: { base: { fontSize: "12px" } },
1640
+ errorStyles: { base: { color: "#f44336" } },
1641
+ },
1642
+ },
1643
+ },
1644
+ });
1645
+ ```
1646
+
1647
+ ### Field validation functions — no longer needed
1648
+
1649
+ The following validation utilities are still exported but are no longer needed. Skyflow secure iframes handle all card field validation internally.
1650
+
1651
+ ```js
1652
+ // No longer needed — Skyflow iframes validate fields automatically
1653
+ import {
1654
+ validateCardNumber,
1655
+ validateCardholderName,
1656
+ validateCVV,
1657
+ validateExpirationDate,
1658
+ validateExpirationMonth,
1659
+ validateExpirationYear,
1660
+ } from "tonder-web-sdk";
1661
+ ```
1662
+
1663
+ Use the `events.cardNumberEvents.onBlur` / `onChange` callbacks with `{ isValid }` instead — see [Section 4.4](#44-form-events-icardvents).
1664
+
1665
+ ### `getSkyflowTokens` — removed
1666
+
1667
+ The `getSkyflowTokens` helper has been removed. Use `saveCustomerCard()` to tokenize card data, and `revealCardFields()` to display it — see [Section 9.2](#92-save-a-new-card-enrollment) and [Section 10](#10-revealing-card-data--revealcardfields-liteinlinecheckout).
1668
+
1669
+ </details>
1670
+
1671
+ ---
1672
+
1673
+ ## 16. License
1084
1674
 
1085
- [MIT](https://choosealicense.com/licenses/mit/)
1675
+ Copyright © Tonder. All rights reserved.