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