tonder-web-sdk 1.15.2 → 1.16.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.
Files changed (46) hide show
  1. package/.husky/pre-commit +4 -0
  2. package/.prettierignore +8 -0
  3. package/.prettierrc +10 -0
  4. package/README.md +189 -35
  5. package/eslint.config.mjs +15 -0
  6. package/package.json +21 -4
  7. package/src/classes/3dsHandler.js +58 -62
  8. package/src/classes/BaseInlineCheckout.js +21 -36
  9. package/src/classes/LiteInlineCheckout.js +8 -8
  10. package/src/classes/checkout.js +75 -71
  11. package/src/classes/globalLoader.js +9 -7
  12. package/src/classes/inlineCheckout.js +528 -250
  13. package/src/data/apmApi.js +8 -14
  14. package/src/data/businessApi.js +5 -8
  15. package/src/data/cardApi.js +5 -14
  16. package/src/data/checkoutApi.js +54 -54
  17. package/src/data/customerApi.js +1 -6
  18. package/src/data/index.js +15 -15
  19. package/src/data/openPayApi.js +7 -7
  20. package/src/data/skyflowApi.js +14 -16
  21. package/src/helpers/skyflow.js +210 -119
  22. package/src/helpers/styles.js +56 -27
  23. package/src/helpers/template-skeleton.js +1 -1
  24. package/src/helpers/template.js +984 -541
  25. package/src/helpers/utils.js +152 -58
  26. package/src/helpers/validations.js +34 -35
  27. package/src/index-dev.js +38 -11
  28. package/src/index.html +20 -12
  29. package/src/index.js +19 -13
  30. package/src/shared/catalog/commonLogosCatalog.js +7 -0
  31. package/src/shared/catalog/paymentMethodsCatalog.js +242 -243
  32. package/src/shared/constants/colors.js +15 -0
  33. package/src/shared/constants/displayMode.js +4 -0
  34. package/src/shared/constants/fieldPathNames.js +4 -0
  35. package/src/shared/constants/htmlTonderIds.js +18 -0
  36. package/src/shared/constants/messages.js +10 -9
  37. package/types/card.d.ts +17 -17
  38. package/types/checkout.d.ts +85 -87
  39. package/types/common.d.ts +4 -1
  40. package/types/customer.d.ts +10 -10
  41. package/types/index.d.ts +9 -11
  42. package/types/inlineCheckout.d.ts +81 -61
  43. package/types/liteInlineCheckout.d.ts +78 -83
  44. package/types/paymentMethod.d.ts +17 -17
  45. package/types/transaction.d.ts +94 -94
  46. package/v1/bundle.min.js +3 -3
@@ -1,66 +1,98 @@
1
- import { apmItemsTemplate, cardItemsTemplate, cardTemplate } from '../helpers/template.js'
2
1
  import {
3
- clearSpace,
4
- injectMercadoPagoSecurity,
5
- mapCards,
6
- showError,
7
- showMessage
8
- } from '../helpers/utils';
9
- import { initSkyflow } from '../helpers/skyflow'
10
- import { globalLoader } from './globalLoader.js';
2
+ apmItemsTemplate,
3
+ cardItemsTemplate,
4
+ cardTemplate,
5
+ containerCheckoutTemplate,
6
+ } from "../helpers/template.js";
7
+ import { clearSpace, executeCallback, mapCards, showError, showMessage } from "../helpers/utils";
8
+ import { initSkyflow, initUpdateSkyflow } from "../helpers/skyflow";
9
+ import { globalLoader } from "./globalLoader.js";
11
10
  import { BaseInlineCheckout } from "./BaseInlineCheckout";
12
11
  import {
12
+ fetchCustomerAPMs,
13
13
  fetchCustomerCards,
14
14
  removeCustomerCard,
15
15
  saveCustomerCard,
16
- fetchCustomerAPMs
17
16
  } from "../data";
18
17
  import { MESSAGES } from "../shared/constants/messages";
18
+ import Accordion from "accordion-js";
19
+ import get from "lodash.get";
20
+ import { HTML_IDS } from "../shared/constants/htmlTonderIds";
21
+ import { DISPLAY_MODE } from "../shared/constants/displayMode";
19
22
 
