@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tonder.io/ionic-full-sdk",
3
- "version": "0.0.15-beta",
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.22-beta",
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
- localStorage.setItem("verify_transaction_status_url", url)
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
- redirectTo3DS() {
43
- const url = this.payload?.next_action?.redirect_to_url?.url
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
- return true
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.getVerifyTransactionUrl();
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 === 200) {
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 null;
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 (!process3ds.redirectTo3DS()) {
174
- resolve(response);
175
- } else {
176
- resolve(response);
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
- cardTokensSkyflowTonder = {
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: cardTokensSkyflowTonder.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: cardTokensSkyflowTonder,
532
- name: cardTokensSkyflowTonder.cardholder_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}`);
@@ -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
  }