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 +1435 -845
- package/package.json +3 -3
- package/types/card.d.ts +0 -8
- package/types/common.d.ts +55 -0
- package/types/inlineCheckout.d.ts +16 -2
- package/types/liteInlineCheckout.d.ts +130 -52
- package/v2/bundle.js +69092 -0
- package/v2/bundle.min.js +1 -0
- package/v2/index.html +574 -0
- package/v1/bundle.js +0 -1
- package/v1/bundle.min.js +0 -1
- package/v1/index.html +0 -180
package/README.md
CHANGED
|
@@ -1,1059 +1,1493 @@
|
|
|
1
|
-
#
|
|
1
|
+
# tonder-web-sdk
|
|
2
2
|
|
|
3
|
-
|
|
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. [
|
|
8
|
-
2. [
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
- [
|
|
13
|
-
- [
|
|
14
|
-
4.
|
|
15
|
-
5. [
|
|
16
|
-
6. [
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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
|
-
|
|
107
|
+
Choose **one** of the following installation methods:
|
|
25
108
|
|
|
26
|
-
### NPM
|
|
109
|
+
### Option A — NPM (recommended for bundlers)
|
|
27
110
|
|
|
28
111
|
```bash
|
|
29
|
-
npm
|
|
112
|
+
npm install tonder-web-sdk
|
|
113
|
+
# or
|
|
114
|
+
yarn add tonder-web-sdk
|
|
30
115
|
```
|
|
31
116
|
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
151
|
+
---
|
|
49
152
|
|
|
50
|
-
|
|
153
|
+
## 3. Choosing an Integration
|
|
51
154
|
|
|
52
|
-
|
|
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
|
-
|
|
164
|
+
---
|
|
55
165
|
|
|
56
|
-
|
|
166
|
+
## 4. Constructor & Configuration
|
|
57
167
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
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
|
-
|
|
68
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
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
|
-
|
|
89
|
-
inlineCheckout.injectCheckout();
|
|
221
|
+
### 4.3 Secure token
|
|
90
222
|
|
|
91
|
-
|
|
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
|
-
|
|
97
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
111
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
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
|
-
|
|
129
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
|
|
457
|
+
---
|
|
197
458
|
|
|
198
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
235
|
-
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
574
|
+
**Metadata for reporting:**
|
|
349
575
|
|
|
350
|
-
|
|
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
|
-
|
|
585
|
+
---
|
|
353
586
|
|
|
354
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
601
|
+
For `InlineCheckout`, saved-card selection is handled automatically by the built-in UI.
|
|
365
602
|
|
|
366
|
-
|
|
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
|
-
|
|
605
|
+
### 7.3 Alternative Payment Method (APM)
|
|
371
606
|
|
|
372
|
-
|
|
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
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
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
|
-
|
|
477
|
-
payment_method: "Spei",
|
|
478
|
-
};
|
|
638
|
+
});
|
|
479
639
|
```
|
|
480
640
|
|
|
481
|
-
|
|
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
|
-
|
|
671
|
+
</details>
|
|
484
672
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
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
|
-
|
|
692
|
+
> **Note:** 3DS authentication is handled automatically by the SDK. You do not need to handle redirects manually.
|
|
493
693
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
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
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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
|
-
|
|
725
|
+
// Normal page load — mount card fields (LiteInlineCheckout) or let InlineCheckout render
|
|
726
|
+
```
|
|
520
727
|
|
|
521
|
-
|
|
728
|
+
---
|
|
522
729
|
|
|
523
|
-
|
|
730
|
+
## 9. Managing Saved Cards (LiteInlineCheckout)
|
|
524
731
|
|
|
525
|
-
|
|
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
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
const crypto = require('crypto');
|
|
734
|
+
```js
|
|
735
|
+
const { cards } = await liteCheckout.getCustomerCards();
|
|
736
|
+
// Returns: ICustomerCardsResponse = { user_id: number, cards: ICard[] }
|
|
535
737
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
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
|
-
|
|
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
|
-
|
|
569
|
-
console.log("Generated HMAC:", signature);
|
|
570
|
-
```
|
|
762
|
+
### 9.2 Save a new card (enrollment)
|
|
571
763
|
|
|
572
|
-
|
|
764
|
+
**Prerequisites:** Mount all 5 card fields (`mountCardFields()` with no `card_id`) and let the user fill them in.
|
|
573
765
|
|
|
574
|
-
|
|
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
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
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
|
-
|
|
785
|
+
---
|
|
589
786
|
|
|
590
|
-
###
|
|
787
|
+
### 9.3 Remove a card
|
|
591
788
|
|
|
592
|
-
|
|
789
|
+
```js
|
|
790
|
+
await liteCheckout.removeCustomerCard(card.fields.skyflow_id);
|
|
593
791
|
|
|
594
|
-
|
|
595
|
-
|
|
792
|
+
// Refresh the card list
|
|
793
|
+
const { cards } = await liteCheckout.getCustomerCards();
|
|
794
|
+
```
|
|
596
795
|
|
|
597
|
-
|
|
796
|
+
---
|
|
598
797
|
|
|
599
|
-
|
|
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
|
-
|
|
802
|
+
> **When to call:** Only after a successful `saveCustomerCard()` or new-card `payment()`.
|
|
605
803
|
|
|
606
|
-
|
|
804
|
+
**Default container IDs and redaction:**
|
|
607
805
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
827
|
+
await liteCheckout.revealCardFields({
|
|
828
|
+
fields: ["cardholder_name", "card_number", "expiration_month", "expiration_year"],
|
|
829
|
+
});
|
|
830
|
+
```
|
|
627
831
|
|
|
628
|
-
|
|
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
|
-
|
|
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
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
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
|
-
|
|
650
|
-
|
|
651
|
-
|
|
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
|
-
|
|
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
|
-
|
|
657
|
-
import { InlineCheckout } from "tonder-web-sdk";
|
|
894
|
+
---
|
|
658
895
|
|
|
659
|
-
|
|
660
|
-
const returnUrl = "http://your-website.com/checkout";
|
|
896
|
+
## 11. Error Handling
|
|
661
897
|
|
|
662
|
-
|
|
663
|
-
mode: "development",
|
|
664
|
-
apiKey,
|
|
665
|
-
returnUrl,
|
|
666
|
-
styles: customStyles, // Define your custom styles here
|
|
667
|
-
});
|
|
898
|
+
### 11.1 Error structure
|
|
668
899
|
|
|
669
|
-
|
|
670
|
-
inlineCheckout.injectCheckout();
|
|
900
|
+
When a public SDK method fails, it throws a plain error object with the following shape:
|
|
671
901
|
|
|
672
|
-
|
|
673
|
-
|
|
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
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
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
|
-
|
|
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
|
-
|
|
695
|
-
import { InlineCheckout } from "tonder-web-sdk";
|
|
934
|
+
### 11.2 Error code reference
|
|
696
935
|
|
|
697
|
-
|
|
698
|
-
|
|
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
|
-
|
|
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
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
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
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
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
|
-
|
|
742
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
753
|
-
const returnUrl = "http://your-website.com/checkout";
|
|
1037
|
+
---
|
|
754
1038
|
|
|
755
|
-
|
|
756
|
-
mode: "development",
|
|
757
|
-
apiKey,
|
|
758
|
-
returnUrl,
|
|
759
|
-
});
|
|
1039
|
+
### 12.2 LiteInlineCheckout styles
|
|
760
1040
|
|
|
761
|
-
|
|
762
|
-
liteCheckout.injectCheckout();
|
|
1041
|
+
Pass styles inside `customization.styles`. Uses the same `ICardStyles` format:
|
|
763
1042
|
|
|
764
|
-
|
|
765
|
-
|
|
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
|
-
|
|
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
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
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
|
-
|
|
1110
|
+
---
|
|
793
1111
|
|
|
794
|
-
|
|
1112
|
+
### 12.4 InlineCheckout UI customization
|
|
795
1113
|
|
|
796
|
-
|
|
1114
|
+
Control visibility and behavior of the pre-built checkout UI:
|
|
797
1115
|
|
|
798
|
-
```
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1160
|
+
---
|
|
807
1161
|
|
|
808
|
-
|
|
809
|
-
const [tonderInstance, setTonderInstance] = useState(null);
|
|
1162
|
+
## 13. Framework Examples
|
|
810
1163
|
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
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
|
-
|
|
826
|
-
}, []);
|
|
1201
|
+
await checkout.injectCheckout();
|
|
827
1202
|
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
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
|
-
|
|
1229
|
+
---
|
|
837
1230
|
|
|
838
|
-
|
|
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
|
-
|
|
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
|
-
|
|
849
|
-
|
|
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
|
-
|
|
852
|
-
|
|
1264
|
+
<script>
|
|
1265
|
+
const { LiteInlineCheckout } = window.TonderSdk;
|
|
853
1266
|
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
1267
|
+
const liteCheckout = new LiteInlineCheckout({
|
|
1268
|
+
apiKey: "YOUR_PUBLIC_API_KEY",
|
|
1269
|
+
mode: "stage",
|
|
1270
|
+
returnUrl: window.location.href,
|
|
1271
|
+
});
|
|
857
1272
|
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
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
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
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
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1323
|
+
---
|
|
894
1324
|
|
|
895
|
-
|
|
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
|
-
|
|
902
|
-
|
|
903
|
-
|
|
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
|
|
913
|
-
const
|
|
914
|
-
const [loading, setLoading] = useState(false);
|
|
1331
|
+
export default function CheckoutPage() {
|
|
1332
|
+
const checkoutRef = useRef(null);
|
|
915
1333
|
|
|
916
1334
|
useEffect(() => {
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
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
|
-
|
|
924
|
-
|
|
925
|
-
|
|
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
|
-
}, [
|
|
1372
|
+
}, []);
|
|
929
1373
|
|
|
930
|
-
|
|
931
|
-
if (!tonder) return;
|
|
932
|
-
setLoading(true);
|
|
1374
|
+
async function handlePay() {
|
|
933
1375
|
try {
|
|
934
|
-
const response = await
|
|
935
|
-
|
|
936
|
-
|
|
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="
|
|
948
|
-
<
|
|
949
|
-
|
|
950
|
-
|
|
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
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
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
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
export class TonderService {
|
|
969
|
-
private inlineCheckout: InlineCheckout;
|
|
1412
|
+
res.json({ access });
|
|
1413
|
+
}
|
|
1414
|
+
```
|
|
970
1415
|
|
|
971
|
-
|
|
972
|
-
this.initializeInlineCheckout();
|
|
973
|
-
}
|
|
1416
|
+
---
|
|
974
1417
|
|
|
975
|
-
|
|
976
|
-
this.inlineCheckout = new InlineCheckout({ ...this.sdkParameters });
|
|
977
|
-
}
|
|
1418
|
+
### 13.4 Angular
|
|
978
1419
|
|
|
979
|
-
|
|
980
|
-
|
|
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
|
-
|
|
984
|
-
|
|
985
|
-
|
|
1434
|
+
**Component class:**
|
|
1435
|
+
```typescript
|
|
1436
|
+
import { Component, OnInit, OnDestroy } from "@angular/core";
|
|
1437
|
+
import { LiteInlineCheckout } from "tonder-web-sdk";
|
|
986
1438
|
|
|
987
|
-
|
|
988
|
-
|
|
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
|
-
|
|
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
|
-
|
|
994
|
-
|
|
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
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1454
|
+
this.liteCheckout.configureCheckout({
|
|
1455
|
+
customer: { email: "user@example.com" },
|
|
1456
|
+
secureToken: access,
|
|
1457
|
+
});
|
|
1001
1458
|
|
|
1002
|
-
|
|
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
|
-
|
|
1461
|
+
const tdsResult = await this.liteCheckout.verify3dsTransaction();
|
|
1462
|
+
if (tdsResult) {
|
|
1463
|
+
// Handle 3DS return
|
|
1464
|
+
return;
|
|
1465
|
+
}
|
|
1028
1466
|
|
|
1029
|
-
|
|
1030
|
-
|
|
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
|
-
|
|
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.
|
|
1052
|
-
|
|
1053
|
-
|
|
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(
|
|
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
|
-
|
|
1498
|
+
---
|
|
1065
1499
|
|
|
1066
|
-
|
|
1500
|
+
## 14. HMAC Signature Validation
|
|
1067
1501
|
|
|
1068
|
-
|
|
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
|
-
###
|
|
1504
|
+
### Overview
|
|
1072
1505
|
|
|
1073
|
-
|
|
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
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1675
|
+
Copyright © Tonder. All rights reserved.
|