20
23
  export class InlineCheckout extends BaseInlineCheckout {
21
24
  static injected = false;
22
- static cardsInjected = false
23
- static apmsInjected = false
24
- static apmsData = [];
25
+ static cardsInjected = false;
26
+ static apmsInjected = false;
27
+ #cardsData = [];
28
+ #paymentMethodsData = [];
29
+ #customerData = {};
30
+ accordionCards = null;
31
+ accordionPaymentMethods = null;
32
+
25
33
  deletingCards = [];
26
- customer = {}
27
- items = []
28
- collectContainer = null
29
- merchantData = {}
30
- cartTotal = null
31
- metadata = {}
32
- card = {}
34
+ customer = {};
35
+ items = [];
36
+ collectContainer = null;
37
+ updateCollectContainer = null;
38
+ merchantData = {};
39
+ cartTotal = null;
40
+ metadata = {};
41
+ card = {};
33
42
  collectorIds = {
34
- cardsListContainer: "cardsListContainer",
35
- holderName: "collectCardholderName",
36
- cardNumber: "collectCardNumber",
37
- expirationMonth: "collectExpirationMonth",
38
- expirationYear: "collectExpirationYear",
39
- cvv: "collectCvv",
40
- tonderPayButton: "tonderPayButton",
41
- msgError: "msgError",
42
- msgNotification: "msgNotification",
43
- apmsListContainer: "apmsListContainer"
44
- }
43
+ cardsListContainer: HTML_IDS.cardsListContainer,
44
+ holderName: HTML_IDS.collectCardholderName,
45
+ cardNumber: HTML_IDS.collectCardNumber,
46
+ expirationMonth: HTML_IDS.collectExpirationMonth,
47
+ expirationYear: HTML_IDS.collectExpirationYear,
48
+ cvv: HTML_IDS.collectCvv,
49
+ tonderPayButton: HTML_IDS.tonderPayButton,
50
+ tonderCancelButton: HTML_IDS.tonderCancelButton,
51
+ msgError: HTML_IDS.msgError,
52
+ msgNotification: HTML_IDS.msgNotification,
53
+ msgErrorText: HTML_IDS.msgErrorText,
54
+ msgNotificationText: HTML_IDS.msgNotificationText,
55
+ apmsListContainer: HTML_IDS.apmsListContainer,
56
+ };
45
57
  customization = {
58
+ displayMode: DISPLAY_MODE.light,
46
59
  saveCards: {
47
60
  showSaveCardOption: false,
48
61
  showSaved: false,
49
- autoSave: false
50
- }
51
- }
62
+ autoSave: false,
63
+ },
64
+ paymentButton: {
65
+ show: false,
66
+ text: "Pagar",
67
+ showAmount: true,
68
+ },
69
+ cancelButton: {
70
+ show: false,
71
+ text: "Cancelar",
72
+ },
73
+ paymentMethods: {
74
+ show: true,
75
+ },
76
+ cardForm: {
77
+ show: true,
78
+ },
79
+ showMessages: true,
80
+ };
81
+ callbacks = {
82
+ onCancel: () => {},
83
+ };
52
84
  constructor({
53
85
  mode = "stage",
54
86
  apiKey,
55
87
  returnUrl,
56
- renderPaymentButton = false,
57
- callBack = () => { },
88
+ callBack = () => {},
58
89
  styles,
59
90
  customization,
91
+ callbacks,
60
92
  }) {
61
93
  super({ mode, apiKey, returnUrl, callBack });
62
- this.renderPaymentButton = renderPaymentButton;
63
- this.customStyles = styles
94
+ this.customStyles = styles;
95
+ this.callbacks = { ...this.callbacks, ...(callbacks ? { ...callbacks } : {}) };
64
96
  this.abortRefreshCardsController = new AbortController();
65
97
  // TODO: Wait until SaveCards is ready (server token).
66
98
  this.customization = {
@@ -70,54 +102,21 @@ export class InlineCheckout extends BaseInlineCheckout {
70
102
  ...this.customization.saveCards,
71
103
  ...(customization?.saveCards || {}),
72
104
  },
73
- }
74
- }
75
-
76
- #mountPayButton() {
77
- if (!this.renderPaymentButton) return;
78
-
79
- const payButton = document.querySelector("#tonderPayButton");
80
- if (!payButton) {
81
- console.error("Pay button not found");
82
- return;
83
- }
84
-
85
- payButton.style.display = "block";
86
- payButton.textContent = `Pagar $${this.cartTotal}`;
87
- payButton.onclick = async (event) => {
88
- event.preventDefault();
89
- await this.#handlePaymentClick(payButton);
105
+ paymentButton: {
106
+ ...this.customization.paymentButton,
107
+ ...(customization?.paymentButton || {}),
108
+ },
109
+ paymentMethods: {
110
+ ...this.customization.paymentMethods,
111
+ ...(customization?.paymentMethods || {}),
112
+ },
113
+ cardForm: {
114
+ ...this.customization.cardForm,
115
+ ...(customization?.cardForm || {}),
116
+ },
90
117
  };
91
118
  }
92
119
 
93
- async #handlePaymentClick(payButton) {
94
- const prevButtonContent = payButton.innerHTML;
95
- payButton.innerHTML = `<div class="lds-dual-ring"></div>`;
96
- try {
97
- const response = await this.payment(this.customer);
98
- this.callBack(response);
99
- } catch (error) {
100
- console.error("Payment error:", error);
101
- } finally {
102
- payButton.innerHTML = prevButtonContent;
103
- }
104
- }
105
-
106
- _setCartTotal(total) {
107
- this.cartTotal = total
108
- this.#updatePayButton()
109
- }
110
-
111
- #updatePayButton() {
112
- const payButton = document.querySelector("#tonderPayButton");
113
- if (!payButton) return
114
- payButton.textContent = `Pagar $${this.cartTotal}`;
115
- }
116
-
117
- setCallback(cb) {
118
- this.cb = cb
119
- }
120
-
121
120
  /**
122
121
  * Injects the checkout into the DOM and initializes it.
123
122
  * Checks for an existing container and sets up an observer if needed.
@@ -125,81 +124,28 @@ export class InlineCheckout extends BaseInlineCheckout {
125
124
  * @public
126
125
  */
