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

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
  }
@@ -237,47 +122,6 @@ export class InlineCheckout {
237
122
  });
238
123
  }
239
124
 
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
125
  #mount(containerTonderCheckout) {
282
126
  containerTonderCheckout.innerHTML = cardTemplate({renderPaymentButton: this.renderPaymentButton, customStyles: this.customStyles});
283
127
  globalLoader.show()
@@ -285,22 +129,9 @@ export class InlineCheckout {
285
129
  InlineCheckout.injected = true;
286
130
  }
287
131
 
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
132
  async #mountAPMs() {
302
133
  try{
303
- const apms = await getCustomerAPMs(this.baseUrl, this.apiKeyTonder, "?status=active&page_size=10000");
134
+ const apms = await fetchCustomerAPMs(this.baseUrl, this.apiKeyTonder);
304
135
  if(apms && apms['results'] && apms['results'].length > 0){
305
136
  this.apmsData = apms['results']
306
137
  this.#loadAPMList(apms['results'])
@@ -310,19 +141,23 @@ export class InlineCheckout {
310
141
  }
311
142
  }
312
143
 
313
- async #mountTonder(getCards = false) {
144
+ async #mountTonder(getCards = true) {
314
145
  this.#mountPayButton()
146
+ await this._initializeCheckout()
315
147
  try {
316
148
  const {
317
149
  vault_id,
318
- vault_url,
319
- mercado_pago
320
- } = await this.#fetchMerchantData();
150
+ vault_url
151
+ } = this.merchantData;
321
152
  if (this.email && getCards) {
322
- const customerResponse = await this.getCustomer({ email: this.email });
153
+ const customerResponse = await this._getCustomer({ email: this.email });
323
154
  if ("auth_token" in customerResponse) {
324
155
  const { auth_token } = customerResponse
325
- const cards = await getCustomerCards(this.baseUrl, auth_token);
156
+ const cards = await fetchCustomerCards(
157
+ this.baseUrl,
158
+ auth_token,
159
+ this.merchantData.business.pk
160
+ );
326
161
 
327
162
  if ("cards" in cards) {
328
163
  const cardsMapped = cards.cards.map(mapCards)
@@ -330,9 +165,6 @@ export class InlineCheckout {
330
165
  }
331
166
  }
332
167
  }
333
- if (!!mercado_pago && !!mercado_pago.active){
334
- injectMercadoPagoSecurity()
335
- }
336
168
 
337
169
  await this.#mountAPMs();
338
170
 
@@ -380,16 +212,14 @@ export class InlineCheckout {
380
212
  }
381
213
  }
382
214
 
383
- async #checkout() {
215
+ async _checkout() {
384
216
  try {
385
217
  document.querySelector("#tonderPayButton").disabled = true;
386
218
  } catch (error) {
387
219
  }
220
+ const { business } = this.merchantData
221
+ let cardTokens;
388
222
 
389
- const { openpay_keys, reference, business } = this.merchantData
390
- const total = Number(this.cartTotal)
391
-
392
- let cardTokens = null;
393
223
  if (this.radioChecked === "new" || this.radioChecked === undefined) {
394
224
  cardTokens = await this.#getCardTokens();
395
225
  } else {
@@ -398,107 +228,47 @@ export class InlineCheckout {
398
228
  }
399
229
  }
400
230
  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(
231
+ const customerData = await this._getCustomer(
411
232
  this.customer,
412
233
  this.abortController.signal
413
234
  )
235
+ const { auth_token } = customerData;
414
236
  if (auth_token && this.email) {
415
237
  const saveCard = document.getElementById("save-checkout-card");
416
238
  if (saveCard && "checked" in saveCard && saveCard.checked) {
417
- await registerCard(this.baseUrl, auth_token, { skyflow_id: cardTokens.skyflow_id });
239
+ try {
240
+ await saveCustomerCard(this.baseUrl, auth_token, business.pk, {
241
+ skyflow_id: cardTokens.skyflow_id,
242
+ });
243
+ showMessage("Tarjeta registrada con éxito", this.collectorIds.msgNotification);
244
+ } catch (error) {
245
+ if (error?.message) {
246
+ showError(error.message)
247
+ }
248
+ }
418
249
 
419
250
  this.cardsInjected = false;
420
251
 
421
- const cards = await getCustomerCards(this.baseUrl, auth_token);
252
+ const cards = await fetchCustomerCards(
253
+ this.baseUrl,
254
+ auth_token,
255
+ this.merchantData.business.pk,
256
+ );
422
257
  if ("cards" in cards) {
423
258
  const cardsMapped = cards.cards.map((card) => mapCards(card))
424
259
  this.#loadCardsList(cardsMapped, auth_token)
425
260
  }
426
-
427
- showMessage("Tarjeta registrada con éxito", this.collectorIds.msgNotification);
428
-
429
261
  }
430
262
  }
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
263
 
465
264
  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
265
 
497
- const jsonResponseRouter = await startCheckoutRouter(
498
- this.baseUrl,
499
- this.apiKeyTonder,
500
- routerItems
501
- );
266
+ const jsonResponseRouter = await this._handleCheckout({
267
+ ...( selected_apm && Object.keys(selected_apm).length > 0
268
+ ? {payment_method: selected_apm.payment_method}
269
+ : {card: cardTokens}),
270
+ customer: customerData
271
+ });
502
272
 
503
273
  if (jsonResponseRouter) {
504
274
  try {
@@ -559,6 +329,7 @@ export class InlineCheckout {
559
329
  for (const cardButton of cardsButtons) {
560
330
  cardButton.addEventListener("click", (event) => {
561
331
  event.preventDefault();
332
+ event.stopImmediatePropagation();
562
333
  this.#handleDeleteCardButtonClick(token, cardButton)
563
334
  }, false);
564
335
  }
@@ -596,7 +367,8 @@ export class InlineCheckout {
596
367
  this.abortRefreshCardsController.abort();
597
368
  this.abortRefreshCardsController = new AbortController();
598
369
  }
599
- await deleteCustomerCard(this.baseUrl, customerToken, skyflow_id)
370
+ const businessId = this.merchantData.business.pk
371
+ await removeCustomerCard(this.baseUrl, customerToken, skyflow_id, businessId)
600
372
  } catch {
601
373
  } finally {
602
374
  this.deletingCards = this.deletingCards.filter(id => id !== skyflow_id);
@@ -607,7 +379,12 @@ export class InlineCheckout {
607
379
  async #refreshCardOnDelete(customerToken) {
608
380
  if (this.deletingCards.length > 0) return;
609
381
  this.cardsInjected = false
610
- const cards = await getCustomerCards(this.baseUrl, customerToken, "", this.abortRefreshCardsController.signal)
382
+ const cards = await fetchCustomerCards(
383
+ this.baseUrl,
384
+ customerToken,
385
+ this.merchantData.business.pk,
386
+ this.abortRefreshCardsController.signal
387
+ )
611
388
  if ("cards" in cards) {
612
389
  const cardsMapped = cards.cards.map(mapCards)
613
390
  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
+ }