tonder-web-sdk 1.11.12 → 1.12.0-beta.1

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.
@@ -1,31 +1,22 @@
1
- import { apmItemsTemplate, cardItemsTemplate, cardTemplate } from '../helpers/template.js'
2
- import { cardTemplateSkeleton } from '../helpers/template-skeleton.js'
1
+ import {apmItemsTemplate, cardItemsTemplate, cardTemplate} from '../helpers/template.js'
3
2
  import {
4
- getBusiness,
5
- customerRegister,
6
- createOrder,
7
- createPayment,
8
- startCheckoutRouter,
9
- getOpenpayDeviceSessionID,
10
- getCustomerCards,
11
- registerCard,
12
- deleteCustomerCard,
13
- getCustomerAPMs
14
- } from '../data/api';
15
- import {
16
- showError,
17
- getBrowserInfo,
18
- mapCards,
19
- showMessage,
20
3
  clearSpace,
21
- injectMercadoPagoSecurity
4
+ injectMercadoPagoSecurity,
5
+ mapCards,
6
+ showError,
7
+ showMessage
22
8
  } from '../helpers/utils';
23
- import { initSkyflow } from '../helpers/skyflow'
24
- import { ThreeDSHandler } from './3dsHandler.js';
25
- import { globalLoader } from './globalLoader.js';
26
-
9
+ import {initSkyflow} from '../helpers/skyflow'
10
+ import {globalLoader} from './globalLoader.js';
11
+ import {BaseInlineCheckout} from "./BaseInlineCheckout";
12
+ import {
13
+ fetchCustomerCards,
14
+ removeCustomerCard,
15
+ saveCustomerCard,
16
+ fetchCustomerAPMs
17
+ } from "../data";
27
18
 
28
- export class InlineCheckout {
19
+ export class InlineCheckout extends BaseInlineCheckout{
29
20
  static injected = false;
30
21
  static cardsInjected = false
31
22
  static apmsInjected = false
@@ -33,7 +24,6 @@ export class InlineCheckout {
33
24
  deletingCards = [];
34
25
  customer = {}
35
26
  items = []
36
- baseUrl = null
37
27
  collectContainer = null
38
28
  merchantData = {}
39
29
  cartTotal = null
@@ -56,36 +46,14 @@ export class InlineCheckout {
56
46
  mode = "stage",
57
47
  apiKey,
58
48
  returnUrl,
59
- successUrl,
60
49
  renderPaymentButton = false,
61
50
  callBack = () => { },
62
51
  styles
63
52
  }) {
64
- this.apiKeyTonder = apiKey;
65
- this.returnUrl = returnUrl;
66
- this.successUrl = successUrl;
53
+ super({ mode, apiKey, returnUrl, callBack });
67
54
  this.renderPaymentButton = renderPaymentButton;
68
- this.callBack = callBack;
69
55
  this.customStyles = styles
70
- this.mode = mode
71
- this.baseUrl = this.#getBaseUrl()
72
-
73
- this.abortController = new AbortController()
74
56
  this.abortRefreshCardsController = new AbortController()
75
- this.process3ds = new ThreeDSHandler(
76
- { apiKey: apiKey, baseUrl: this.baseUrl, successUrl: successUrl }
77
- )
78
- }
79
-
80
- #getBaseUrl() {
81
- const modeUrls = {
82
- 'production': 'https://app.tonder.io',
83
- 'sandbox': 'https://sandbox.tonder.io',
84
- 'stage': 'https://stage.tonder.io',
85
- 'development': 'http://localhost:8000',
86
- };
87
-
88
- return modeUrls[this.mode] || modeUrls['stage']
89
57
  }
90
58
 
91
59
  #mountPayButton() {
@@ -118,90 +86,7 @@ export class InlineCheckout {
118
86
  }
119
87
  }
120
88
 