127
126
  async injectCheckout() {
128
- if (InlineCheckout.injected) return
129
- const containerTonderCheckout = document.querySelector("#tonder-checkout");
127
+ if (InlineCheckout.injected) return;
128
+ const containerTonderCheckout = document.querySelector(`#${HTML_IDS.tonderCheckout}`);
130
129
  if (containerTonderCheckout) {
131
- await this.#mount(containerTonderCheckout)
130
+ await this.#mount(containerTonderCheckout);
132
131
  return;
133
132
  }
134
133
  const observer = new MutationObserver(async (mutations, obs) => {
135
- const containerTonderCheckout = document.querySelector("#tonder-checkout");
134
+ const containerTonderCheckout = document.querySelector(`#${HTML_IDS.tonderCheckout}`);
136
135
  if (containerTonderCheckout) {
137
- await this.#mount(containerTonderCheckout)
136
+ await this.#mount(containerTonderCheckout);
138
137
  obs.disconnect();
139
138
  }
140
139
  });
141
140
  observer.observe(document.body, {
142
141
  childList: true,
143
142
  subtree: true,
144
- attributeFilter: ['id']
143
+ attributeFilter: ["id"],
145
144
  });
146
145
  }
147
146
 
148
- async #mount(containerTonderCheckout) {
149
- containerTonderCheckout.innerHTML = cardTemplate({ renderPaymentButton: this.renderPaymentButton, customStyles: this.customStyles, customization: this.customization });
150
- globalLoader.show()
151
- await this.#mountTonder();
152
- InlineCheckout.injected = true;
153
- }
154
-
155
- async #mountAPMs() {
156
- try {
157
- const apms = await fetchCustomerAPMs(this.baseUrl, this.apiKeyTonder);
158
- if (apms && apms['results'] && apms['results'].length > 0) {
159
- this.apmsData = apms['results']
160
- this.#loadAPMList(apms['results'])
161
- }
162
- } catch (e) {
163
- console.warn("Error getting APMS")
164
- }
165
- }
166
-
167
- async #mountTonder(getCards = true) {
168
- this.#mountPayButton()
169
- await this._initializeCheckout()
170
- try {
171
- const {
172
- vault_id,
173
- vault_url
174
- } = this.merchantData;
175
- if (this.email && getCards) {
176
- const customerResponse = await this._getCustomer({ email: this.email });
177
- if ("auth_token" in customerResponse) {
178
- const { auth_token } = customerResponse
179
- await this.#loadCardsList(auth_token)
180
- }
181
- }
182
-
183
- await this.#mountAPMs();
184
-
185
- this.collectContainer = await initSkyflow(
186
- vault_id,
187
- vault_url,
188
- this.baseUrl,
189
- this.apiKeyTonder,
190
- this.abortController.signal,
191
- this.customStyles,
192
- this.collectorIds
193
- );
194
- setTimeout(() => {
195
- globalLoader.remove()
196
- }, 800)
197
- } catch (e) {
198
- if (e && e.name !== 'AbortError') {
199
- globalLoader.remove()
200
- showError("No se pudieron cargar los datos del comercio.")
201
- }
202
- }
147
+ setCallback(cb) {
148
+ this.cb = cb;
203
149
  }
204
150
 
205
151
  /**
@@ -218,9 +164,9 @@ export class InlineCheckout extends BaseInlineCheckout {
218
164
  * @public
219
165
  */
