@tonder.io/ionic-full-sdk 0.0.15-beta → 0.0.17-beta
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/dist/classes/3dsHandler.d.ts +10 -2
- package/dist/classes/inlineCheckout.d.ts +5 -0
- package/dist/helpers/utils.d.ts +9 -0
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/src/classes/3dsHandler.ts +143 -17
- package/src/classes/inlineCheckout.ts +68 -32
- package/src/helpers/utils.ts +13 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tonder.io/ionic-full-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.17-beta",
|
|
4
4
|
"description": "Tonder ionic full SDK",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.js",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"author": "",
|
|
11
11
|
"license": "ISC",
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@tonder.io/ionic-lite-sdk": "^0.0.
|
|
13
|
+
"@tonder.io/ionic-lite-sdk": "^0.0.25-beta",
|
|
14
14
|
"crypto-js": "^4.1.1",
|
|
15
15
|
"skyflow-js": "^1.34.1"
|
|
16
16
|
},
|
|
@@ -27,7 +27,45 @@ export class ThreeDSHandler {
|
|
|
27
27
|
saveVerifyTransactionUrl() {
|
|
28
28
|
const url = this.payload?.next_action?.redirect_to_url?.verify_transaction_status_url
|
|
29
29
|
if (url) {
|
|
30
|
-
|
|
30
|
+
this.saveUrlWithExpiration(url)
|
|
31
|
+
} else {
|
|
32
|
+
const url = this.payload?.next_action?.iframe_resources?.verify_transaction_status_url
|
|
33
|
+
if (url) {
|
|
34
|
+
this.saveUrlWithExpiration(url)
|
|
35
|
+
} else {
|
|
36
|
+
console.log('No verify_transaction_status_url found');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
saveUrlWithExpiration(url: string) {
|
|
42
|
+
try {
|
|
43
|
+
const now = new Date()
|
|
44
|
+
const item = {
|
|
45
|
+
url: url,
|
|
46
|
+
// Expires after 20 minutes
|
|
47
|
+
expires: now.getTime() + 20 * 60 * 1000
|
|
48
|
+
}
|
|
49
|
+
localStorage.setItem('verify_transaction_status', JSON.stringify(item))
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.log('error: ', error)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
getUrlWithExpiration() {
|
|
56
|
+
const status = localStorage.getItem("verify_transaction_status");
|
|
57
|
+
if(status) {
|
|
58
|
+
const item = JSON.parse(status)
|
|
59
|
+
if (!item) return
|
|
60
|
+
const now = new Date()
|
|
61
|
+
if (now.getTime() > item.expires) {
|
|
62
|
+
this.removeVerifyTransactionUrl()
|
|
63
|
+
return null
|
|
64
|
+
} else {
|
|
65
|
+
return item.url
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
return null
|
|
31
69
|
}
|
|
32
70
|
}
|
|
33
71
|
|
|
@@ -39,17 +77,54 @@ export class ThreeDSHandler {
|
|
|
39
77
|
return localStorage.getItem("verify_transaction_status_url")
|
|
40
78
|
}
|
|
41
79
|
|
|
42
|
-
|
|
43
|
-
const
|
|
80
|
+
loadIframe() {
|
|
81
|
+
const iframe = this.payload?.next_action?.iframe_resources?.iframe
|
|
82
|
+
|
|
83
|
+
if (iframe) {
|
|
84
|
+
return new Promise((resolve, reject) => {
|
|
85
|
+
const iframe = this.payload?.next_action?.iframe_resources?.iframe
|
|
86
|
+
|
|
87
|
+
if (iframe) {
|
|
88
|
+
this.saveVerifyTransactionUrl()
|
|
89
|
+
const container = document.createElement('div')
|
|
90
|
+
container.innerHTML = iframe
|
|
91
|
+
document.body.appendChild(container)
|
|
92
|
+
|
|
93
|
+
// Create and append the script tag manually
|
|
94
|
+
const script = document.createElement('script')
|
|
95
|
+
script.textContent = 'document.getElementById("tdsMmethodForm").submit();'
|
|
96
|
+
container.appendChild(script)
|
|
97
|
+
|
|
98
|
+
// Resolve the promise when the iframe is loaded
|
|
99
|
+
const iframeElement = document.getElementById('tdsMmethodTgtFrame')
|
|
100
|
+
if(iframeElement) {
|
|
101
|
+
iframeElement.onload = () => resolve(true)
|
|
102
|
+
} else {
|
|
103
|
+
console.log('No redirection found');
|
|
104
|
+
reject(false)
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
console.log('No redirection found');
|
|
108
|
+
reject(false)
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
getRedirectUrl() {
|
|
115
|
+
return this.payload?.next_action?.redirect_to_url?.url
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
redirectToChallenge() {
|
|
119
|
+
const url = this.getRedirectUrl()
|
|
44
120
|
if (url) {
|
|
45
|
-
this.saveVerifyTransactionUrl()
|
|
46
|
-
|
|
121
|
+
this.saveVerifyTransactionUrl()
|
|
122
|
+
window.location = url;
|
|
47
123
|
} else {
|
|
48
124
|
console.log('No redirection found');
|
|
49
|
-
return false
|
|
50
125
|
}
|
|
51
126
|
}
|
|
52
|
-
|
|
127
|
+
|
|
53
128
|
// Returns an object
|
|
54
129
|
// https://example.com/?name=John&age=30&city=NewYork
|
|
55
130
|
// { name: "John", age: "30", city: "NewYork" }
|
|
@@ -64,8 +139,63 @@ export class ThreeDSHandler {
|
|
|
64
139
|
return parameters;
|
|
65
140
|
}
|
|
66
141
|
|
|
142
|
+
handleSuccessTransaction(response: any) {
|
|
143
|
+
this.removeVerifyTransactionUrl();
|
|
144
|
+
if(this.successUrl) {
|
|
145
|
+
window.location = this.successUrl as Location;
|
|
146
|
+
}
|
|
147
|
+
console.log('Transacción autorizada exitosamente.');
|
|
148
|
+
return response;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
handleDeclinedTransaction(response: any) {
|
|
152
|
+
this.removeVerifyTransactionUrl();
|
|
153
|
+
console.log('Transacción rechazada.');
|
|
154
|
+
throw new Error("Transacción rechazada.");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// TODO: the method below needs to be tested with a real 3DS challenge
|
|
158
|
+
// since we couldn't get a test card that works with this feature
|
|
159
|
+
async handle3dsChallenge(response_json: any) {
|
|
160
|
+
// Create the form element:
|
|
161
|
+
const form = document.createElement('form');
|
|
162
|
+
form.name = 'frm';
|
|
163
|
+
form.method = 'POST';
|
|
164
|
+
form.action = response_json.redirect_post_url;
|
|
165
|
+
|
|
166
|
+
// Add hidden fields:
|
|
167
|
+
const creqInput = document.createElement('input');
|
|
168
|
+
creqInput.type = 'hidden';
|
|
169
|
+
creqInput.name = response_json.creq;
|
|
170
|
+
creqInput.value = response_json.creq;
|
|
171
|
+
form.appendChild(creqInput);
|
|
172
|
+
|
|
173
|
+
const termUrlInput = document.createElement('input');
|
|
174
|
+
termUrlInput.type = 'hidden';
|
|
175
|
+
termUrlInput.name = response_json.term_url;
|
|
176
|
+
termUrlInput.value = response_json.TermUrl;
|
|
177
|
+
form.appendChild(termUrlInput);
|
|
178
|
+
|
|
179
|
+
// Append the form to the body:
|
|
180
|
+
document.body.appendChild(form);
|
|
181
|
+
form.submit();
|
|
182
|
+
|
|
183
|
+
await this.verifyTransactionStatus();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async handleTransactionResponse(response: any) {
|
|
187
|
+
const response_json = await response.json();
|
|
188
|
+
if (response_json.status === "Pending") {
|
|
189
|
+
return await this.handle3dsChallenge(response_json);
|
|
190
|
+
} else if (["Success", "Authorized"].includes(response_json.status)) {
|
|
191
|
+
return this.handleSuccessTransaction(response);
|
|
192
|
+
} else {
|
|
193
|
+
return this.handleDeclinedTransaction(response);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
67
197
|
async verifyTransactionStatus() {
|
|
68
|
-
const verifyUrl = this.
|
|
198
|
+
const verifyUrl = this.getUrlWithExpiration();
|
|
69
199
|
|
|
70
200
|
if (verifyUrl) {
|
|
71
201
|
const url = `${this.baseUrl}${verifyUrl}`;
|
|
@@ -79,23 +209,19 @@ export class ThreeDSHandler {
|
|
|
79
209
|
// body: JSON.stringify(data),
|
|
80
210
|
});
|
|
81
211
|
|
|
82
|
-
if (response.status
|
|
83
|
-
this.removeVerifyTransactionUrl();
|
|
84
|
-
//@ts-ignore
|
|
85
|
-
window.location = this.successUrl
|
|
86
|
-
console.log('La transacción se verificó con éxito.');
|
|
87
|
-
return response;
|
|
88
|
-
} else {
|
|
212
|
+
if (response.status !== 200) {
|
|
89
213
|
console.error('La verificación de la transacción falló.');
|
|
90
|
-
return
|
|
214
|
+
return
|
|
91
215
|
}
|
|
216
|
+
|
|
217
|
+
return await this.handleTransactionResponse(response);
|
|
92
218
|
} catch (error) {
|
|
93
219
|
console.error('Error al verificar la transacción:', error);
|
|
94
220
|
return error;
|
|
95
221
|
}
|
|
96
222
|
} else {
|
|
97
223
|
console.log('No verify_transaction_status_url found');
|
|
98
|
-
return null;
|
|
99
224
|
}
|
|
100
225
|
}
|
|
226
|
+
|
|
101
227
|
}
|
|
@@ -3,7 +3,8 @@ import { LiteCheckout } from '@tonder.io/ionic-lite-sdk';
|
|
|
3
3
|
import {
|
|
4
4
|
showError,
|
|
5
5
|
showMessage,
|
|
6
|
-
mapCards
|
|
6
|
+
mapCards,
|
|
7
|
+
getBrowserInfo
|
|
7
8
|
} from '../helpers/utils';
|
|
8
9
|
import { initSkyflow } from '../helpers/skyflow'
|
|
9
10
|
import { ThreeDSHandler } from './3dsHandler';
|
|
@@ -62,6 +63,9 @@ export class InlineCheckout {
|
|
|
62
63
|
radioChecked: string | null
|
|
63
64
|
fetchingPayment: boolean
|
|
64
65
|
isOpenPaySandbox: boolean = true
|
|
66
|
+
metadata = {}
|
|
67
|
+
card: { skyflow_id: string } | null= null
|
|
68
|
+
currency: string = ""
|
|
65
69
|
|
|
66
70
|
constructor ({
|
|
67
71
|
apiKey,
|
|
@@ -155,12 +159,26 @@ export class InlineCheckout {
|
|
|
155
159
|
}
|
|
156
160
|
}
|
|
157
161
|
|
|
162
|
+
#handleMetadata(data: { metadata: any }) {
|
|
163
|
+
this.metadata = data?.metadata
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
#handleCurrency(data: { currency: string }) {
|
|
167
|
+
this.currency = data?.currency
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
#handleCard(data: { skyflow_id: string }) {
|
|
171
|
+
this.card = data
|
|
172
|
+
}
|
|
173
|
+
|
|
158
174
|
payment(data: any) {
|
|
159
175
|
return new Promise(async (resolve, reject) => {
|
|
160
176
|
try {
|
|
161
177
|
this.#handleCustomer(data.customer)
|
|
162
178
|
this.setCartTotal(data.cart?.total)
|
|
163
179
|
this.setCartItems(data.cart?.items)
|
|
180
|
+
this.#handleMetadata(data)
|
|
181
|
+
this.#handleCurrency(data)
|
|
164
182
|
const response: ErrorResponse | StartCheckoutResponse | false | undefined = await this.#checkout()
|
|
165
183
|
if (response) {
|
|
166
184
|
const process3ds = new ThreeDSHandler({
|
|
@@ -170,10 +188,28 @@ export class InlineCheckout {
|
|
|
170
188
|
successUrl: this.successUrl
|
|
171
189
|
});
|
|
172
190
|
this.callBack(response);
|
|
173
|
-
if
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
191
|
+
if("next_action" in response) {
|
|
192
|
+
const iframe = response?.next_action?.iframe_resources?.iframe
|
|
193
|
+
if (iframe) {
|
|
194
|
+
process3ds.loadIframe()?.then(() => {
|
|
195
|
+
//TODO: Check if this will be necessary on the frontend side
|
|
196
|
+
// after some the tests in production, since the 3DS process
|
|
197
|
+
// doesn't works properly on the sandbox environment
|
|
198
|
+
// setTimeout(() => {
|
|
199
|
+
// process3ds.verifyTransactionStatus();
|
|
200
|
+
// }, 10000);
|
|
201
|
+
process3ds.verifyTransactionStatus();
|
|
202
|
+
}).catch((error) => {
|
|
203
|
+
console.log('Error loading iframe:', error)
|
|
204
|
+
})
|
|
205
|
+
} else {
|
|
206
|
+
const redirectUrl = process3ds.getRedirectUrl()
|
|
207
|
+
if (redirectUrl) {
|
|
208
|
+
process3ds.redirectToChallenge()
|
|
209
|
+
} else {
|
|
210
|
+
resolve(response);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
177
213
|
}
|
|
178
214
|
}
|
|
179
215
|
} catch (error) {
|
|
@@ -231,6 +267,7 @@ export class InlineCheckout {
|
|
|
231
267
|
this.#unmountForm();
|
|
232
268
|
}
|
|
233
269
|
this.radioChecked = radio.id;
|
|
270
|
+
this.#handleCard({ skyflow_id: radio.id });
|
|
234
271
|
}
|
|
235
272
|
|
|
236
273
|
#handleCustomer(customer: Customer) {
|
|
@@ -405,6 +442,23 @@ export class InlineCheckout {
|
|
|
405
442
|
|
|
406
443
|
}
|
|
407
444
|
|
|
445
|
+
async #getCardTokens() {
|
|
446
|
+
if (this.card?.skyflow_id) return this.card
|
|
447
|
+
if(this.collectContainer && "container" in this.collectContainer && "collect" in this.collectContainer.container) {
|
|
448
|
+
try {
|
|
449
|
+
const collectResponseSkyflowTonder: any = await this.collectContainer?.container.collect();
|
|
450
|
+
const cardTokens = await collectResponseSkyflowTonder["records"][0]["fields"];
|
|
451
|
+
return cardTokens;
|
|
452
|
+
} catch (error) {
|
|
453
|
+
showError("Por favor, verifica todos los campos de tu tarjeta", this.collectorIds.msgError, this.collectorIds.tonderPayButton)
|
|
454
|
+
throw error;
|
|
455
|
+
}
|
|
456
|
+
} else {
|
|
457
|
+
showError("Ocurrió un error al conectar con skyflow", this.collectorIds.msgError, this.collectorIds.tonderPayButton)
|
|
458
|
+
throw "Ocurrió un error al conectar con skyflow";
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
408
462
|
async #checkout() {
|
|
409
463
|
|
|
410
464
|
try {
|
|
@@ -426,31 +480,9 @@ export class InlineCheckout {
|
|
|
426
480
|
|
|
427
481
|
const { openpay_keys, reference, business } = this.merchantData
|
|
428
482
|
|
|
429
|
-
const total = Number(this.cartTotal)
|
|
430
|
-
|
|
431
|
-
let cardTokensSkyflowTonder: any = null;
|
|
432
|
-
|
|
433
|
-
if(this.radioChecked === "new") {
|
|
434
|
-
|
|
435
|
-
if(this.collectContainer && "container" in this.collectContainer && "collect" in this.collectContainer.container) {
|
|
436
|
-
try {
|
|
437
|
-
const collectResponseSkyflowTonder: any = await this.collectContainer?.container.collect();
|
|
438
|
-
cardTokensSkyflowTonder = await collectResponseSkyflowTonder["records"][0]["fields"];
|
|
439
|
-
} catch (error) {
|
|
440
|
-
showError("Por favor, verifica todos los campos de tu tarjeta", this.collectorIds.msgError, this.collectorIds.tonderPayButton)
|
|
441
|
-
throw error;
|
|
442
|
-
}
|
|
443
|
-
} else {
|
|
444
|
-
showError("Por favor, verifica todos los campos de tu tarjeta", this.collectorIds.msgError, this.collectorIds.tonderPayButton)
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
} else {
|
|
483
|
+
const total = Number(this.cartTotal);
|
|
448
484
|
|
|
449
|
-
|
|
450
|
-
skyflow_id: this.radioChecked
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
}
|
|
485
|
+
const cardTokens: any = await this.#getCardTokens()
|
|
454
486
|
|
|
455
487
|
let deviceSessionIdTonder: any;
|
|
456
488
|
|
|
@@ -474,7 +506,7 @@ export class InlineCheckout {
|
|
|
474
506
|
|
|
475
507
|
if(saveCard && "checked" in saveCard && saveCard.checked) {
|
|
476
508
|
|
|
477
|
-
await this.liteCheckout.registerCustomerCard(auth_token, { skyflow_id:
|
|
509
|
+
await this.liteCheckout.registerCustomerCard(auth_token, { skyflow_id: cardTokens.skyflow_id });
|
|
478
510
|
|
|
479
511
|
this.cardsInjected = false;
|
|
480
512
|
|
|
@@ -528,8 +560,8 @@ export class InlineCheckout {
|
|
|
528
560
|
|
|
529
561
|
// Checkout router
|
|
530
562
|
const routerItems: StartCheckoutRequest = {
|
|
531
|
-
card:
|
|
532
|
-
name:
|
|
563
|
+
card: cardTokens,
|
|
564
|
+
name: cardTokens.cardholder_name,
|
|
533
565
|
last_name: "",
|
|
534
566
|
email_client: this.email,
|
|
535
567
|
phone_number: this.phone,
|
|
@@ -547,11 +579,15 @@ export class InlineCheckout {
|
|
|
547
579
|
business_id: business.pk,
|
|
548
580
|
payment_id: ("pk" in jsonResponsePayment) && jsonResponsePayment.pk,
|
|
549
581
|
source: 'sdk',
|
|
582
|
+
metadata: this.metadata,
|
|
583
|
+
browser_info: getBrowserInfo(),
|
|
584
|
+
currency: this.currency
|
|
550
585
|
};
|
|
551
586
|
|
|
552
587
|
const jsonResponseRouter = await this.liteCheckout.startCheckoutRouter(
|
|
553
588
|
routerItems
|
|
554
589
|
);
|
|
590
|
+
|
|
555
591
|
if (jsonResponseRouter) {
|
|
556
592
|
try {
|
|
557
593
|
const selector: any = document.querySelector(`#${this.collectorIds.tonderPayButton}`);
|
package/src/helpers/utils.ts
CHANGED
|
@@ -133,4 +133,17 @@ export const mapCards = (card: CardResponse) => {
|
|
|
133
133
|
const last = carArr[carArr.length - 1];
|
|
134
134
|
newCard.card_number = `••••${last}`;
|
|
135
135
|
return newCard;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export const getBrowserInfo = () => {
|
|
139
|
+
const browserInfo = {
|
|
140
|
+
javascript_enabled: true, // Assumed since JavaScript is running
|
|
141
|
+
time_zone: new Date().getTimezoneOffset(),
|
|
142
|
+
language: navigator.language || 'en-US', // Fallback to 'en-US'
|
|
143
|
+
color_depth: window.screen ? window.screen.colorDepth : null,
|
|
144
|
+
screen_width: window.screen ? window.screen.width * window.devicePixelRatio || window.screen.width : null,
|
|
145
|
+
screen_height: window.screen ? window.screen.height * window.devicePixelRatio || window.screen.height : null,
|
|
146
|
+
user_agent: navigator.userAgent,
|
|
147
|
+
};
|
|
148
|
+
return browserInfo;
|
|
136
149
|
}
|