121
- async handle3dsRedirect(response) {
122
- const iframe = response?.next_action?.iframe_resources?.iframe
123
-
124
- if (iframe) {
125
- this.process3ds.loadIframe().then(() => {
126
- //TODO: Check if this will be necessary on the frontend side
127
- // after some the tests in production, since the 3DS process
128
- // doesn't works properly on the sandbox environment
129
- // setTimeout(() => {
130
- // process3ds.verifyTransactionStatus();
131
- // }, 10000);
132
- this.process3ds.verifyTransactionStatus();
133
- }).catch((error) => {
134
- console.log('Error loading iframe:', error)
135
- })
136
- } else {
137
- const redirectUrl = this.process3ds.getRedirectUrl()
138
- if (redirectUrl) {
139
- this.process3ds.redirectToChallenge()
140
- } else {
141
- return response;
142
- }
143
- }
144
- }
145
-
146
- payment(data) {
147
- return new Promise(async (resolve, reject) => {
148
- try {
149
- this.#handleCustomer(data.customer)
150
- this.setCartTotal(data.cart?.total)
151
- this.setCartItems(data.cart?.items)
152
- this.#handleMetadata(data)
153
- this.#handleCurrency(data)
154
- this.#handleCard(data)
155
- const response = await this.#checkout()
156
- this.process3ds.setPayload(response)
157
- this.callBack(response);
158
- const payload = await this.handle3dsRedirect(response)
159
- if (payload) {
160
- resolve(response);
161
- }
162
- } catch (error) {
163
- reject(error);
164
- }
165
- });
166
- }
167
-
168
- #handleCustomer(customer) {
169
- if (!customer) return
170
-
171
- this.firstName = customer?.firstName
172
- this.lastName = customer?.lastName
173
- this.country = customer?.country
174
- this.address = customer?.street
175
- this.city = customer?.city
176
- this.state = customer?.state
177
- this.postCode = customer?.postCode
178
- this.email = customer?.email
179
- this.phone = customer?.phone
180
- this.customer = customer
181
- }
182
-
183
- #handleMetadata(data) {
184
- this.metadata = data?.metadata
185
- }
186
-
187
- #handleCurrency(data) {
188
- this.currency = data?.currency
189
- }
190
-
191
- #handleCard(data) {
192
- this.card = data?.card
193
- }
194
-
195
- setCartItems(items) {
196
- this.cartItems = items
197
- }
198
-
199
- configureCheckout(data) {
200
- if ('customer' in data)
201
- this.#handleCustomer(data['customer'])
202
- }
203
-
204
- setCartTotal(total) {
89
+ _setCartTotal(total) {
205
90
  this.cartTotal = total
206
91
  this.#updatePayButton()
207
92
  }
@@ -217,6 +102,7 @@ export class InlineCheckout {
217
102
  }
218
103
 
219
104
  injectCheckout() {
105
+ console.log("HERE: 1", InlineCheckout.injected)
220
106
  if (InlineCheckout.injected) return
221
107
  const containerTonderCheckout = document.querySelector("#tonder-checkout");
222
108
  if (containerTonderCheckout) {
@@ -237,47 +123,6 @@ export class InlineCheckout {
237
123
  });
238
124
  }
239
125
 
240
- async verify3dsTransaction () {
241
- globalLoader.show()
242
- const result3ds = await this.process3ds.verifyTransactionStatus()
243
- const resultCheckout = await this.resumeCheckout(result3ds)
244
- this.process3ds.setPayload(resultCheckout)
245
- globalLoader.remove()
246
- return this.handle3dsRedirect(resultCheckout)
247
- }
248
-
249
-
250
- async resumeCheckout(response) {
251
- // Stop the routing process if the transaction is either hard declined or successful
252
- if (response?.decline?.error_type === "Hard") {
253
- return response
254
- }
255
-
256
- if (["Success", "Authorized"].includes(response?.transaction_status)) {
257
- return response;
258
- }
259
-
260
- if (response) {
261
- globalLoader.show()
262
- const routerItems = {
263
- checkout_id: response?.checkout?.id,
264
- };
265
- try {
266
- const routerResponse = await startCheckoutRouter(
267
- this.baseUrl,
268
- this.apiKeyTonder,
269
- routerItems
270
- );
271
- return routerResponse
272
- } catch (error) {
273
- // throw error
274
- } finally {
275
- globalLoader.remove()
276
- }
277
- return response
278
- }
279
- }
280
-
281
126
  #mount(containerTonderCheckout) {
282
127
  containerTonderCheckout.innerHTML = cardTemplate({renderPaymentButton: this.renderPaymentButton, customStyles: this.customStyles});
283
128
  globalLoader.show()
@@ -285,22 +130,9 @@ export class InlineCheckout {
285
130
  InlineCheckout.injected = true;
286
131
  }
287
132
 
288
- async #fetchMerchantData() {
289
- this.merchantData = await getBusiness(
290
- this.baseUrl,
291
- this.apiKeyTonder,
292
- this.abortController.signal
293
- );
294
- return this.merchantData
295
- }
296
-
297
- async getCustomer(customer, signal) {
298
- return await customerRegister(this.baseUrl, this.apiKeyTonder, customer, signal);
299
- }
300
-
301
133
  async #mountAPMs() {