220
166
  removeCheckout() {
221
- InlineCheckout.injected = false
222
- InlineCheckout.cardsInjected = false
223
- InlineCheckout.apmsInjected = false
167
+ InlineCheckout.injected = false;
168
+ InlineCheckout.cardsInjected = false;
169
+ InlineCheckout.apmsInjected = false;
224
170
  // Cancel all requests
225
171
  this.abortController.abort();
226
172
  this.abortController = new AbortController();
@@ -229,174 +175,399 @@ export class InlineCheckout extends BaseInlineCheckout {
229
175
  console.log("InlineCheckout removed from DOM and cleaned up.");
230
176
  }
231
177
 
232
- async #getCardTokens() {
233
- if (this.card?.skyflow_id) return this.card
178
+ async #mount(containerTonderCheckout) {
179
+ containerTonderCheckout.innerHTML = containerCheckoutTemplate({
180
+ customStyles: this.customStyles,
181
+ customization: this.customization,
182
+ });
183
+ globalLoader.show();
184
+ await this._initializeCheckout();
185
+ await this.#loadInitialData();
186
+ const currentContent = document.querySelector(`#${HTML_IDS.tonderContainer}`);
187
+ currentContent.innerHTML = `
188
+ ${currentContent.innerHTML}
189
+ ${cardTemplate({
190
+ customStyles: this.customStyles,
191
+ customization: this.customization,
192
+ cardsData: this.#cardsData,
193
+ paymentMethodsData: this.#paymentMethodsData,
194
+ collectorIds: this.collectorIds,
195
+ })}
196
+ `;
197
+ await this.#mountTonder();
198
+ InlineCheckout.injected = true;
199
+ }
200
+
201
+ async #mountTonder() {
234
202
  try {
235
- const collectResponse = await this.collectContainer.container.collect();
236
- const cardTokens = await collectResponse["records"][0]["fields"];
237
- return cardTokens;
238
- } catch (error) {
239
- showError("Por favor, verifica todos los campos de tu tarjeta")
240
- throw error;
203
+ const { vault_id, vault_url } = this.merchantData;
204
+
205
+ if (this.email && this.#cardsData.length > 0) {
206
+ await this.#loadCardsList();
207
+ }
208
+ this.#mountButtons();
209
+ await this.#loadAPMList();
210
+
211
+ this.collectContainer = await initSkyflow(
212
+ vault_id,
213
+ vault_url,
214
+ this.baseUrl,
215
+ this.apiKeyTonder,
216
+ this.abortController.signal,
217
+ this.customStyles,
218
+ this.collectorIds,
219
+ this.customization.displayMode,
220
+ );
221
+
222
+ setTimeout(() => {
223
+ globalLoader.remove();
224
+ }, 800);
225
+ } catch (e) {
226
+ if (e && e.name !== "AbortError") {
227
+ globalLoader.remove();
228
+ showError("No se pudieron cargar los datos del comercio.", this.radioChecked);
229
+ }
241
230
  }
242
231
  }
243
232
 
244
- async _checkout() {
233
+ #mountButtons(cardId = "") {
234
+ if (this.customization.paymentButton.show) {
235
+ this.#mountButton(this.collectorIds.tonderPayButton, cardId, this.#handlePaymentClick);
236
+ }
237
+ if (this.customization.cancelButton.show) {
238
+ this.#mountButton(this.collectorIds.tonderCancelButton, cardId, async () => {
239
+ await executeCallback({
240
+ callbacks: this.callbacks,
241
+ callback: "onCancel",
242
+ throwError: true,
243
+ });
244
+ });
245
+ }
246
+ const containerID = `#acContainer${cardId}`;
247
+ const container = document.querySelector(containerID);
248
+ document.querySelectorAll(".ac-option-panel-container").forEach(cont => {
249
+ cont.classList.remove("show");
250
+ });
251
+
252
+ if (container) {
253
+ container.classList.add("show");
254
+ }
255
+ }
256
+
257
+ #mountButton(buttonId = "", cardId = "", fn = () => {}) {
258
+ if (!this.customization.paymentButton.show) return;
259
+
260
+ const btnID = `#${buttonId}${cardId}`;
261
+ const findButton = document.querySelector(btnID);
262
+
263
+ if (!findButton) {
264
+ console.error(`${buttonId} not found`);
265
+ return;
266
+ }
267
+
268
+ this.#updateButton({
269
+ style: { display: "block" },
270
+ buttonId,
271
+ ...(cardId ? { cardId } : {}),
272
+ });
273
+
274
+ findButton.onclick = async event => {
275
+ event.preventDefault();
276
+ await fn();
277
+ };
278
+ }
279
+
280
+ #handlePaymentClick = async () => {
245
281
  try {
246
- document.querySelector("#tonderPayButton").disabled = true;
282
+ const response = await this.payment();
283
+ this.callBack(response);
247
284
  } catch (error) {
285
+ console.error("Payment error:", error);
248
286
  }
249
- const { business } = this.merchantData
250
- let cardTokens;
287
+ };
251
288
 
