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