302
134
  try{
303
- const apms = await getCustomerAPMs(this.baseUrl, this.apiKeyTonder, "?status=active&page_size=10000");
135
+ const apms = await fetchCustomerAPMs(this.baseUrl, this.apiKeyTonder);
304
136
  if(apms && apms['results'] && apms['results'].length > 0){
305
137
  this.apmsData = apms['results']
306
138
  this.#loadAPMList(apms['results'])
@@ -310,19 +142,23 @@ export class InlineCheckout {
310
142
  }
311
143
  }
312
144
 
313
- async #mountTonder(getCards = false) {
145
+ async #mountTonder(getCards = true) {
314
146
  this.#mountPayButton()
147
+ await this._initializeCheckout()
315
148
  try {
316
149
  const {
317
150
  vault_id,
318
- vault_url,
319
- mercado_pago
320
- } = await this.#fetchMerchantData();
151
+ vault_url
152
+ } = this.merchantData;
321
153
  if (this.email && getCards) {
322
- const customerResponse = await this.getCustomer({ email: this.email });
154
+ const customerResponse = await this._getCustomer({ email: this.email });
323
155
  if ("auth_token" in customerResponse) {
324
156
  const { auth_token } = customerResponse
325
- const cards = await getCustomerCards(this.baseUrl, auth_token);
157
+ const cards = await fetchCustomerCards(
158
+ this.baseUrl,
159
+ auth_token,
160
+ this.merchantData.business.pk
161
+ );
326
162
 
327
163
  if ("cards" in cards) {
328
164
  const cardsMapped = cards.cards.map(mapCards)
@@ -330,9 +166,6 @@ export class InlineCheckout {
330
166
  }
331
167
  }
332
168
  }
333
- if (!!mercado_pago && !!mercado_pago.active){
334
- injectMercadoPagoSecurity()
335
- }
336
169
 
337
170
  await this.#mountAPMs();
338
171
 
@@ -380,16 +213,14 @@ export class InlineCheckout {
380
213
  }
381
214
  }
382
215
 
383
- async #checkout() {
216
+ async _checkout() {
384
217
  try {
385
218
  document.querySelector("#tonderPayButton").disabled = true;
386
219
  } catch (error) {
387
220
  }
221
+ const { business } = this.merchantData
222
+ let cardTokens;
388
223
 
389
- const { openpay_keys, reference, business } = this.merchantData
390
- const total = Number(this.cartTotal)
391
-
392
- let cardTokens = null;
393
224
  if (this.radioChecked === "new" || this.radioChecked === undefined) {
394
225
  cardTokens = await this.#getCardTokens();
395
226
  } else {
@@ -398,107 +229,47 @@ export class InlineCheckout {
398
229
  }
399
230
  }
400
231
  try {
401
- let deviceSessionIdTonder;
402
- if (openpay_keys.merchant_id && openpay_keys.public_key) {
403
- deviceSessionIdTonder = await getOpenpayDeviceSessionID(
404
- openpay_keys.merchant_id,
405
- openpay_keys.public_key,
406
- this.abortController.signal
407
- );
408
- }
409
-
410
- const { id, auth_token } = await this.getCustomer(
232
+ const customerData = await this._getCustomer(
411
233
  this.customer,
412
234
  this.abortController.signal
413
235
  )
236
+ const { auth_token } = customerData;
414
237
  if (auth_token && this.email) {
415
238
  const saveCard = document.getElementById("save-checkout-card");
416
239
  if (saveCard && "checked" in saveCard && saveCard.checked) {
417
- await registerCard(this.baseUrl, auth_token, { skyflow_id: cardTokens.skyflow_id });
240
+ try {
241
+ await saveCustomerCard(this.baseUrl, auth_token, business.pk, {
242
+ skyflow_id: cardTokens.skyflow_id,
243
+ });
244
+ showMessage("Tarjeta registrada con éxito", this.collectorIds.msgNotification);
245
+ } catch (error) {
246
+ if (error?.message) {
247
+ showError(error.message)
248
+ }
249
+ }
418
250
 
419
251
  this.cardsInjected = false;
420
252
 
421
- const cards = await getCustomerCards(this.baseUrl, auth_token);
253
+ const cards = await fetchCustomerCards(
254
+ this.baseUrl,
255
+ auth_token,
256
+ this.merchantData.business.pk,
257
+ );
422
258
  if ("cards" in cards) {
423
259
  const cardsMapped = cards.cards.map((card) => mapCards(card))
424
260
  this.#loadCardsList(cardsMapped, auth_token)
425
261
  }
426
-
427
- showMessage("Tarjeta registrada con éxito", this.collectorIds.msgNotification);
428
-
429
262
  }
430
263
  }
431
- var orderItems = {
432
- business: this.apiKeyTonder,
433
- client: auth_token,
434
- billing_address_id: null,
435
- shipping_address_id: null,
436
- amount: total,
437
- status: "A",
438
- reference: reference,
439
- is_oneclick: true,
440
- items: this.cartItems,
441
- };
442
- const jsonResponseOrder = await createOrder(
443
- this.baseUrl,
444
- this.apiKeyTonder,
445
- orderItems
446
- );
447
-
448
- // Create payment
449
- const now = new Date();
450
- const dateString = now.toISOString();
451
-
452
- var paymentItems = {
453
- business_pk: business.pk,
454
- client_id: id,
455
- amount: total,
456
- date: dateString,
457
- order_id: jsonResponseOrder.id,
458
- };
459
- const jsonResponsePayment = await createPayment(
460
- this.baseUrl,
461
- this.apiKeyTonder,
462
- paymentItems
463
- );
464
264
 
465
265
  const selected_apm = this.apmsData ? this.apmsData.find((iapm) => iapm.pk === this.radioChecked):{};
466
-
467
- // Checkout router
468
- const routerItems = {
469
- name: this.firstName || "",
470
- last_name: this.lastName || "",
471
- email_client: this.email,
472
- phone_number: this.phone,
473
- return_url: this.returnUrl,
474
- id_product: "no_id",
475
- quantity_product: 1,
476
- id_ship: "0",
477
- instance_id_ship: "0",
478
- amount: total,
479
- title_ship: "shipping",
480
- description: "transaction",
481
- device_session_id: deviceSessionIdTonder ? deviceSessionIdTonder : null,
482
- token_id: "",
483
- order_id: jsonResponseOrder.id,
484
- business_id: business.pk,
485
- payment_id: jsonResponsePayment.pk,
486
- source: 'sdk',
487
- metadata: this.metadata,
488
- browser_info: getBrowserInfo(),
489
- currency: this.currency,
490
- ...( selected_apm && Object.keys(selected_apm).length > 0
491
- ? {payment_method: selected_apm.payment_method}
492
- : {card: cardTokens}
493
- ),
494
- ...(typeof MP_DEVICE_SESSION_ID !== "undefined" ? {mp_device_session_id: MP_DEVICE_SESSION_ID}:{})
495
- };
496
266
 
497
- const jsonResponseRouter = await startCheckoutRouter(
498
- this.baseUrl,
499
- this.apiKeyTonder,
500
- routerItems
501
- );
267
+ const jsonResponseRouter = await this._handleCheckout({
268
+ ...( selected_apm && Object.keys(selected_apm).length > 0
269
+ ? {payment_method: selected_apm.payment_method}
270
+ : {card: cardTokens}),
271
+ customer: customerData
272
+ });
502
273
 
503
274
  if (jsonResponseRouter) {
504
275
  try {
@@ -559,6 +330,7 @@ export class InlineCheckout {
559
330
  for (const cardButton of cardsButtons) {
560
331
  cardButton.addEventListener("click", (event) => {
561
332
  event.preventDefault();
333
+ event.stopImmediatePropagation();
562
334
  this.#handleDeleteCardButtonClick(token, cardButton)
563
335
  }, false);
564
336
  }
@@ -596,7 +368,8 @@ export class InlineCheckout {
596
368
  this.abortRefreshCardsController.abort();
597
369
  this.abortRefreshCardsController = new AbortController();
598
370
  }
599
- await deleteCustomerCard(this.baseUrl, customerToken, skyflow_id)
371
+ const businessId = this.merchantData.business.pk
372
+ await removeCustomerCard(this.baseUrl, customerToken, skyflow_id, businessId)
600
373
  } catch {
601
374
  } finally {
602
375
  this.deletingCards = this.deletingCards.filter(id => id !== skyflow_id);
@@ -607,7 +380,12 @@ export class InlineCheckout {
607
380
  async #refreshCardOnDelete(customerToken) {
608
381
  if (this.deletingCards.length > 0) return;
609
382
  this.cardsInjected = false
610
- const cards = await getCustomerCards(this.baseUrl, customerToken, "", this.abortRefreshCardsController.signal)
383
+ const cards = await fetchCustomerCards(
384
+ this.baseUrl,
385
+ customerToken,
386
+ this.merchantData.business.pk,
387
+ this.abortRefreshCardsController.signal
388
+ )
611
389
  if ("cards" in cards) {
612
390
  const cardsMapped = cards.cards.map(mapCards)
613
391
  this.#loadCardsList(cardsMapped, customerToken)
@@ -0,0 +1,44 @@
1
+ import {
2
+ buildErrorResponse,
3
+ buildErrorResponseFromCatch,
4
+ } from "../helpers/utils";
5
+
6
+ /**
7
+ * Fetches Alternative Payment Methods (APMs) of a customer.
8
+ * @param {string} baseUrl - The base URL of the API.
9
+ * @param {string} apiKey - The API key for authentication.
10
+ * @param params - The query params to filter APMs
11
+ * @param {AbortSignal} signal - The abort signal to cancel the request.
12
+ * @returns {Promise<Object>} The available APMs.
13
+ */
14
+ export async function fetchCustomerAPMs(
15
+ baseUrl,
16
+ apiKey,
17
+ params = {
18
+ status: "active",
19
+ pagesize: "10000",
20
+ },
21
+ signal = null,
22
+ ) {
23
+ try {
24
+ const queryString = new URLSearchParams(params).toString();
25
+
26
+ const response = await fetch(
27
+ `${baseUrl}/api/v1/payment_methods?${queryString}`,
28
+ {
29
+ method: "GET",
30
+ headers: {
31
+ Authorization: `Token ${apiKey}`,
32
+ "Content-Type": "application/json",
33
+ },
34
+ signal,
35
+ },
36
+ );
37
+
38
+ if (response.ok) return await response.json();
39
+ const res_json = await response.json();
40
+ throw await buildErrorResponse(response, res_json);
41
+ } catch (error) {
42
+ throw buildErrorResponseFromCatch(error);
43
+ }
44
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Fetches business information.
3
+ * @param {string} baseUrl - The base URL of the Tonder API.
4
+ * @param {string} apiKey - The API key for authentication.
5
+ * @param {AbortSignal} signal - The abort signal to cancel the request.
6
+ * @returns {Promise<Object>} The business information.
7
+ */
8
+ export async function fetchBusiness(baseUrl, apiKey, signal) {
9
+ const getBusiness = await fetch(
10
+ `${baseUrl}/api/v1/payments/business/${apiKey}`,
11
+ {
12
+ headers: {
13
+ Authorization: `Token ${apiKey}`,
14
+ },
15
+ signal: signal,
16
+ },
17
+ );
18
+ return await getBusiness.json();
19
+ }
@@ -0,0 +1,116 @@
1
+ import {
2
+ buildErrorResponse,
3
+ buildErrorResponseFromCatch,
4
+ } from "../helpers/utils";
5
+
6
+ /**
7
+ * Saves/Update a customer's card information.
8
+ * @param {string} baseUrl - The base URL of the API.
9
+ * @param {string} customerToken - The customer's authentication token.
10
+ * @param {string} businessId - The business ID.
11
+ * @param {Object} data - The card information to be saved.
12
+ * @returns {Promise<Object>} The saved card data.
13
+ */
14
+ export async function saveCustomerCard(
15
+ baseUrl,
16
+ customerToken,
17
+ businessId,
18
+ data,
19
+ ) {
20
+ try {
21
+ const url = `${baseUrl}/api/v1/business/${businessId}/cards/`;
22
+ const response = await fetch(url, {
23
+ method: "POST",
24
+ headers: {
25
+ Authorization: `Token ${customerToken}`,
26
+ "Content-Type": "application/json",
27
+ },
28
+ body: JSON.stringify(data),
29
+ });
30
+
31
+ if (response.ok) return await response.json();
32
+
33
+ const res_json = await response.json();
34
+
35
+ if (response.status === 409) {
36
+ if ((res_json.error = "Card number already exists.")) {
37
+ return {
38
+ code: 200,
39
+ body: res_json,
40
+ name: "",
41
+ message: res_json.error,
42
+ };
43
+ }
44
+ }
45
+ throw await buildErrorResponse(response, res_json);
46
+ } catch (error) {
47
+ throw buildErrorResponseFromCatch(error);
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Removes a customer's card.
53
+ * @param {string} baseUrl - The base URL of the API.
54
+ * @param {string} customerToken - The customer's authentication token.
55
+ * @param {string} skyflowId - The Skyflow ID of the card to be removed.
56
+ * @param {string} businessId - The business ID.
57
+ * @returns {Promise<Object>} The result of the card removal operation.
58
+ */
59
+ export async function removeCustomerCard(
60
+ baseUrl,
61
+ customerToken,
62
+ skyflowId = "",
63
+ businessId,
64
+ ) {
65
+ try {
66
+ const url = `${baseUrl}/api/v1/business/${businessId}/cards/${skyflowId}`;
67
+
68
+ const response = await fetch(url, {
69
+ method: "DELETE",
70
+ headers: {
71
+ Authorization: `Token ${customerToken}`,
72
+ "Content-Type": "application/json",
73
+ },
74
+ });
75
+
76
+ if (response.ok) return await response.json();
77
+ const res_json = await response.json();
78
+
79
+ throw await buildErrorResponse(response, res_json);
80
+ } catch (error) {
81
+ throw buildErrorResponseFromCatch(error);
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Fetches a customer's saved cards.
87
+ * @param {string} baseUrl - The base URL of the API.
88
+ * @param {string} customerToken - The customer's authentication token.
89
+ * @param {string} businessId - The business ID.
90
+ * @param {AbortSignal} signal - The abort signal to cancel the request.
91
+ * @returns {Promise<Object>} The customer's saved cards.
92
+ */
93
+ export async function fetchCustomerCards(
94
+ baseUrl,
95
+ customerToken,
96
+ businessId,
97
+ signal= null,
98
+ ) {
99
+ try {
100
+ const url = `${baseUrl}/api/v1/business/${businessId}/cards/`;
101
+ const response = await fetch(url, {
102
+ method: "GET",
103
+ headers: {
104
+ Authorization: `Token ${customerToken}`,
105
+ "Content-Type": "application/json",
106
+ },
107
+ signal,
108
+ });
109
+
110
+ if (response.ok) return await response.json();
111
+ const res_json = await response.json();
112
+ throw await buildErrorResponse(response, res_json);
113
+ } catch (error) {
114
+ throw buildErrorResponseFromCatch(error);
115
+ }
116
+ }