252
- if (this.radioChecked === "new" || this.radioChecked === undefined) {
253
- cardTokens = await this.#getCardTokens();
254
- } else {
255
- cardTokens = {
256
- skyflow_id: this.radioChecked
289
+ _setCartTotal(total) {
290
+ this.cartTotal = total;
291
+ this.#updateButton();
292
+ }
293
+
294
+ #updateButton(data) {
295
+ try {
296
+ const buttonDataId = data?.buttonId || this.collectorIds.tonderPayButton;
297
+ const btnID =
298
+ data?.cardId && data?.cardId !== "new"
299
+ ? `#${buttonDataId}${data.cardId}`
300
+ : `#${buttonDataId}`;
301
+ const textButton =
302
+ buttonDataId === this.collectorIds.tonderPayButton
303
+ ? `<div class="pay-button-text">${this.customization.paymentButton.text}${this.customization.paymentButton.showAmount ? ` $${this.cartTotal}` : ""}</div>`
304
+ : `<div class="cancel-button-text">${this.customization.cancelButton.text}</div>`;
305
+
306
+ const btnTextContent = data?.textContent || textButton;
307
+ const disabledBtn = data?.disabled;
308
+ const loadingHtml = data?.loading ? `<div class="spinner-tndr"></div>` : "";
309
+ const btnStyle = data?.style || {};
310
+ const payButton = document.querySelector(btnID);
311
+ if (!payButton) return;
312
+ if (loadingHtml !== "") {
313
+ payButton.innerHTML = loadingHtml;
314
+ } else {
315
+ payButton.innerHTML = btnTextContent;
316
+ }
317
+ if (btnStyle) {
318
+ Object.keys(btnStyle).forEach(btn => {
319
+ payButton.style[btn] = btnStyle[btn];
320
+ });
321
+ }
322
+ if (disabledBtn !== undefined && "disabled" in payButton) {
323
+ payButton.disabled = disabledBtn;
257
324
  }
325
+ } catch (e) {
326
+ console.error("Pay button not found due to update", e);
258
327
  }
328
+ }
329
+
330
+ async #getCardTokens(cardSelected) {
331
+ if (this.card?.skyflow_id) return this.card;
259
332
  try {
260
- const customerData = await this._getCustomer(
261
- this.customer,
262
- this.abortController.signal
263
- )
264
- const { auth_token } = customerData;
265
- if (auth_token && this.email) {
266
- await this.#handleSaveCard(auth_token, business.pk, cardTokens)
333
+ const collectResponse =
334
+ cardSelected && cardSelected !== "new"
335
+ ? await this.updateCollectContainer.container.collect()
336
+ : await this.collectContainer.container.collect();
337
+ return await collectResponse["records"][0]["fields"];
338
+ } catch (error) {
339
+ showError("Por favor, verifica todos los campos de tu tarjeta", this.radioChecked);
340
+ throw error;
341
+ }
342
+ }
343
+
344
+ async _checkout() {
345
+ this.#updateButton({
346
+ cardId: this.radioChecked,
347
+ loading: true,
348
+ disabled: true,
349
+ });
350
+ try {
351
+ const { business } = this.merchantData;
352
+ let cardTokens;
353
+ const selected_apm = this.#paymentMethodsData
354
+ ? this.#paymentMethodsData.find(iapm => iapm.pk === this.radioChecked)
355
+ : {};
356
+
357
+ if (this.radioChecked === "new" || this.radioChecked === undefined) {
358
+ cardTokens = await this.#getCardTokens(this.radioChecked);
359
+ } else {
360
+ if (!selected_apm) {
361
+ await this.#getCardTokens(this.radioChecked);
362
+ }
363
+ cardTokens = {
364
+ skyflow_id: this.radioChecked,
365
+ };
267
366
  }
268
367
 
269
- const selected_apm = this.apmsData ? this.apmsData.find((iapm) => iapm.pk === this.radioChecked) : {};
368
+ this.#customerData = await this._getCustomer(this.customer, this.abortController.signal);
369
+ if (this.email) {
370
+ await this.#handleSaveCard(business.pk, cardTokens);
371
+ }
270
372
 
271
373
  const jsonResponseRouter = await this._handleCheckout({
272
374
  ...(selected_apm && Object.keys(selected_apm).length > 0
273
375
  ? { payment_method: selected_apm.payment_method }
274
376
  : { card: cardTokens }),
275
- customer: customerData
377
+ customer: this.#customerData,
276
378
  });
277
379
 
278
380
  if (jsonResponseRouter) {
279
- try {
280
- document.querySelector("#tonderPayButton").disabled = false;
281
- } catch { }
282
381
  return jsonResponseRouter;
283
382
  } else {
284
- showError("No se ha podido procesar el pago")
383
+ showError("No se ha podido procesar el pago", this.radioChecked);
285
384
  return false;
286
385
  }
287
386
  } catch (error) {
288
- console.log(error);
289
- showError("Ha ocurrido un error")
387
+ console.log("Error payment", error);
388
+
389
+ showError("Ha ocurrido un error", this.radioChecked);
290
390
  throw error;
391
+ } finally {
392
+ this.#updateButton({ cardId: this.radioChecked, disabled: false });
291
393
  }
292
- };
394
+ }
293
395
 
