tonder-web-sdk 1.15.2 → 1.16.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.
package/README.md CHANGED
@@ -7,7 +7,7 @@ Tonder SDK helps to integrate the services Tonder offers in your own website
7
7
  1. [Installation](#installation)
8
8
  2. [Usage](#usage)
9
9
  - [InlineCheckout](#inlinecheckout)
10
- - [LiteCheckout](#litecheckout)
10
+ - [LiteInlineCheckout](#Liteinlinecheckout)
11
11
  3. [Configuration Options](#configuration-options)
12
12
  4. [Styling InlineCheckout](#styling-inlinecheckout)
13
13
  5. [Payment Data Structure](#payment-data-structure)
@@ -96,16 +96,16 @@ inlineCheckout.verify3dsTransaction().then((response) => {
96
96
  const response = await inlineCheckout.payment(checkoutData);
97
97
  ```
98
98
 
99
- ### LiteCheckout
99
+ ### LiteInlineCheckout
100
100
 
101
- LiteCheckout allows you to build a custom checkout interface using Tonder's core functionality.
101
+ LiteInlineCheckout allows you to build a custom checkout interface using Tonder's core functionality.
102
102
 
103
103
  ```javascript
104
- import { LiteCheckout } from "tonder-web-sdk";
104
+ import { LiteInlineCheckout } from "tonder-web-sdk";
105
105
  ```
106
106
 
107
107
  ```javascript
108
- const liteCheckout = new LiteCheckout({
108
+ const LiteInlineCheckout = new LiteInlineCheckout({
109
109
  apiKey: "your-api-key", // Your api key getted from Tonder Dashboard
110
110
  returnUrl: "http://your-website.com/checkout",
111
111
  });
@@ -115,54 +115,58 @@ const liteCheckout = new LiteCheckout({
115
115
  inlineCheckout.configureCheckout({ customer: { email: "example@email.com" } });
116
116
 
117
117
  // Initialize the checkout
118
- await liteCheckout.injectCheckout();
118
+ await LiteInlineCheckout.injectCheckout();
119
119
  ```
120
120
 
121
121
  ```javascript
122
122
  // Retrieve customer's saved cards
123
- const cards = await liteCheckout.getCustomerCards();
123
+ const cards = await LiteInlineCheckout.getCustomerCards();
124
124
  ```
125
125
 
126
126
  ```javascript
127
127
  // Save a new card
128
- const newCard = await liteCheckout.saveCustomerCard(cardData);
128
+ const newCard = await LiteInlineCheckout.saveCustomerCard(cardData);
129
129
  ```
130
130
 
131
131
  ```javascript
132
132
  // Remove a saved card
133
- await liteCheckout.removeCustomerCard(cardId);
133
+ await LiteInlineCheckout.removeCustomerCard(cardId);
134
134
  ```
135
135
 
136
136
  ```javascript
137
137
  // Get available payment methods
138
- const paymentMethods = await liteCheckout.getCustomerPaymentMethods();
138
+ const paymentMethods = await LiteInlineCheckout.getCustomerPaymentMethods();
139
139
  ```
140
140
 
141
141
  ```javascript
142
142
  // Process a payment
143
- const paymentResponse = await liteCheckout.payment(paymentData);
143
+ const paymentResponse = await LiteInlineCheckout.payment(paymentData);
144
144
  ```
145
145
 
146
146
  ```javascript
147
147
  // Verify a 3DS transaction
148
- const verificationResult = await liteCheckout.verify3dsTransaction();
148
+ const verificationResult = await LiteInlineCheckout.verify3dsTransaction();
149
149
  ```
150
150
 
151
151
  ## Configuration Options
152
152
 
153
- Both InlineCheckout and LiteCheckout accept the following configuration options:
153
+ Both InlineCheckout and LiteInlineCheckout accept the following configuration options:
154
154
 
155
155
  | Property | Type | Description |
156
- | :----------------------------------------: | :-----: | :-----------------------------------------------------------------------------------------------------------------------------------------: |
156
+ |:------------------------------------------:|:-------:|:-------------------------------------------------------------------------------------------------------------------------------------------:|
157
157
  | mode | string | Environment mode. Options: 'stage', 'production', 'sandbox'. Default: 'stage' |
158
158
  | apiKey | string | Your API key from the Tonder Dashboard |
159
159
  | returnUrl | string | URL where the checkout form is mounted (used for 3DS) |
160
+ | renderPaymentButton | boolean | View the default Tonder payment button |
160
161
  | styles | object | (InlineCheckout only) Custom styles for the checkout interface |
161
162
  | customization | object | Object to customize the checkout behavior and UI. Default value `{saveCards: {showSaved: true, showSaveCardOption: true, autoSave: false}}` |
162
163
  | customization.saveCards.showSaved | boolean | Show saved cards in the checkout UI. Default value: `true` |
163
164
  | customization.saveCards.showSaveCardOption | object | Show the option to save the card for future payments. Default value: `true` |
164
165
  | customization.saveCards.autoSave | object | Automatically save the card without showing the option to the user. Default value: `false` |
165
166
 
167
+ > **Important Note about SaveCard functionality**:
168
+ > To properly implement the SaveCard feature, you must use a SecureToken. For detailed implementation instructions and best practices, please refer to our official documentation on [How to use SecureToken for secure card saving](https://docs.tonder.io/integration/sdks/secure-token#how-to-use-securetoken-for-secure-card-saving).
169
+
166
170
  ## Styling InlineCheckout
167
171
 
168
172
  You can customize the appearance of InlineCheckout by passing a `styles` object:
@@ -252,15 +256,15 @@ When calling the `payment` method, use the following data structure:
252
256
 
253
257
  - **metadata**: Object for including any additional information about the transaction. This can be used for internal references or tracking.
254
258
 
255
- - **card**: (for LiteCheckout) Object containing card information. This is used differently depending on whether it's a new card or a saved card:
259
+ - **card**: (for LiteInlineCheckout) Object containing card information. This is used differently depending on whether it's a new card or a saved card:
256
260
 
257
261
  - For a new card: Include `card_number`, `cvv`, `expiration_month`, `expiration_year`, and `cardholder_name`.
258
262
  - For a saved card: Include only the `skyflow_id` of the saved card.
259
263
  - This is only used when not paying with a payment_method.
260
264
 
261
- - **payment_method**: (for LiteCheckout) String indicating the alternative payment method to be used (e.g., "Spei"). This is only used when not paying with a card.
265
+ - **payment_method**: (for LiteInlineCheckout) String indicating the alternative payment method to be used (e.g., "Spei"). This is only used when not paying with a card.
262
266
 
263
- Note: The exact fields required may vary depending on whether you're using InlineCheckout or LiteCheckout, and the specific payment method being used.
267
+ Note: The exact fields required may vary depending on whether you're using InlineCheckout or LiteInlineCheckout, and the specific payment method being used.
264
268
 
265
269
  ```javascript
266
270
  const paymentData = {
@@ -312,7 +316,7 @@ const paymentData = {
312
316
 
313
317
  ## Field Validation Functions
314
318
 
315
- For LiteCheckout implementations, the SDK provides validation functions to ensure the integrity of card data before submitting:
319
+ For LiteInlineCheckout implementations, the SDK provides validation functions to ensure the integrity of card data before submitting:
316
320
 
317
321
  - `validateCardNumber(cardNumber)`: Validates the card number using the Luhn algorithm.
318
322
  - `validateCardholderName(name)`: Checks if the cardholder name is valid.
@@ -357,7 +361,7 @@ if (
357
361
  - `payment(data)`: Process a payment
358
362
  - `verify3dsTransaction()`: Verify a 3DS transaction
359
363
 
360
- ### LiteCheckout Methods
364
+ ### LiteInlineCheckout Methods
361
365
 
362
366
  - `configureCheckout(data)`: Set initial checkout data
363
367
  - `injectCheckout()`: Initialize the checkout
@@ -434,6 +438,59 @@ document
434
438
  });
435
439
  ```
436
440
 
441
+
442
+ #### InlineCheckout with default Tonder Payment button Example (your-script.js)
443
+ > 💡 **Note:** It is important to send all payment data (customer, cart, metadata, etc) when configuring the checkout; this is necessary when using Tonder's default payment button.
444
+
445
+ ```javascript
446
+ import { InlineCheckout } from "tonder-web-sdk";
447
+
448
+ const apiKey = "your-api-key";
449
+ const returnUrl = "http://your-website.com/checkout";
450
+
451
+ const inlineCheckout = new InlineCheckout({
452
+ mode: "development",
453
+ apiKey,
454
+ returnUrl,
455
+ styles: customStyles,
456
+ renderPaymentButton: true, // activate default Tonder Payment button
457
+ callBack: (response) => {
458
+ console.log('Payment response', response)
459
+ }
460
+ });
461
+
462
+ // It is important to send all payment data (customer, cart, metadata, etc) when configuring the checkout; this is necessary when using Tonder's default payment button.
463
+ inlineCheckout.configureCheckout(
464
+ {
465
+ customer: { email: "example@email.com" },
466
+ currency: "mxn",
467
+ cart: {
468
+ total: 399,
469
+ items: [
470
+ {
471
+ description: "Black T-Shirt",
472
+ quantity: 1,
473
+ price_unit: 1,
474
+ discount: 0,
475
+ taxes: 0,
476
+ product_reference: 1,
477
+ name: "T-Shirt",
478
+ amount_total: 399,
479
+ },
480
+ ],
481
+ },
482
+ metadata: {}, // Optional
483
+ order_reference: "" // Optional
484
+ }
485
+ );
486
+ inlineCheckout.injectCheckout();
487
+
488
+ inlineCheckout.verify3dsTransaction().then((response) => {
489
+ console.log("Verify 3ds response", response);
490
+ });
491
+ ```
492
+
493
+
437
494
  #### LiteCheckout Example (your-script.js)
438
495
 
439
496
  ```javascript
@@ -442,16 +499,16 @@ import { LiteInlineCheckout } from "tonder-web-sdk";
442
499
  const apiKey = "your-api-key";
443
500
  const returnUrl = "http://your-website.com/checkout";
444
501
 
445
- const liteCheckout = new LiteInlineCheckout({
502
+ const LiteInlineCheckout = new LiteInlineCheckout({
446
503
  mode: "development",
447
504
  apiKey,
448
505
  returnUrl,
449
506
  });
450
507
 
451
- liteCheckout.configureCheckout({ customer: { email: "example@email.com" } });
452
- liteCheckout.injectCheckout();
508
+ LiteInlineCheckout.configureCheckout({ customer: { email: "example@email.com" } });
509
+ LiteInlineCheckout.injectCheckout();
453
510
 
454
- liteCheckout.verify3dsTransaction().then((response) => {
511
+ LiteInlineCheckout.verify3dsTransaction().then((response) => {
455
512
  console.log("Verify 3ds response", response);
456
513
  });
457
514
 
@@ -469,7 +526,7 @@ document
469
526
 
470
527
  try {
471
528
  const paymentData = { ...checkoutData, card: cardData };
472
- const response = await liteCheckout.payment(paymentData);
529
+ const response = await LiteInlineCheckout.payment(paymentData);
473
530
  console.log("Payment response:", response);
474
531
  alert("Payment successful");
475
532
  } catch (error) {
@@ -681,7 +738,7 @@ export class TonderService {
681
738
  // Add more functions, for example for lite sdk: get payment methods
682
739
 
683
740
  // getCustomerPaymentMethods(): Promise<IPaymentMethod[]> {
684
- // return this.liteCheckout.getCustomerPaymentMethods();
741
+ // return this.LiteInlineCheckout.getCustomerPaymentMethods();
685
742
  // }
686
743
  }
687
744
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tonder-web-sdk",
3
- "version": "1.15.2",
3
+ "version": "1.16.1",
4
4
  "description": "tonder sdk for integrations",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -11,6 +11,7 @@
11
11
  "types": "types/index.d.ts",
12
12
  "license": "ISC",
13
13
  "dependencies": {
14
+ "accordion-js": "^3.4.0",
14
15
  "crypto-js": "^4.1.1",
15
16
  "dotenv": "^16.3.1"
16
17
  },
@@ -13,6 +13,7 @@ import { getBrowserInfo, injectMercadoPagoSecurity } from "../helpers/utils";
13
13
  export class BaseInlineCheckout {
14
14
  baseUrl = "";
15
15
  cartTotal = "0";
16
+ secureToken = "";
16
17
  constructor({ mode = "stage", apiKey, returnUrl, callBack = () => {} }) {
17
18
  this.apiKeyTonder = apiKey;
18
19
  this.returnUrl = returnUrl;
@@ -33,8 +34,8 @@ export class BaseInlineCheckout {
33
34
  * @public
34
35
  */
35
36
  configureCheckout(data) {
36
- if ("customer" in data) this.#handleCustomer(data["customer"]);
37
37
  if ("secureToken" in data) this.#handleSecureToken(data["secureToken"]);
38
+ this.#setCheckoutData(data)
38
39
  }
39
40
 
40
41
  /**
@@ -66,12 +67,7 @@ export class BaseInlineCheckout {
66
67
  payment(data) {
67
68
  return new Promise(async (resolve, reject) => {
68
69
  try {
69
- this.#handleCustomer(data.customer);
70
- this._setCartTotal(data.cart?.total);
71
- this.#setCartItems(data.cart?.items);
72
- this.#handleMetadata(data);
73
- this.#handleCurrency(data);
74
- this.#handleCard(data);
70
+ this.#setCheckoutData(data)
75
71
  const response = await this._checkout(data);
76
72
  this.process3ds.setPayload(response);
77
73
  this.callBack(response);
@@ -214,6 +210,16 @@ export class BaseInlineCheckout {
214
210
  }
215
211
  }
216
212
 
213
+ #setCheckoutData(data){
214
+ if(!data) return;
215
+ this.#handleCustomer(data.customer);
216
+ this._setCartTotal(data.cart?.total);
217
+ this.#setCartItems(data.cart?.items);
218
+ this.#handleMetadata(data);
219
+ this.#handleCurrency(data);
220
+ this.#handleCard(data);
221
+ }
222
+
217
223
  async #fetchMerchantData() {
218
224
  this.merchantData = await fetchBusiness(
219
225
  this.baseUrl,
@@ -42,6 +42,7 @@ export class LiteInlineCheckout extends BaseInlineCheckout {
42
42
  const response = await fetchCustomerCards(
43
43
  this.baseUrl,
44
44
  auth_token,
45
+ this.secureToken,
45
46
  this.merchantData.business.pk,
46
47
  );
47
48
  return {
@@ -87,6 +88,7 @@ export class LiteInlineCheckout extends BaseInlineCheckout {
87
88
  return await saveCustomerCard(
88
89
  this.baseUrl,
89
90
  auth_token,
91
+ this.secureToken,
90
92
  business?.pk,
91
93
  skyflowTokens,
92
94
  );
@@ -117,6 +119,7 @@ export class LiteInlineCheckout extends BaseInlineCheckout {
117
119
  return await removeCustomerCard(
118
120
  this.baseUrl,
119
121
  auth_token,
122
+ this.secureToken,
120
123
  skyflowId,
121
124
  business?.pk,
122
125
  );
@@ -6,7 +6,7 @@ import {
6
6
  showError,
7
7
  showMessage
8
8
  } from '../helpers/utils';
9
- import { initSkyflow } from '../helpers/skyflow'
9
+ import { initSkyflow, initUpdateSkyflow } from '../helpers/skyflow'
10
10
  import { globalLoader } from './globalLoader.js';
11
11
  import { BaseInlineCheckout } from "./BaseInlineCheckout";
12
12
  import {
@@ -16,16 +16,20 @@ import {
16
16
  fetchCustomerAPMs
17
17
  } from "../data";
18
18
  import { MESSAGES } from "../shared/constants/messages";
19
+ import Accordion from "accordion-js";
19
20
 
20
21
  export class InlineCheckout extends BaseInlineCheckout {
21
22
  static injected = false;
22
23
  static cardsInjected = false
23
24
  static apmsInjected = false
24
25
  static apmsData = [];
26
+ accordionC = null;
27
+
25
28
  deletingCards = [];
26
29
  customer = {}
27
30
  items = []
28
31
  collectContainer = null
32
+ updateCollectContainer = null
29
33
  merchantData = {}
30
34
  cartTotal = null
31
35
  metadata = {}
@@ -73,17 +77,36 @@ export class InlineCheckout extends BaseInlineCheckout {
73
77
  }
74
78
  }
75
79
 
76
- #mountPayButton() {
80
+ #mountPayButton(cardId="") {
77
81
  if (!this.renderPaymentButton) return;
78
82
 
79
- const payButton = document.querySelector("#tonderPayButton");
83
+ const btnID = `#tonderPayButton${cardId}`;
84
+ const payButton = document.querySelector(btnID);
85
+ const containerID = `#acContainer${cardId}`;
86
+ const container = document.querySelector(containerID);
87
+
80
88
  if (!payButton) {
81
89
  console.error("Pay button not found");
82
90
  return;
83
91
  }
84
92
 
93
+ if (cardId !== "") {
94
+ const sdkPayButton = document.querySelector(`#tonderPayButton`);
95
+ if(sdkPayButton){
96
+ sdkPayButton.style.display = "none";
97
+ }
98
+ }
99
+
85
100
  payButton.style.display = "block";
86
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
+
87
110
  payButton.onclick = async (event) => {
88
111
  event.preventDefault();
89
112
  await this.#handlePaymentClick(payButton);
@@ -94,7 +117,7 @@ export class InlineCheckout extends BaseInlineCheckout {
94
117
  const prevButtonContent = payButton.innerHTML;
95
118
  payButton.innerHTML = `<div class="lds-dual-ring"></div>`;
96
119
  try {
97
- const response = await this.payment(this.customer);
120
+ const response = await this.payment();
98
121
  this.callBack(response);
99
122
  } catch (error) {
100
123
  console.error("Payment error:", error);
@@ -229,10 +252,10 @@ export class InlineCheckout extends BaseInlineCheckout {
229
252
  console.log("InlineCheckout removed from DOM and cleaned up.");
230
253
  }
231
254
 
232
- async #getCardTokens() {
255
+ async #getCardTokens(cardSelected) {
233
256
  if (this.card?.skyflow_id) return this.card
234
257
  try {
235
- const collectResponse = await this.collectContainer.container.collect();
258
+ const collectResponse = cardSelected && cardSelected !== "new" ? await this.updateCollectContainer.container.collect():await this.collectContainer.container.collect();
236
259
  const cardTokens = await collectResponse["records"][0]["fields"];
237
260
  return cardTokens;
238
261
  } catch (error) {
@@ -250,8 +273,9 @@ export class InlineCheckout extends BaseInlineCheckout {
250
273
  let cardTokens;
251
274
 
252
275
  if (this.radioChecked === "new" || this.radioChecked === undefined) {
253
- cardTokens = await this.#getCardTokens();
276
+ cardTokens = await this.#getCardTokens(this.radioChecked);
254
277
  } else {
278
+ await this.#getCardTokens(this.radioChecked);
255
279
  cardTokens = {
256
280
  skyflow_id: this.radioChecked
257
281
  }
@@ -327,8 +351,9 @@ export class InlineCheckout extends BaseInlineCheckout {
327
351
  const injectInterval = setInterval(() => {
328
352
  const queryElement = document.querySelector(`#${this.collectorIds.cardsListContainer}`);
329
353
  if (queryElement && InlineCheckout.injected) {
330
- queryElement.innerHTML = cardItemsTemplate(cards)
354
+ queryElement.innerHTML = cardItemsTemplate(cards, {renderPaymentButton: this.renderPaymentButton})
331
355
  clearInterval(injectInterval)
356
+ this.#generateCardsAccordion()
332
357
  this.#mountRadioButtons(token)
333
358
  this.cardsInjected = true
334
359
  }
@@ -336,6 +361,54 @@ export class InlineCheckout extends BaseInlineCheckout {
336
361
  }
337
362
  }
338
363
 
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);
407
+ }
408
+
409
+ this.#handleRadioButtonClick(radio_card)
410
+ }
411
+
339
412
  #loadAPMList(apms) {
340
413
  if (this.apmsInjected) return;
341
414
  const injectInterval = setInterval(() => {
@@ -359,7 +432,8 @@ export class InlineCheckout extends BaseInlineCheckout {
359
432
  for (const radio of radioButtons) {
360
433
  radio.style.display = "block";
361
434
  radio.onclick = async (event) => {
362
- await this.#handleRadioButtonClick(radio);
435
+ const position = Array.from(radioButtons).indexOf(radio);
436
+ await this.#handleRadioButtonClick(radio, position);
363
437
  };
364
438
  }
365
439
  const cardsButtons = document.getElementsByClassName("card-delete-button");
@@ -372,19 +446,25 @@ export class InlineCheckout extends BaseInlineCheckout {
372
446
  }
373
447
  }
374
448
 
375
- async #handleRadioButtonClick(radio) {
449
+ async #handleRadioButtonClick(radio, position = null) {
376
450
  if (radio.id === this.radioChecked || (radio.id === "new" && this.radioChecked === undefined)) return;
377
451
  const containerForm = document.querySelector(".container-form");
378
452
  if (containerForm) {
379
453
  containerForm.style.display = radio.id === "new" ? "block" : "none";
380
454
  }
455
+
381
456
  if (radio.id === "new") {
457
+ this.accordionC.closeAll()
382
458
  if (this.radioChecked !== radio.id) {
383
459
  globalLoader.show()
384
460
  this.#mountTonder(false);
385
461
  InlineCheckout.injected = true;
386
462
  }
387
463
  } else {
464
+ if (position !== null) {
465
+ this.accordionC.closeAll()
466
+ this.accordionC.open(position)
467
+ }
388
468
  this.#unmountForm();
389
469
  }
390
470
  this.radioChecked = radio.id;