tonder-web-sdk 1.9.3-beta.1 → 1.10.3

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 CHANGED
@@ -139,6 +139,7 @@ const checkoutData = {
139
139
  const apiKey = "4c87c36e697e65ddfe288be0afbe7967ea0ab865";
140
140
  const returnUrl = "http://my-website:8080/checkout"
141
141
  const successUrl = "http://my-website:8080/success"
142
+
142
143
  // if using script tag, it should be initialized like this
143
144
  // new TonderSdk.InlineCheckout
144
145
  const inlineCheckout = new InlineCheckout({
@@ -148,8 +149,22 @@ const inlineCheckout = new InlineCheckout({
148
149
  styles: customStyles
149
150
  });
150
151
 
152
+ // The configureCheckout function allows you to set initial information,
153
+ // such as the customer's email, which is used to retrieve a list of saved cards.
154
+ inlineCheckout.configureCheckout({customer: {email: "example@email.com"}});
155
+
156
+
151
157
  inlineCheckout.injectCheckout();
152
158
 
159
+ // To verify a 3ds transaction you can use the following method
160
+ // It should be called after the injectCheckout method
161
+ // The response status will be one of the following
162
+ // ['Declined', 'Cancelled', 'Failed', 'Success', 'Pending', 'Authorized']
163
+
164
+ inlineCheckout.verify3dsTransaction().then(response => {
165
+ console.log('Verify 3ds response', response)
166
+ })
167
+
153
168
  const response = await inlineCheckout.payment(checkoutData);
154
169
  ```
155
170
 
@@ -163,7 +178,7 @@ const response = await inlineCheckout.payment(checkoutData);
163
178
  | mode | string | 'stage' 'production' 'sandbox', default 'stage' |
164
179
  | apiKey | string | You can take this from you Tonder Dashboard |
165
180
  | backgroundColor | string | Hex color #000000 |
166
- | returnUrl | string | |
181
+ | returnUrl | string | url where the checkout form is mounted (3ds) |
167
182
  | successUrl | string | |
168
183
  | backgroundColor | string | |
169
184
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tonder-web-sdk",
3
- "version": "1.9.3-beta.1",
3
+ "version": "1.10.3",
4
4
  "description": "tonder sdk for integrations",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -39,6 +39,18 @@ export class ThreeDSHandler {
39
39
  }
40
40
  }
41
41
 
42
+ saveCheckoutId(checkoutId) {
43
+ localStorage.setItem('checkout_id', JSON.stringify(checkoutId))
44
+ }
45
+
46
+ removeCheckoutId() {
47
+ localStorage.removeItem("checkout_id")
48
+ }
49
+
50
+ getCurrentCheckoutId() {
51
+ return JSON.parse(localStorage.getItem("checkout_id"));
52
+ }
53
+
42
54
  getUrlWithExpiration() {
43
55
  const item = JSON.parse(localStorage.getItem("verify_transaction_status"))
44
56
  if (!item) return
@@ -117,17 +129,17 @@ export class ThreeDSHandler {
117
129
  return parameters;
118
130
  }
119
131
 
132
+ // TODO: Remove this duplication
120
133
  handleSuccessTransaction(response) {
121
134
  this.removeVerifyTransactionUrl();
122
- window.location = this.successUrl
123
- console.log('Transacción autorizada exitosamente.');
135
+ // window.location = this.successUrl
136
+ console.log('Transacción autorizada');
124
137
  return response;
125
138
  }
126
139
 
127
140
  handleDeclinedTransaction(response) {
128
141
  this.removeVerifyTransactionUrl();
129
- console.log('Transacción rechazada.');
130
- throw new Error("Transacción rechazada.");
142
+ return response;
131
143
  }
132
144
 
133
145
  // TODO: the method below needs to be tested with a real 3DS challenge
@@ -159,15 +171,18 @@ export class ThreeDSHandler {
159
171
  await this.verifyTransactionStatus();
160
172
  }
161
173
 
174
+ // TODO: This method could be removed
162
175
  async handleTransactionResponse(response) {
163
176
  const response_json = await response.json();
164
177
 
165
- if (response_json.status === "Pending") {
178
+ // Azul property
179
+ if (response_json.status === "Pending" && response_json.redirect_post_url) {
166
180
  return await this.handle3dsChallenge(response_json);
167
181
  } else if (["Success", "Authorized"].includes(response_json.status)) {
168
- return this.handleSuccessTransaction(response);
182
+ return this.handleSuccessTransaction(response_json);
169
183
  } else {
170
- return this.handleDeclinedTransaction(response);
184
+ this.handleDeclinedTransaction();
185
+ return response_json
171
186
  }
172
187
  }
173
188
 
@@ -185,19 +200,23 @@ export class ThreeDSHandler {
185
200
  },
186
201
  // body: JSON.stringify(data),
187
202
  });
188
-
189
203
  if (response.status !== 200) {
190
204
  console.error('La verificación de la transacción falló.');
191
- return
205
+ this.removeVerifyTransactionUrl();
206
+ return response
192
207
  }
193
208
 
194
209
  return await this.handleTransactionResponse(response);
195
210
  } catch (error) {
196
211
  console.error('Error al verificar la transacción:', error);
197
- return error;
212
+ this.removeVerifyTransactionUrl();
198
213
  }
199
214
  } else {
200
215
  console.log('No verify_transaction_status_url found');
201
216
  }
202
217
  }
218
+
219
+ setPayload = (payload) => {
220
+ this.payload = payload
221
+ }
203
222
  }
@@ -43,7 +43,6 @@ export class InlineCheckout {
43
43
  tonderPayButton: "tonderPayButton",
44
44
  msgError: "msgError",
45
45
  msgNotification: "msgNotification"
46
-
47
46
  }
48
47
 
49
48
  constructor({
@@ -78,7 +77,7 @@ export class InlineCheckout {
78
77
  'stage': 'https://stage.tonder.io',
79
78
  'development': 'http://localhost:8000',
80
79
  };
81
-
80
+
82
81
  return modeUrls[this.mode] || modeUrls['stage']
83
82
  }
84
83
 
@@ -112,6 +111,31 @@ export class InlineCheckout {
112
111
  }
113
112
  }
114
113
 
114
+ async handle3dsRedirect(response) {
115
+ const iframe = response?.next_action?.iframe_resources?.iframe
116
+
117
+ if (iframe) {
118
+ this.process3ds.loadIframe().then(() => {
119
+ //TODO: Check if this will be necessary on the frontend side
120
+ // after some the tests in production, since the 3DS process
121
+ // doesn't works properly on the sandbox environment
122
+ // setTimeout(() => {
123
+ // process3ds.verifyTransactionStatus();
124
+ // }, 10000);
125
+ this.process3ds.verifyTransactionStatus();
126
+ }).catch((error) => {
127
+ console.log('Error loading iframe:', error)
128
+ })
129
+ } else {
130
+ const redirectUrl = this.process3ds.getRedirectUrl()
131
+ if (redirectUrl) {
132
+ this.process3ds.redirectToChallenge()
133
+ } else {
134
+ return response;
135
+ }
136
+ }
137
+ }
138
+
115
139
  payment(data) {
116
140
  return new Promise(async (resolve, reject) => {
117
141
  try {
@@ -122,36 +146,12 @@ export class InlineCheckout {
122
146
  this.#handleCurrency(data)
123
147
  this.#handleCard(data)
124
148
  const response = await this.#checkout()
125
- if (response) {
126
- const process3ds = new ThreeDSHandler({
127
- baseUrl: this.baseUrl,
128
- apiKey: this.apiKeyTonder,
129
- payload: response,
130
- });
131
- this.callBack(response);
132
-
133
- const iframe = response?.next_action?.iframe_resources?.iframe
134
-
135
- if (iframe) {
136
- process3ds.loadIframe().then(() => {
137
- //TODO: Check if this will be necessary on the frontend side
138
- // after some the tests in production, since the 3DS process
139
- // doesn't works properly on the sandbox environment
140
- // setTimeout(() => {
141
- // process3ds.verifyTransactionStatus();
142
- // }, 10000);
143
- process3ds.verifyTransactionStatus();
144
- }).catch((error) => {
145
- console.log('Error loading iframe:', error)
146
- })
147
- } else {
148
- const redirectUrl = process3ds.getRedirectUrl()
149
- if (redirectUrl) {
150
- process3ds.redirectToChallenge()
151
- } else {
152
- resolve(response);
153
- }
154
- }
149
+ this.process3ds.setPayload(response)
150
+ this.process3ds.saveCheckoutId(response.checkout_id)
151
+ this.callBack(response);
152
+ const payload = await this.handle3dsRedirect(response)
153
+ if (payload) {
154
+ resolve(response);
155
155
  }
156
156
  } catch (error) {
157
157
  reject(error);
@@ -160,7 +160,6 @@ export class InlineCheckout {
160
160
  }
161
161
 
162
162
  #handleCustomer(customer) {
163
- console.log('customer: ', customer)
164
163
  if (!customer) return
165
164
 
166
165
  this.firstName = customer?.firstName
@@ -188,15 +187,15 @@ export class InlineCheckout {
188
187
  }
189
188
 
190
189
  setCartItems(items) {
191
- console.log('items: ', items)
192
190
  this.cartItems = items
193
191
  }
194
192
 
195
- setCustomerEmail (email) {
196
- this.email = email
193
+ configureCheckout(data) {
194
+ if ('customer' in data)
195
+ this.#handleCustomer(data['customer'])
197
196
  }
197
+
198
198
  setCartTotal(total) {
199
- console.log('total: ', total)
200
199
  this.cartTotal = total
201
200
  this.#updatePayButton()
202
201
  }
@@ -213,7 +212,6 @@ export class InlineCheckout {
213
212
 
214
213
  injectCheckout() {
215
214
  if (InlineCheckout.injected) return
216
- this.process3ds.verifyTransactionStatus()
217
215
  const containerTonderCheckout = document.querySelector("#tonder-checkout");
218
216
  if (containerTonderCheckout) {
219
217
  this.#mount(containerTonderCheckout)
@@ -230,15 +228,40 @@ export class InlineCheckout {
230
228
  childList: true,
231
229
  subtree: true,
232
230
  attributeFilter: ['id']
233
- });
231
+ });
234
232
  }
235
233
 
234
+ async verify3dsTransaction () {
235
+ const result3ds = await this.process3ds.verifyTransactionStatus()
236
+ const resultCheckout = await this.resumeCheckout(result3ds)
237
+ this.process3ds.setPayload(resultCheckout)
238
+ if (resultCheckout?.is_route_finished && resultCheckout?.provider === 'tonder') {
239
+ return resultCheckout
240
+ }
241
+ return this.handle3dsRedirect(resultCheckout)
242
+ }
243
+
244
+ async resumeCheckout(response) {
245
+ if (["Failed", "Declined", "Cancelled"].includes(response?.status)) {
246
+ const routerItems = {
247
+ // TODO: Replace this with reponse.checkout_id
248
+ checkout_id: this.process3ds.getCurrentCheckoutId(),
249
+ };
250
+ const routerResponse = await startCheckoutRouter(
251
+ this.baseUrl,
252
+ this.apiKeyTonder,
253
+ routerItems
254
+ );
255
+ return routerResponse
256
+ }
257
+ return response
258
+ }
236
259
 
237
260
  #addGlobalLoader() {
238
261
  let checkoutContainer = document.querySelector("#global-loader");
239
262
  if (checkoutContainer) {
240
- checkoutContainer.innerHTML = cardTemplateSkeleton;
241
- checkoutContainer.style.display = 'block';
263
+ checkoutContainer.innerHTML = cardTemplateSkeleton;
264
+ checkoutContainer.style.display = 'block';
242
265
  }
243
266
  }
244
267
 
@@ -249,7 +272,7 @@ export class InlineCheckout {
249
272
  }
250
273
  }
251
274
 
252
- #mount(containerTonderCheckout){
275
+ #mount(containerTonderCheckout) {
253
276
  containerTonderCheckout.innerHTML = cardTemplate;
254
277
  this.#addGlobalLoader();
255
278
  this.#mountTonder();
@@ -269,20 +292,21 @@ export class InlineCheckout {
269
292
  return await customerRegister(this.baseUrl, this.apiKeyTonder, customer, signal);
270
293
  }
271
294
 
272
- async #mountTonder(getCards = true) {
295
+ async #mountTonder(getCards = false) {
273
296
  this.#mountPayButton()
274
- try{
297
+ try {
275
298
  const {
276
299
  vault_id,
277
300
  vault_url,
278
301
  } = await this.#fetchMerchantData();
279
- if(this.email && getCards){
280
- const customerResponse = await this.getCustomer({email: this.email});
281
- if("auth_token" in customerResponse) {
302
+ console.log("this.email : ", this.email )
303
+ if (this.email && getCards) {
304
+ const customerResponse = await this.getCustomer({ email: this.email });
305
+ if ("auth_token" in customerResponse) {
282
306
  const { auth_token } = customerResponse
283
307
  const cards = await getCustomerCards(this.baseUrl, auth_token);
284
308
 
285
- if("cards" in cards) {
309
+ if ("cards" in cards) {
286
310
  const cardsMapped = cards.cards.map(mapCards)
287
311
  this.#loadCardsList(cardsMapped, auth_token)
288
312
  }
@@ -301,7 +325,7 @@ export class InlineCheckout {
301
325
  setTimeout(() => {
302
326
  this.#removeGlobalLoader()
303
327
  }, 800)
304
- }catch(e){
328
+ } catch (e) {
305
329
  if (e && e.name !== 'AbortError') {
306
330
  this.#removeGlobalLoader()
307
331
  showError("No se pudieron cargar los datos del comercio.")
@@ -342,9 +366,9 @@ export class InlineCheckout {
342
366
  const total = Number(this.cartTotal)
343
367
 
344
368
  let cardTokens = null;
345
- if(this.radioChecked === "new" || this.radioChecked === undefined){
369
+ if (this.radioChecked === "new" || this.radioChecked === undefined) {
346
370
  cardTokens = await this.#getCardTokens();
347
- }else{
371
+ } else {
348
372
  cardTokens = {
349
373
  skyflow_id: this.radioChecked
350
374
  }
@@ -360,24 +384,24 @@ export class InlineCheckout {
360
384
  }
361
385
 
362
386
  const { id, auth_token } = await this.getCustomer(
363
- this.customer,
387
+ this.customer,
364
388
  this.abortController.signal
365
389
  )
366
- if(auth_token && this.email){
390
+ if (auth_token && this.email) {
367
391
  const saveCard = document.getElementById("save-checkout-card");
368
- if(saveCard && "checked" in saveCard && saveCard.checked){
392
+ if (saveCard && "checked" in saveCard && saveCard.checked) {
369
393
  await registerCard(this.baseUrl, auth_token, { skyflow_id: cardTokens.skyflow_id });
370
-
394
+
371
395
  this.cardsInjected = false;
372
396
 
373
397
  const cards = await getCustomerCards(this.baseUrl, auth_token);
374
- if("cards" in cards) {
398
+ if ("cards" in cards) {
375
399
  const cardsMapped = cards.cards.map((card) => mapCards(card))
376
400
  this.#loadCardsList(cardsMapped, auth_token)
377
401
  }
378
402
 
379
403
  showMessage("Tarjeta registrada con éxito", this.collectorIds.msgNotification);
380
-
404
+
381
405
  }
382
406
  }
383
407
  var orderItems = {
@@ -391,7 +415,6 @@ export class InlineCheckout {
391
415
  is_oneclick: true,
392
416
  items: this.cartItems,
393
417
  };
394
- console.log('orderItems: ', orderItems)
395
418
  const jsonResponseOrder = await createOrder(
396
419
  this.baseUrl,
397
420
  this.apiKeyTonder,
@@ -462,8 +485,8 @@ export class InlineCheckout {
462
485
  }
463
486
  };
464
487
 
465
- #loadCardsList (cards, token) {
466
- if(this.cardsInjected) return;
488
+ #loadCardsList(cards, token) {
489
+ if (this.cardsInjected) return;
467
490
  const injectInterval = setInterval(() => {
468
491
  const queryElement = document.querySelector(`#${this.collectorIds.cardsListContainer}`);
469
492
  if (queryElement && InlineCheckout.injected) {
@@ -475,7 +498,7 @@ export class InlineCheckout {
475
498
  }, 500);
476
499
  }
477
500
 
478
- #mountRadioButtons (token) {
501
+ #mountRadioButtons(token) {
479
502
  const radioButtons = document.getElementsByName(`card_selected`);
480
503
  for (const radio of radioButtons) {
481
504
  radio.style.display = "block";
@@ -492,14 +515,14 @@ export class InlineCheckout {
492
515
  }
493
516
  }
494
517
 
495
- async #handleRadioButtonClick (radio) {
496
- if(radio.id === this.radioChecked || ( radio.id === "new" && this.radioChecked === undefined)) return;
518
+ async #handleRadioButtonClick(radio) {
519
+ if (radio.id === this.radioChecked || (radio.id === "new" && this.radioChecked === undefined)) return;
497
520
  const containerForm = document.querySelector(".container-form");
498
- if(containerForm) {
521
+ if (containerForm) {
499
522
  containerForm.style.display = radio.id === "new" ? "block" : "none";
500
523
  }
501
- if(radio.id === "new") {
502
- if(this.radioChecked !== radio.id) {
524
+ if (radio.id === "new") {
525
+ if (this.radioChecked !== radio.id) {
503
526
  this.#addGlobalLoader()
504
527
  this.#mountTonder(false);
505
528
  InlineCheckout.injected = true;
@@ -510,45 +533,45 @@ export class InlineCheckout {
510
533
  this.radioChecked = radio.id;
511
534
  }
512
535
 
513
- async #handleDeleteCardButtonClick (customerToken, button) {
536
+ async #handleDeleteCardButtonClick(customerToken, button) {
514
537
  const id = button.attributes.getNamedItem("id")
515
538
  const skyflow_id = id?.value?.split("_")?.[2]
516
- if(skyflow_id) {
539
+ if (skyflow_id) {
517
540
  const cardClicked = document.querySelector(`#card_container-${skyflow_id}`);
518
- if(cardClicked) {
541
+ if (cardClicked) {
519
542
  cardClicked.style.display = "none"
520
543
  }
521
- try{
522
- this.deletingCards.push(skyflow_id);
523
- if (this.abortRefreshCardsController) {
524
- this.abortRefreshCardsController.abort();
525
- this.abortRefreshCardsController = new AbortController();
544
+ try {
545
+ this.deletingCards.push(skyflow_id);
546
+ if (this.abortRefreshCardsController) {
547
+ this.abortRefreshCardsController.abort();
548
+ this.abortRefreshCardsController = new AbortController();
549
+ }
550
+ await deleteCustomerCard(this.baseUrl, customerToken, skyflow_id)
551
+ } catch {
552
+ } finally {
553
+ this.deletingCards = this.deletingCards.filter(id => id !== skyflow_id);
554
+ this.#refreshCardOnDelete(customerToken)
526
555
  }
527
- await deleteCustomerCard(this.baseUrl, customerToken, skyflow_id)
528
- }catch{
529
- } finally {
530
- this.deletingCards = this.deletingCards.filter(id => id !== skyflow_id);
531
- this.#refreshCardOnDelete(customerToken)
532
- }
533
556
  }
534
557
  }
535
- async #refreshCardOnDelete(customerToken){
536
- if(this.deletingCards.length > 0) return;
558
+ async #refreshCardOnDelete(customerToken) {
559
+ if (this.deletingCards.length > 0) return;
537
560
  this.cardsInjected = false
538
561
  const cards = await getCustomerCards(this.baseUrl, customerToken, "", this.abortRefreshCardsController.signal)
539
- if("cards" in cards) {
562
+ if ("cards" in cards) {
540
563
  const cardsMapped = cards.cards.map(mapCards)
541
564
  this.#loadCardsList(cardsMapped, customerToken)
542
565
  }
543
566
  }
544
- #unmountForm () {
567
+ #unmountForm() {
545
568
  InlineCheckout.injected = false
546
- if(this.collectContainer) {
547
- if("unmount" in this.collectContainer.elements.cardHolderNameElement) this.collectContainer.elements.cardHolderNameElement.unmount()
548
- if("unmount" in this.collectContainer.elements.cardNumberElement) this.collectContainer.elements.cardNumberElement.unmount()
549
- if("unmount" in this.collectContainer.elements.expiryYearElement) this.collectContainer.elements.expiryYearElement.unmount()
550
- if("unmount" in this.collectContainer.elements.expiryMonthElement) this.collectContainer.elements.expiryMonthElement.unmount()
551
- if("unmount" in this.collectContainer.elements.cvvElement) this.collectContainer.elements.cvvElement.unmount()
569
+ if (this.collectContainer) {
570
+ if ("unmount" in this.collectContainer.elements.cardHolderNameElement) this.collectContainer.elements.cardHolderNameElement.unmount()
571
+ if ("unmount" in this.collectContainer.elements.cardNumberElement) this.collectContainer.elements.cardNumberElement.unmount()
572
+ if ("unmount" in this.collectContainer.elements.expiryYearElement) this.collectContainer.elements.expiryYearElement.unmount()
573
+ if ("unmount" in this.collectContainer.elements.expiryMonthElement) this.collectContainer.elements.expiryMonthElement.unmount()
574
+ if ("unmount" in this.collectContainer.elements.cvvElement) this.collectContainer.elements.cvvElement.unmount()
552
575
  }
553
576
  }
554
577
  }
@@ -19,12 +19,6 @@ export const cardTemplate = `
19
19
  <div id="collectExpirationYear" class="expiration-year"></div>
20
20
  <div id="collectCvv" class="empty-div"></div>
21
21
  </div>
22
- <div class="checkbox">
23
- <input id="save-checkout-card" type="checkbox">
24
- <label for="save-checkout-card">
25
- Guardar tarjeta para futuros pagos
26
- </label>
27
- </div>
28
22
  <div id="msgError"></div>
29
23
  <div id="msgNotification"></div>
30
24
  </div>
package/src/index-dev.js CHANGED
@@ -97,21 +97,26 @@ const checkoutData = {
97
97
  };
98
98
 
99
99
  // localhost
100
- const apiKey = "4c87c36e697e65ddfe288be0afbe7967ea0ab865";
100
+ const apiKey = "11e3d3c3e95e0eaabbcae61ebad34ee5f93c3d27";
101
101
  const returnUrl = "http://127.0.0.1:8080/"
102
102
  const successUrl = "http://127.0.0.1:8080/success"
103
103
  // stage
104
104
  // const apiKey = "8365683bdc33dd6d50fe2397188d79f1a6765852";
105
105
 
106
106
  const inlineCheckout = new InlineCheckout({
107
- mode: 'development',
107
+ mode: 'stage',
108
108
  apiKey,
109
109
  returnUrl,
110
110
  successUrl,
111
111
  styles: customStyles
112
112
  });
113
- inlineCheckout.setCustomerEmail(checkoutData.customer.email)
113
+ inlineCheckout.configureCheckout({customer: checkoutData.customer})
114
114
  inlineCheckout.injectCheckout();
115
+ //
116
+ // ['Declined', 'Cancelled', 'Failed', 'Success', 'Pending', 'Authorized']
117
+ inlineCheckout.verify3dsTransaction().then(response => {
118
+ console.log('Verify 3ds response', response)
119
+ })
115
120
 
116
121
  document.addEventListener('DOMContentLoaded', function() {
117
122
  const payButton = document.getElementById('pay-button');