294
- async #handleSaveCard(auth_token, businessId, cardTokens) {
396
+ async #handleSaveCard(businessId, cardTokens) {
397
+ if (!this.#customerData.auth_token) return;
295
398
  const saveCard = document.getElementById("save-checkout-card");
296
- if ((saveCard && "checked" in saveCard && saveCard.checked) || !!this.customization.saveCards?.autoSave) {
399
+ if (
400
+ (saveCard && "checked" in saveCard && saveCard.checked) ||
401
+ !!this.customization.saveCards?.autoSave
402
+ ) {
297
403
  try {
298
404
  await saveCustomerCard(
299
405
  this.baseUrl,
300
- auth_token,
406
+ this.#customerData.auth_token,
301
407
  this.secureToken,
302
408
  businessId,
303
- { skyflow_id: cardTokens.skyflow_id, }
409
+ {
410
+ skyflow_id: cardTokens.skyflow_id,
411
+ },
304
412
  );
305
- showMessage(MESSAGES.cardSaved, this.collectorIds.msgNotification);
413
+ showMessage(MESSAGES.cardSaved, this.radioChecked);
306
414
  } catch (error) {
307
415
  if (error?.message) {
308
- showError(error.message)
416
+ showError(error.message, this.radioChecked);
309
417
  }
310
418
  }
311
419
 
312
- await this.#loadCardsList(auth_token)
420
+ await this.#loadCardsList();
313
421
  }
314
422
  }
315
- async #loadCardsList(token) {
316
- if(this.cardsInjected || !this.customization.saveCards?.showSaved) return;
317
- this.cardsInjected = false
318
- const cardsResponse = await fetchCustomerCards(
319
- this.baseUrl,
320
- token,
321
- this.secureToken,
322
- this.merchantData.business.pk,
323
- );
324
- let cards = []
325
- if("cards" in cardsResponse) {
326
- cards = cardsResponse.cards.map(mapCards)
423
+
424
+ async #loadCardsList() {
425
+ try {
426
+ if (this.cardsInjected || !this.customization.saveCards?.showSaved) return;
427
+ this.cardsInjected = false;
428
+ let cards = [];
429
+ cards = this.#cardsData.map(mapCards);
327
430
  const injectInterval = setInterval(() => {
328
431
  const queryElement = document.querySelector(`#${this.collectorIds.cardsListContainer}`);
329
432
  if (queryElement && InlineCheckout.injected) {
330
- queryElement.innerHTML = cardItemsTemplate(cards)
331
- clearInterval(injectInterval)
332
- this.#mountRadioButtons(token)
333
- this.cardsInjected = true
433
+ queryElement.innerHTML = cardItemsTemplate({
434
+ cards: cards,
435
+ customization: this.customization,
436
+ collectorIds: this.collectorIds,
437
+ customStyles: this.customStyles,
438
+ });
439
+ clearInterval(injectInterval);
440
+ this.#generateAccordion();
441
+ this.#mountRadioButtons();
442
+ this.cardsInjected = true;
334
443
  }
335
444
  }, 500);
445
+ } catch (error) {
446
+ console.warn("Error mount Customer Cards", error);
336
447
  }
337
448
  }
338
449
 
339
- #loadAPMList(apms) {
340
- if (this.apmsInjected) return;
341
- const injectInterval = setInterval(() => {
342
- const queryElement = document.querySelector(`#${this.collectorIds.apmsListContainer}`);
343
- if (queryElement && InlineCheckout.injected) {
344
- const filteredAndSortedApms = apms
345
- .filter((apm) =>
346
- clearSpace(apm.category.toLowerCase()) !== 'cards' && apm.status.toLowerCase() === 'active')
347
- .sort((a, b) => a.priority - b.priority);
348
-
349
- queryElement.innerHTML = apmItemsTemplate(filteredAndSortedApms);
350
- clearInterval(injectInterval);
351
- this.#mountRadioButtons();
352
- this.apmsInjected = true;
353
- }
354
- }, 500);
450
+ #loadAPMList() {
451
+ try {
452
+ if (this.apmsInjected || !this.customization.paymentMethods?.show) return;
453
+ const injectInterval = setInterval(() => {
454
+ const queryElement = document.querySelector(`#${this.collectorIds.apmsListContainer}`);
455
+ if (queryElement && InlineCheckout.injected) {
456
+ const filteredAndSortedApms = this.#paymentMethodsData
457
+ .filter(
458
+ apm =>
459
+ clearSpace(apm.category.toLowerCase()) !== "cards" &&
460
+ apm.status.toLowerCase() === "active",
461
+ )
462
+ .sort((a, b) => a.priority - b.priority);
463
+ queryElement.innerHTML = apmItemsTemplate({
464
+ paymentMethods: filteredAndSortedApms,
465
+ customization: this.customization,
466
+ collectorIds: this.collectorIds,
467
+ });
468
+ clearInterval(injectInterval);
469
+ this.#generateAccordion("paymentMethods");
470
+ this.#mountRadioButtons();
471
+ this.apmsInjected = true;
472
+ }
473
+ }, 500);
474
+ } catch (error) {
475
+ console.warn("Error mount Payment Methods", error);
476
+ }
355
477
  }
356
478
 
357
- #mountRadioButtons(token = '') {
479
+ async #loadInitialData() {
480
+ try {
481
+ const canGetCards = this.email && this.customization.saveCards?.showSaved;
482
+ const pmResponsePromise = this.customization.paymentMethods?.show
483
+ ? fetchCustomerAPMs(this.baseUrl, this.apiKeyTonder)
484
+ : Promise.resolve(null);
485
+ const customerDataPromise = canGetCards
486
+ ? this._getCustomer({ email: this.email })
487
+ : Promise.resolve(null);
488
+
489
+ const [pmResponse, customerData] = await Promise.all([
490
+ pmResponsePromise,
491
+ customerDataPromise,
492
+ ]);
493
+
494
+ this.#paymentMethodsData = get(pmResponse, "results", []);
495
+ this.#customerData = customerData;
496
+
497
+ if (canGetCards && customerData && "auth_token" in customerData) {
498
+ const { auth_token } = customerData;
499
+ const cardsResponse = await fetchCustomerCards(
500
+ this.baseUrl,
501
+ auth_token,
502
+ this.secureToken,
503
+ this.merchantData.business.pk,
504
+ );
505
+ this.#cardsData = get(cardsResponse, "cards", []);
506
+ }
507
+ } catch (e) {
508
+ console.warn("Error loading initial data", e);
509
+ }
510
+ }
511
+ #mountRadioButtons() {
358
512
  const radioButtons = document.getElementsByName(`card_selected`);
