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