359
513
  for (const radio of radioButtons) {
360
514
  radio.style.display = "block";
361
- radio.onclick = async (event) => {
362
- await this.#handleRadioButtonClick(radio);
515
+ radio.onclick = async event => {
516
+ const position = Array.from(radioButtons).indexOf(radio);
517
+ const classType = radio.classList[0];
518
+ await this.#handleRadioButtonClick(radio, position, classType);
363
519
  };
364
520
  }
365
521
  const cardsButtons = document.getElementsByClassName("card-delete-button");
366
522
  for (const cardButton of cardsButtons) {
367
- cardButton.addEventListener("click", (event) => {
368
- event.preventDefault();
369
- event.stopImmediatePropagation();
370
- this.#handleDeleteCardButtonClick(token, cardButton)
371
- }, false);
523
+ cardButton.addEventListener(
524
+ "click",
525
+ event => {
526
+ event.preventDefault();
527
+ event.stopImmediatePropagation();
528
+ this.#handleDeleteCardButtonClick(cardButton);
529
+ },
530
+ false,
531
+ );
372
532
  }
373
533
  }
374
534
 
375
- async #handleRadioButtonClick(radio) {
376
- if (radio.id === this.radioChecked || (radio.id === "new" && this.radioChecked === undefined)) return;
535
+ async #handleRadioButtonClick(radio, position = null, type = "") {
536
+ if (radio.id === this.radioChecked || (radio.id === "new" && this.radioChecked === undefined))
537
+ return;
377
538
  const containerForm = document.querySelector(".container-form");
378
539
  if (containerForm) {
379
540
  containerForm.style.display = radio.id === "new" ? "block" : "none";
380
541
  }
542
+
381
543
  if (radio.id === "new") {
544
+ this.#removeClass(["cvvContainer", "cvvContainerCard"]);
545
+ this.#handleOpenCloseAccordion("", null, true);
382
546
  if (this.radioChecked !== radio.id) {
383
- globalLoader.show()
384
- this.#mountTonder(false);
547
+ globalLoader.show();
548
+ await this.#mountTonder();
385
549
  InlineCheckout.injected = true;
386
550
  }
387
551
  } else {
552
+ this.#handleOpenCloseAccordion(type, null, false, true);
553
+ if (position !== null) {
554
+ this.#handleOpenCloseAccordion(type, position, true);
555
+ }
388
556
  this.#unmountForm();
389
557
  }
390
558
  this.radioChecked = radio.id;
391
559
  }
392
560
 
393
- async #handleDeleteCardButtonClick(customerToken, button) {
394
- const id = button.attributes.getNamedItem("id")
395
- const skyflow_id = id?.value?.split("_")?.[2]
561
+ async #handleDeleteCardButtonClick(button) {
562
+ if (!this.#customerData.auth_token) return;
563
+
564
+ const id = button.attributes.getNamedItem("id");
565
+ const skyflow_id = id?.value?.split("_")?.[2];
566
+
396
567
  if (skyflow_id) {
397
- const cardClicked = document.querySelector(`#card_container-${skyflow_id}`);
568
+ const cardClicked = document.querySelector(`#option_container-${skyflow_id}`);
398
569
  if (cardClicked) {
399
- cardClicked.style.display = "none"
570
+ cardClicked.style.display = "none";
400
571
  }
401
572
  try {
402
573
  this.deletingCards.push(skyflow_id);
@@ -404,32 +575,139 @@ export class InlineCheckout extends BaseInlineCheckout {
404
575
  this.abortRefreshCardsController.abort();
405
576
  this.abortRefreshCardsController = new AbortController();
406
577
  }
407
- const businessId = this.merchantData.business.pk
578
+ const businessId = this.merchantData.business.pk;
408
579
  await removeCustomerCard(
409
580
  this.baseUrl,
410
- customerToken,
581
+ this.#customerData.auth_token,
411
582
  this.secureToken,
412
583
  skyflow_id,
413
- businessId
414
- )
415
- } catch (error) { } finally {
584
+ businessId,
585
+ );
586
+ } catch (error) {
587
+ } finally {
416
588
  this.deletingCards = this.deletingCards.filter(id => id !== skyflow_id);
417
- this.#refreshCardOnDelete(customerToken)
589
+ this.#refreshCardOnDelete();
418
590
  }
419
591
  }
420
592
  }
421
- async #refreshCardOnDelete(customerToken) {
593
+
594
+ async #refreshCardOnDelete() {
422
595
  if (this.deletingCards.length > 0) return;
423
- await this.#loadCardsList(customerToken)
596
+ await this.#loadCardsList();
597
+ }
598
+
599
+ #generateAccordion(type = "cards") {
600
+ const accordionByType = {
601
+ cards: {
602
+ accClass: "accordion-container",
603
+ triggerClass: "card-item-label",
604
+ },
605
+ paymentMethods: {
606
+ accClass: "accordion-container-apm",
607
+ triggerClass: "apm-item-label",
608
+ },
609
+ };
610
+ const accordion = new Accordion("." + accordionByType[type].accClass, {
611
+ triggerClass: accordionByType[type].triggerClass,
612
+ duration: 300,
613
+ collapse: true,
614
+ showMultiple: false,
615
+ onOpen: async currentElement => {
616
+ await this.#handleOpenCardAccordion(currentElement, type);
617
+ },
618
+ });
619
+
620
+ if (type === "cards") {
621
+ this.accordionCards = accordion;
622
+ } else if (type === "paymentMethods") {
623
+ this.accordionPaymentMethods = accordion;
624
+ }
625
+ }
626
+
627
+ #removeClass(selectors = [], className = "show") {
628
+ selectors.forEach(slcItem => {
629
+ document.querySelectorAll("." + slcItem).forEach(container => {
630
+ container.classList.remove(className);
631
+ });
632
+ });
633
+ }
634
+
635
+ async #handleOpenCardAccordion(currentElement, type = "cards") {
636
+ const { vault_id, vault_url } = this.merchantData;
637
+ const container_radio_id = currentElement.id.replace("option_container-", "");
638
+
639
+ if (
640
+ this.updateCollectContainer &&
641
+ "unmount" in this.updateCollectContainer?.elements?.cvvElement
642
+ ) {
643
+ await this.updateCollectContainer.elements.cvvElement.unmount();
644
+ }
645
+ this.#removeClass(["cvvContainer", "cvvContainerCard"]);
646
+
647
+ const radio_card = document.getElementById(container_radio_id);
648
+ radio_card.checked = true;
649
+
650
+ try {
651
+ if (type === "cards") {
652
+ this.updateCollectContainer = await initUpdateSkyflow(
653
+ container_radio_id,
654
+ vault_id,
655
+ vault_url,
656
+ this.baseUrl,
657
+ this.apiKeyTonder,
658
+ this.abortController.signal,
659
+ this.customStyles,
660
+ this.customization.displayMode,
661
+ );
662
+ setTimeout(() => {
663
+ document.querySelector(`#cvvContainer${container_radio_id}`).classList.add("show");
664
+ }, 5);
665
+ }
666
+
667
+ this.#mountButtons(container_radio_id);
668
+ } catch (e) {
669
+ console.error("Ha ocurrido un error", e);
670
+ }
671
+ await this.#handleRadioButtonClick(radio_card, null, type);
424
672
  }
673
+
674
+ #handleOpenCloseAccordion(type = "", position = null, closeAll = false, closeOthers = false) {
675
+ const accordions = [
676
+ { type: "cards", accordion: this.accordionCards },
677
+ { type: "paymentMethods", accordion: this.accordionPaymentMethods },
678
+ ];
679
+ accordions.forEach(({ accordion, type: currentType }) => {
680
+ if (!accordion) return;
681
+
682
+ if (closeAll && accordion.closeAll) {
683
+ accordion.closeAll();
684
+ }
685
+
686
+ if (closeOthers && currentType !== type && accordion.closeAll) {
687
+ accordion.closeAll();
688
+ }
689
+
690
+ if (position !== null && currentType === type && accordion.open) {
691
+ accordion.open(
692
+ currentType !== "paymentMethods" ? position : position - (this.#cardsData.length + 1),
693
+ );
694
+ }
695
+ });
696
+ }
697
+
425
698
  #unmountForm() {
426
- InlineCheckout.injected = false
699
+ InlineCheckout.injected = false;
427
700
  if (this.collectContainer) {
428
- if ("unmount" in this.collectContainer.elements.cardHolderNameElement) this.collectContainer.elements.cardHolderNameElement.unmount()
429
- if ("unmount" in this.collectContainer.elements.cardNumberElement) this.collectContainer.elements.cardNumberElement.unmount()
430
- if ("unmount" in this.collectContainer.elements.expiryYearElement) this.collectContainer.elements.expiryYearElement.unmount()
431
- if ("unmount" in this.collectContainer.elements.expiryMonthElement) this.collectContainer.elements.expiryMonthElement.unmount()
432
- if ("unmount" in this.collectContainer.elements.cvvElement) this.collectContainer.elements.cvvElement.unmount()
701
+ if ("unmount" in this.collectContainer.elements.cardHolderNameElement)
702
+ this.collectContainer.elements.cardHolderNameElement.unmount();
703
+ if ("unmount" in this.collectContainer.elements.cardNumberElement)
704
+ this.collectContainer.elements.cardNumberElement.unmount();
705
+ if ("unmount" in this.collectContainer.elements.expiryYearElement)
706
+ this.collectContainer.elements.expiryYearElement.unmount();
707
+ if ("unmount" in this.collectContainer.elements.expiryMonthElement)
708
+ this.collectContainer.elements.expiryMonthElement.unmount();
709
+ if ("unmount" in this.collectContainer.elements.cvvElement)
710
+ this.collectContainer.elements.cvvElement.unmount();
433
711
  }
434
712
  }
435
713
  }