@tonder.io/ionic-full-sdk 0.0.25-beta → 0.0.26-beta

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,623 +1,735 @@
1
- import { Card, cardItemsTemplate, cardTemplate, CollectorIds } from '../helpers/template'
2
- import { LiteCheckout } from '@tonder.io/ionic-lite-sdk';
3
- import {
4
- showError,
5
- showMessage,
6
- mapCards,
7
- getBrowserInfo
8
- } from '../helpers/utils';
9
- import { initSkyflow } from '../helpers/skyflow'
10
- import { ThreeDSHandler } from './3dsHandler';
11
- import { ErrorResponse } from '@tonder.io/ionic-lite-sdk/dist/classes/errorResponse';
12
- import { Business, Customer, PaymentData, OrderItem } from '@tonder.io/ionic-lite-sdk/dist/types/commons';
13
- import { CustomerRegisterResponse, StartCheckoutResponse } from '@tonder.io/ionic-lite-sdk/dist/types/responses';
14
- import { StartCheckoutRequest, CreatePaymentRequest, CreateOrderRequest } from '@tonder.io/ionic-lite-sdk/dist/types/requests';
15
- import { InCollectorContainer } from '../helpers/skyflow';
16
-
17
- export type InlineCheckoutConstructor = {
18
- returnUrl: string,
19
- apiKey: string,
20
- successUrl?: string,
21
- renderPaymentButton: boolean,
22
- callBack?: (params: any) => void,
23
- styles?: any,
24
- containerId?: string,
25
- collectorIds?: CollectorIds,
26
- isOpenPaySandbox?: boolean
27
- }
28
-
29
- export class InlineCheckout {
30
- paymentData = {}
31
- items = []
32
- baseUrl = "https://stage.tonder.io";
33
- collectContainer: InCollectorContainer | null = null;
34
- cartTotal?: string | null | number
35
- apiKeyTonder: string
36
- returnUrl?: string
37
- successUrl?: string
38
- renderPaymentButton: boolean
39
- callBack: (params: any) => void
40
- customStyles: any
41
- abortController: AbortController
42
- process3ds: ThreeDSHandler
43
- cb?: () => void
44
- firstName?: string
45
- lastName?: string
46
- country?: string
47
- address?: string
48
- city?: string
49
- state?: string
50
- postCode?: string
51
- email?: string
52
- phone?: string
53
- merchantData?: Business | ErrorResponse
54
- cartItems?: any
55
- injectInterval: any
56
- containerId: string
57
- injected: boolean
58
- cardsInjected: boolean
59
- collectorIds: CollectorIds
60
- platforms?: string[]
61
- liteCheckout: LiteCheckout
62
- clientCards: Card[]
63
- radioChecked: string | null
64
- fetchingPayment: boolean
65
- isOpenPaySandbox: boolean = true
66
- metadata = {}
67
- card = {}
68
- currency: string = ""
69
-
70
- constructor ({
71
- apiKey,
72
- returnUrl,
73
- successUrl,
74
- renderPaymentButton = false,
75
- callBack = () => {},
76
- styles,
77
- containerId,
78
- collectorIds,
79
- isOpenPaySandbox
80
- }: InlineCheckoutConstructor) {
81
- this.apiKeyTonder = apiKey;
82
- this.returnUrl = returnUrl;
83
- this.successUrl = successUrl;
84
- this.renderPaymentButton = renderPaymentButton;
85
- this.callBack = callBack;
86
- this.customStyles = styles
87
-
88
- this.abortController = new AbortController()
89
-
90
- this.liteCheckout = new LiteCheckout({
91
- apiKeyTonder: apiKey,
92
- baseUrlTonder: this.baseUrl,
93
- signal: this.abortController.signal
94
- })
95
-
96
- this.process3ds = new ThreeDSHandler({
97
- apiKey: apiKey,
98
- baseUrl: this.baseUrl,
99
- successUrl: successUrl
100
- })
101
- this.containerId = containerId ? containerId : "tonder-checkout"
102
- this.injected = false;
103
- this.cardsInjected = false;
104
- this.collectorIds = collectorIds ? collectorIds : {
105
- cardsListContainer: "cardsListContainer",
106
- holderName: "collectCardholderName",
107
- cardNumber: "collectCardNumber",
108
- expirationMonth: "collectExpirationMonth",
109
- expirationYear: "collectExpirationYear",
110
- cvv: "collectCvv",
111
- tonderPayButton: "tonderPayButton",
112
- msgError: "msgError",
113
- msgNotification: "msgNotification"
114
- }
115
- this.clientCards = []
116
- this.radioChecked = "new"
117
- this.collectContainer = null;
118
-
119
- this.fetchingPayment = false;
120
-
121
- this.isOpenPaySandbox = isOpenPaySandbox === undefined ? true : isOpenPaySandbox
122
- }
123
-
124
- #mountPayButton() {
125
- if (!this.renderPaymentButton) return;
126
-
127
- const payButton: HTMLElement | null = document.querySelector(`#${this.collectorIds.tonderPayButton}`);
128
-
129
- if (!payButton) {
130
- console.error("Pay button not found");
131
- return;
132
- }
133
-
134
- const LOCALE_MONEY = "en-Latn-US";
135
-
136
- payButton.style.display = "block";
137
-
138
- const inCartTotal = Intl.NumberFormat(LOCALE_MONEY, { minimumFractionDigits: 2 }).format(this.cartTotal as number);
139
-
140
- payButton.textContent = `Pagar $${inCartTotal}`;
141
-
142
- payButton.onclick = async (event) => {
143
- event.preventDefault();
144
- await this.#handlePaymentClick(payButton);
145
- };
146
-
147
- }
148
-
149
- async #handlePaymentClick(payButton: any) {
150
- const prevButtonContent = payButton.innerHTML;
151
- payButton.innerHTML = `<div class="lds-dual-ring"></div>`;
152
- try {
153
- const response = await this.payment(this.paymentData);
154
- this.callBack(response);
155
- } catch (error) {
156
- console.error("Payment error:", error);
157
- } finally {
158
- payButton.innerHTML = prevButtonContent;
159
- }
160
- }
161
-
162
- #handleMetadata(data: { metadata: any }) {
163
- this.metadata = data?.metadata
164
- }
165
-
166
- #handleCurrency(data: { currency: string }) {
167
- this.currency = data?.currency
168
- }
169
-
170
- #handleCard(data: { card: string }) {
171
- this.card = data?.card
172
- }
173
-
174
- payment(data: any) {
175
- return new Promise(async (resolve, reject) => {
176
- try {
177
- this.#handleCustomer(data.customer)
178
- this.setCartTotal(data.cart?.total)
179
- this.setCartItems(data.cart?.items)
180
- this.#handleMetadata(data)
181
- this.#handleCurrency(data)
182
- this.#handleCard(data)
183
- const response: ErrorResponse | StartCheckoutResponse | false | undefined = await this.#checkout()
184
- if (response) {
185
- const process3ds = new ThreeDSHandler({
186
- payload: response,
187
- baseUrl: this.baseUrl,
188
- apiKey: this.apiKeyTonder,
189
- successUrl: this.successUrl
190
- });
191
- this.callBack(response);
192
- if("next_action" in response) {
193
- const iframe = response?.next_action?.iframe_resources?.iframe
194
- if (iframe) {
195
- process3ds.loadIframe()?.then(() => {
196
- //TODO: Check if this will be necessary on the frontend side
197
- // after some the tests in production, since the 3DS process
198
- // doesn't works properly on the sandbox environment
199
- // setTimeout(() => {
200
- // process3ds.verifyTransactionStatus();
201
- // }, 10000);
202
- process3ds.verifyTransactionStatus();
203
- }).catch((error) => {
204
- console.log('Error loading iframe:', error)
205
- })
206
- } else {
207
- const redirectUrl = process3ds.getRedirectUrl()
208
- if (redirectUrl) {
209
- process3ds.redirectToChallenge()
210
- } else {
211
- resolve(response);
212
- }
213
- }
214
- }
215
- }
216
- } catch (error) {
217
- reject(error);
218
- }
219
- });
220
- }
221
-
222
- #mountRadioButtons (token: string) {
223
- const radioButtons: NodeListOf<HTMLElement> = document.getElementsByName(`card_selected`);
224
- for (const radio of radioButtons) {
225
- radio.style.display = "block";
226
- radio.onclick = async (event) => {
227
- //event.preventDefault();
228
- await this.#handleRadioButtonClick(radio);
229
- };
230
- }
231
- const cardsButtons: HTMLCollectionOf<Element> = document.getElementsByClassName("card-delete-button");
232
- for (const cardButton of cardsButtons) {
233
- cardButton.addEventListener("click", (event) => {
234
- event.preventDefault();
235
- this.#handleDeleteCardButtonClick(token, cardButton)
236
- }, false);
237
- }
238
- }
239
-
240
- async #handleDeleteCardButtonClick (customerToken: string, button: Element) {
241
- const id = button.attributes.getNamedItem("id")
242
- const skyflow_id = id?.value?.split("_")?.[2]
243
- if(skyflow_id) {
244
- const cardClicked: HTMLElement | null = document.querySelector(`#card_container-${skyflow_id}`);
245
- if(cardClicked) {
246
- cardClicked.style.display = "none"
247
- }
248
- await this.liteCheckout.deleteCustomerCard(customerToken, skyflow_id)
249
- this.cardsInjected = false
250
- const cards = await this.liteCheckout.getCustomerCards(customerToken)
251
- if("cards" in cards) {
252
- const cardsMapped: Card[] = cards.cards.map(mapCards)
253
- this.loadCardsList(cardsMapped, customerToken)
254
- }
255
- }
256
- }
257
-
258
- async #handleRadioButtonClick (radio: HTMLElement) {
259
- const containerForm: HTMLElement | null = document.querySelector(".container-form");
260
- if(containerForm) {
261
- containerForm.style.display = radio.id === "new" ? "block" : "none";
262
- }
263
- if(radio.id === "new") {
264
- if(this.radioChecked !== radio.id) {
265
- await this.#mountForm();
266
- }
267
- } else {
268
- this.#unmountForm();
269
- }
270
- this.radioChecked = radio.id;
271
- }
272
-
273
- #handleCustomer(customer: Customer) {
274
- if (!customer) return
275
-
276
- this.firstName = customer?.firstName
277
- this.lastName = customer?.lastName
278
- this.country = customer?.country
279
- this.address = customer?.street
280
- this.city = customer?.city
281
- this.state = customer?.state
282
- this.postCode = customer?.postCode
283
- this.email = customer?.email
284
- this.phone = customer?.phone
285
- }
286
-
287
- setCartItems (items: OrderItem[]) {
288
- this.cartItems = items
289
- }
290
-
291
- setCustomerEmail (email: string) {
292
- this.email = email
293
- }
294
-
295
- setPaymentData (data?: PaymentData) {
296
- if (!data) return
297
- this.paymentData = data
298
- }
299
-
300
- setCartTotal (total: string | number) {
301
- this.cartTotal = total
302
- this.#updatePayButton()
303
- }
304
-
305
- #updatePayButton() {
306
- const payButton = document.querySelector(`#${this.collectorIds.tonderPayButton}`);
307
- if (!payButton) return
308
- payButton.textContent = `Pagar $${this.cartTotal}`;
309
- }
310
-
311
- setCallback (cb: any) {
312
- this.cb = cb
313
- }
314
-
315
- injectCheckout() {
316
- if (this.injected) return
317
- this.process3ds.verifyTransactionStatus()
318
- const injectInterval = setInterval(() => {
319
- const queryElement = document.querySelector(`#${this.containerId}`)
320
- if (queryElement) {
321
- queryElement.innerHTML = cardTemplate(this.customStyles ? true : false, this.collectorIds)
322
- this.#mountTonder();
323
- clearInterval(injectInterval);
324
- this.injected = true
325
- }
326
- }, 500);
327
- }
328
-
329
- loadCardsList (cards: Card[], token: string) {
330
- if(this.cardsInjected) return;
331
- const injectInterval = setInterval(() => {
332
- const queryElement = document.querySelector(`#${this.collectorIds.cardsListContainer}`);
333
- if (queryElement && this.injected) {
334
- queryElement.innerHTML = cardItemsTemplate(this.customStyles ? true : false, cards)
335
- clearInterval(injectInterval)
336
- this.#mountRadioButtons(token)
337
- this.cardsInjected = true
338
- }
339
- }, 500);
340
- }
341
-
342
- async #fetchMerchantData() {
343
- this.merchantData = await this.liteCheckout.getBusiness();
344
- return this.merchantData
345
- }
346
-
347
- async getCustomer(email: string) {
348
- return await this.liteCheckout.customerRegister(email);
349
- }
350
-
351
- async #mountTonder() {
352
-
353
- this.#mountPayButton()
354
-
355
- const result = await this.#fetchMerchantData();
356
-
357
- if(result && "vault_id" in result) {
358
-
359
- const { vault_id, vault_url } = result;
360
-
361
- if(this.email) {
362
-
363
- const customerResponse : CustomerRegisterResponse | ErrorResponse = await this.getCustomer(this.email);
364
-
365
- if("auth_token" in customerResponse) {
366
-
367
- const { auth_token } = customerResponse
368
-
369
- const cards = await this.liteCheckout.getCustomerCards(auth_token);
370
-
371
- if("cards" in cards) {
372
-
373
- const cardsMapped: Card[] = cards.cards.map(mapCards)
374
-
375
- this.loadCardsList(cardsMapped, auth_token)
376
-
377
- }
378
-
379
- }
380
-
381
- }
382
-
383
- this.collectContainer = await initSkyflow(
384
- vault_id,
385
- vault_url,
386
- this.baseUrl,
387
- this.abortController.signal,
388
- this.customStyles,
389
- this.collectorIds,
390
- this.apiKeyTonder
391
- );
392
-
393
- }
394
-
395
- }
396
-
397
- removeCheckout() {
398
-
399
- this.injected = false
400
- this.cardsInjected = false
401
- // Cancel all requests
402
- this.abortController.abort();
403
- this.abortController = new AbortController();
404
- clearInterval(this.injectInterval);
405
- console.log("InlineCheckout removed from DOM and cleaned up.");
406
-
407
- }
408
-
409
- #unmountForm () {
410
-
411
- this.injected = false
412
-
413
- if(this.collectContainer) {
414
- if("unmount" in this.collectContainer.elements.cardHolderNameElement) this.collectContainer.elements.cardHolderNameElement.unmount()
415
- if("unmount" in this.collectContainer.elements.cardNumberElement) this.collectContainer.elements.cardNumberElement.unmount()
416
- if("unmount" in this.collectContainer.elements.expiryYearElement) this.collectContainer.elements.expiryYearElement.unmount()
417
- if("unmount" in this.collectContainer.elements.expiryMonthElement) this.collectContainer.elements.expiryMonthElement.unmount()
418
- if("unmount" in this.collectContainer.elements.cvvElement) this.collectContainer.elements.cvvElement.unmount()
419
- }
420
-
421
- }
422
-
423
- async #mountForm () {
424
-
425
- const result = await this.#fetchMerchantData();
426
-
427
- if(result && "vault_id" in result) {
428
-
429
- const { vault_id, vault_url } = result;
430
-
431
- this.collectContainer = await initSkyflow(
432
- vault_id,
433
- vault_url,
434
- this.baseUrl,
435
- this.abortController.signal,
436
- this.customStyles,
437
- this.collectorIds,
438
- this.apiKeyTonder
439
- );
440
-
441
- }
442
-
443
- }
444
-
445
- async #checkout() {
446
-
447
- try {
448
-
449
- try {
450
-
451
- const selector: any = document.querySelector(`#${this.collectorIds.tonderPayButton}`);
452
-
453
- if(selector){
454
- selector.disabled = true;
455
- selector.innerHTML = "Cargando..."
456
- }
457
-
458
- } catch (error) { }
459
-
460
- if(this.merchantData) {
461
-
462
- if("openpay_keys" in this.merchantData) {
463
-
464
- const { openpay_keys, reference, business } = this.merchantData
465
-
466
- const total = Number(this.cartTotal)
467
-
468
- let cardTokensSkyflowTonder: any = null;
469
-
470
- if(this.radioChecked === "new") {
471
-
472
- if(this.collectContainer && "container" in this.collectContainer && "collect" in this.collectContainer.container) {
473
- try {
474
- const collectResponseSkyflowTonder: any = await this.collectContainer?.container.collect();
475
- cardTokensSkyflowTonder = await collectResponseSkyflowTonder["records"][0]["fields"];
476
- } catch (error) {
477
- showError("Por favor, verifica todos los campos de tu tarjeta", this.collectorIds.msgError, this.collectorIds.tonderPayButton)
478
- throw error;
479
- }
480
- } else {
481
- showError("Por favor, verifica todos los campos de tu tarjeta", this.collectorIds.msgError, this.collectorIds.tonderPayButton)
482
- }
483
-
484
- } else {
485
-
486
- cardTokensSkyflowTonder = {
487
- skyflow_id: this.radioChecked
488
- }
489
-
490
- }
491
-
492
- let deviceSessionIdTonder: any;
493
-
494
- if (openpay_keys.merchant_id && openpay_keys.public_key) {
495
- deviceSessionIdTonder = await this.liteCheckout.getOpenpayDeviceSessionID(
496
- openpay_keys.merchant_id,
497
- openpay_keys.public_key,
498
- this.isOpenPaySandbox
499
- );
500
- }
501
-
502
- if(this.email) {
503
-
504
- const customerResponse : CustomerRegisterResponse | ErrorResponse = await this.getCustomer(this.email);
505
-
506
- if("auth_token" in customerResponse) {
507
-
508
- const { auth_token, id: cutomerId } = customerResponse;
509
-
510
- const saveCard: HTMLElement | null = document.getElementById("save-checkout-card");
511
-
512
- if(saveCard && "checked" in saveCard && saveCard.checked) {
513
-
514
- await this.liteCheckout.registerCustomerCard(auth_token, { skyflow_id: cardTokensSkyflowTonder.skyflow_id });
515
-
516
- this.cardsInjected = false;
517
-
518
- const cards = await this.liteCheckout.getCustomerCards(auth_token);
519
-
520
- if("cards" in cards) {
521
-
522
- const cardsMapped: Card[] = cards.cards.map((card) => mapCards(card))
523
-
524
- this.loadCardsList(cardsMapped, auth_token)
525
-
526
- }
527
-
528
- showMessage("Tarjeta registrada con éxito", this.collectorIds.msgNotification);
529
-
530
- }
531
-
532
- const orderItems: CreateOrderRequest = {
533
- business: this.apiKeyTonder,
534
- client: auth_token,
535
- billing_address_id: null,
536
- shipping_address_id: null,
537
- amount: total,
538
- status: "A",
539
- reference: reference,
540
- is_oneclick: true,
541
- items: this.cartItems,
542
- };
543
-
544
- const jsonResponseOrder = await this.liteCheckout.createOrder(
545
- orderItems
546
- );
547
-
548
- // Create payment
549
- const now = new Date();
550
- const dateString = now.toISOString();
551
-
552
- if("id" in jsonResponseOrder) {
553
-
554
- const paymentItems: CreatePaymentRequest = {
555
- business_pk: business.pk,
556
- amount: total,
557
- date: dateString,
558
- order_id: jsonResponseOrder.id,
559
- client_id: cutomerId
560
- };
561
-
562
- const jsonResponsePayment = await this.liteCheckout.createPayment(
563
- paymentItems
564
- );
565
-
566
- // Checkout router
567
- const routerItems: StartCheckoutRequest = {
568
- card: cardTokensSkyflowTonder,
569
- name: this.firstName,
570
- last_name: this.lastName ?? "",
571
- email_client: this.email,
572
- phone_number: this.phone,
573
- return_url: this.returnUrl,
574
- id_product: "no_id",
575
- quantity_product: 1,
576
- id_ship: "0",
577
- instance_id_ship: "0",
578
- amount: total,
579
- title_ship: "shipping",
580
- description: "transaction",
581
- device_session_id: deviceSessionIdTonder ? deviceSessionIdTonder : null,
582
- token_id: "",
583
- order_id: ("id" in jsonResponseOrder) && jsonResponseOrder.id,
584
- business_id: business.pk,
585
- payment_id: ("pk" in jsonResponsePayment) && jsonResponsePayment.pk,
586
- source: 'sdk',
587
- metadata: this.metadata,
588
- browser_info: getBrowserInfo(),
589
- currency: this.currency
590
- };
591
-
592
- const jsonResponseRouter = await this.liteCheckout.startCheckoutRouter(
593
- routerItems
594
- );
595
-
596
- if (jsonResponseRouter) {
597
- try {
598
- const selector: any = document.querySelector(`#${this.collectorIds.tonderPayButton}`);
599
- if(selector) {
600
- selector.disabled = false;
601
- }
602
- } catch {}
603
- return jsonResponseRouter;
604
- } else {
605
- showError("No se ha podido procesar el pago", this.collectorIds.msgError, this.collectorIds.tonderPayButton)
606
- return false;
607
- }
608
- }
609
- }
610
- }
611
- }
612
- } else {
613
- showError("No se han configurado los datos del proveedor de servicio", this.collectorIds.msgError, this.collectorIds.tonderPayButton)
614
- }
615
- } catch (error) {
616
- console.log(error);
617
- showError("Ha ocurrido un error", this.collectorIds.msgError, this.collectorIds.tonderPayButton)
618
- throw error;
619
- } finally {
620
- this.fetchingPayment = false;
621
- }
622
- };
1
+ import { Card, cardItemsTemplate, cardTemplate, CollectorIds } from '../helpers/template'
2
+ import { LiteCheckout } from '@tonder.io/ionic-lite-sdk';
3
+ import {
4
+ showError,
5
+ showMessage,
6
+ mapCards,
7
+ getBrowserInfo
8
+ } from '../helpers/utils';
9
+ import { initSkyflow } from '../helpers/skyflow'
10
+ import { ThreeDSHandler } from './3dsHandler';
11
+ import { ErrorResponse } from '@tonder.io/ionic-lite-sdk/dist/classes/errorResponse';
12
+ import { Business, Customer, PaymentData, OrderItem } from '@tonder.io/ionic-lite-sdk/dist/types/commons';
13
+ import { CustomerRegisterResponse, StartCheckoutResponse } from '@tonder.io/ionic-lite-sdk/dist/types/responses';
14
+ import { StartCheckoutRequest, CreatePaymentRequest, CreateOrderRequest } from '@tonder.io/ionic-lite-sdk/dist/types/requests';
15
+ import { InCollectorContainer } from '../helpers/skyflow';
16
+
17
+ export type InlineCheckoutConstructor = {
18
+ returnUrl: string,
19
+ apiKey: string,
20
+ successUrl?: string,
21
+ renderPaymentButton: boolean,
22
+ renderSaveCardButton: boolean,
23
+ callBack?: (params: any) => void,
24
+ styles?: any,
25
+ containerId?: string,
26
+ collectorIds?: CollectorIds,
27
+ isOpenPaySandbox?: boolean,
28
+ isEnrollmentCard?: boolean
29
+ }
30
+
31
+ export class InlineCheckout {
32
+ paymentData = {}
33
+ items = []
34
+ baseUrl = "https://stage.tonder.io";
35
+ collectContainer: InCollectorContainer | null = null;
36
+ cartTotal?: string | null | number
37
+ apiKeyTonder: string
38
+ returnUrl?: string
39
+ successUrl?: string
40
+ renderPaymentButton: boolean
41
+ renderSaveCardButton: boolean
42
+ callBack: (params: any) => void
43
+ customStyles: any
44
+ abortController: AbortController
45
+ process3ds: ThreeDSHandler
46
+ cb?: () => void
47
+ firstName?: string
48
+ lastName?: string
49
+ country?: string
50
+ address?: string
51
+ city?: string
52
+ state?: string
53
+ postCode?: string
54
+ email?: string
55
+ phone?: string
56
+ merchantData?: Business | ErrorResponse
57
+ cartItems?: any
58
+ injectInterval: any
59
+ containerId: string
60
+ injected: boolean
61
+ cardsInjected: boolean
62
+ collectorIds: CollectorIds
63
+ platforms?: string[]
64
+ liteCheckout: LiteCheckout
65
+ clientCards: Card[]
66
+ radioChecked: string | null
67
+ fetchingPayment: boolean
68
+ isOpenPaySandbox: boolean = true
69
+ metadata = {}
70
+ card = {}
71
+ currency: string = ""
72
+ isEnrollmentCard: boolean = false
73
+
74
+ constructor ({
75
+ apiKey,
76
+ returnUrl,
77
+ successUrl,
78
+ renderPaymentButton = false,
79
+ callBack = () => {},
80
+ styles,
81
+ containerId,
82
+ collectorIds,
83
+ isOpenPaySandbox,
84
+ isEnrollmentCard,
85
+ renderSaveCardButton = false,
86
+ }: InlineCheckoutConstructor) {
87
+ this.apiKeyTonder = apiKey;
88
+ this.returnUrl = returnUrl;
89
+ this.successUrl = successUrl;
90
+ this.renderPaymentButton = renderPaymentButton;
91
+ this.renderSaveCardButton = renderSaveCardButton;
92
+ this.callBack = callBack;
93
+ this.customStyles = styles
94
+
95
+ this.abortController = new AbortController()
96
+
97
+ this.liteCheckout = new LiteCheckout({
98
+ apiKeyTonder: apiKey,
99
+ baseUrlTonder: this.baseUrl,
100
+ signal: this.abortController.signal
101
+ })
102
+
103
+ this.process3ds = new ThreeDSHandler({
104
+ apiKey: apiKey,
105
+ baseUrl: this.baseUrl,
106
+ successUrl: successUrl
107
+ })
108
+ this.containerId = containerId ? containerId : "tonder-checkout"
109
+ this.injected = false;
110
+ this.cardsInjected = false;
111
+ this.collectorIds = collectorIds ? collectorIds : {
112
+ cardsListContainer: "cardsListContainer",
113
+ holderName: "collectCardholderName",
114
+ cardNumber: "collectCardNumber",
115
+ expirationMonth: "collectExpirationMonth",
116
+ expirationYear: "collectExpirationYear",
117
+ cvv: "collectCvv",
118
+ tonderPayButton: "tonderPayButton",
119
+ msgError: "msgError",
120
+ msgNotification: "msgNotification",
121
+ tonderSaveCardButton: "tonderSaveCardButton"
122
+ }
123
+ this.clientCards = []
124
+ this.radioChecked = "new"
125
+ this.collectContainer = null;
126
+
127
+ this.fetchingPayment = false;
128
+
129
+ this.isOpenPaySandbox = isOpenPaySandbox === undefined ? true : isOpenPaySandbox
130
+ this.isEnrollmentCard = isEnrollmentCard === undefined ? false : isEnrollmentCard
131
+ }
132
+
133
+ #mountPayButton() {
134
+ if (!this.renderPaymentButton) return;
135
+
136
+ const payButton: HTMLElement | null = document.querySelector(`#${this.collectorIds.tonderPayButton}`);
137
+
138
+ if (!payButton) {
139
+ console.error("Pay button not found");
140
+ return;
141
+ }
142
+
143
+ const LOCALE_MONEY = "en-Latn-US";
144
+
145
+ payButton.style.display = "block";
146
+
147
+ const inCartTotal = Intl.NumberFormat(LOCALE_MONEY, { minimumFractionDigits: 2 }).format(this.cartTotal as number);
148
+
149
+ payButton.textContent = `Pagar $${inCartTotal}`;
150
+
151
+ payButton.onclick = async (event) => {
152
+ event.preventDefault();
153
+ await this.#handlePaymentClick(payButton);
154
+ };
155
+
156
+ }
157
+
158
+ async #handlePaymentClick(payButton: any) {
159
+ const prevButtonContent = payButton.innerHTML;
160
+ payButton.innerHTML = `<div class="lds-dual-ring"></div>`;
161
+ try {
162
+ const response = await this.payment(this.paymentData);
163
+ this.callBack(response);
164
+ } catch (error) {
165
+ console.error("Payment error:", error);
166
+ } finally {
167
+ payButton.innerHTML = prevButtonContent;
168
+ }
169
+ }
170
+
171
+ #mountSaveCardButton() {
172
+ if (!this.renderSaveCardButton) return;
173
+
174
+ const saveButton: HTMLElement | null = document.querySelector(`#${this.collectorIds.tonderSaveCardButton}`);
175
+
176
+ if (!saveButton) {
177
+ console.error("Save button not found");
178
+ return;
179
+ }
180
+
181
+
182
+ saveButton.style.display = "block";
183
+
184
+ saveButton.textContent = `Guardar`;
185
+
186
+ saveButton.onclick = async (event) => {
187
+ event.preventDefault();
188
+ await this.#handleSaveCardClick(saveButton);
189
+ };
190
+
191
+ }
192
+
193
+ async #handleSaveCardClick(saveButton: any) {
194
+ const prevButtonContent = saveButton.innerHTML;
195
+ saveButton.innerHTML = `<div class="lds-dual-ring"></div>`;
196
+ try {
197
+ const response = await this.saveCard();
198
+ this.callBack(response);
199
+ } catch (error) {
200
+ console.error("Save card error:", error);
201
+ } finally {
202
+ saveButton.innerHTML = prevButtonContent;
203
+ }
204
+ }
205
+
206
+ #handleMetadata(data: { metadata: any }) {
207
+ this.metadata = data?.metadata
208
+ }
209
+
210
+ #handleCurrency(data: { currency: string }) {
211
+ this.currency = data?.currency
212
+ }
213
+
214
+ #handleCard(data: { card: string }) {
215
+ this.card = data?.card
216
+ }
217
+
218
+ payment(data: any) {
219
+ return new Promise(async (resolve, reject) => {
220
+ try {
221
+ this.#handleCustomer(data.customer)
222
+ this.setCartTotal(data.cart?.total)
223
+ this.setCartItems(data.cart?.items)
224
+ this.#handleMetadata(data)
225
+ this.#handleCurrency(data)
226
+ this.#handleCard(data)
227
+ const response: ErrorResponse | StartCheckoutResponse | false | undefined = await this.#checkout()
228
+ if (response) {
229
+ const process3ds = new ThreeDSHandler({
230
+ payload: response,
231
+ baseUrl: this.baseUrl,
232
+ apiKey: this.apiKeyTonder,
233
+ successUrl: this.successUrl
234
+ });
235
+ this.callBack(response);
236
+ if("next_action" in response) {
237
+ const iframe = response?.next_action?.iframe_resources?.iframe
238
+ if (iframe) {
239
+ process3ds.loadIframe()?.then(() => {
240
+ //TODO: Check if this will be necessary on the frontend side
241
+ // after some the tests in production, since the 3DS process
242
+ // doesn't works properly on the sandbox environment
243
+ // setTimeout(() => {
244
+ // process3ds.verifyTransactionStatus();
245
+ // }, 10000);
246
+ process3ds.verifyTransactionStatus();
247
+ }).catch((error) => {
248
+ console.log('Error loading iframe:', error)
249
+ })
250
+ } else {
251
+ const redirectUrl = process3ds.getRedirectUrl()
252
+ if (redirectUrl) {
253
+ process3ds.redirectToChallenge()
254
+ } else {
255
+ resolve(response);
256
+ }
257
+ }
258
+ }
259
+ }
260
+ } catch (error) {
261
+ reject(error);
262
+ }
263
+ });
264
+ }
265
+
266
+ #mountRadioButtons (token: string) {
267
+ const radioButtons: NodeListOf<HTMLElement> = document.getElementsByName(`card_selected`);
268
+ for (const radio of radioButtons) {
269
+ radio.style.display = "block";
270
+ radio.onclick = async (event) => {
271
+ //event.preventDefault();
272
+ await this.#handleRadioButtonClick(radio);
273
+ };
274
+ }
275
+ const cardsButtons: HTMLCollectionOf<Element> = document.getElementsByClassName("card-delete-button");
276
+ for (const cardButton of cardsButtons) {
277
+ cardButton.addEventListener("click", (event) => {
278
+ event.preventDefault();
279
+ this.#handleDeleteCardButtonClick(token, cardButton)
280
+ }, false);
281
+ }
282
+ }
283
+
284
+ async #handleDeleteCardButtonClick (customerToken: string, button: Element) {
285
+ const id = button.attributes.getNamedItem("id")
286
+ const skyflow_id = id?.value?.split("_")?.[2]
287
+ if(skyflow_id) {
288
+ const cardClicked: HTMLElement | null = document.querySelector(`#card_container-${skyflow_id}`);
289
+ if(cardClicked) {
290
+ cardClicked.style.display = "none"
291
+ }
292
+ await this.liteCheckout.deleteCustomerCard(customerToken, skyflow_id)
293
+ this.cardsInjected = false
294
+ const cards = await this.liteCheckout.getCustomerCards(customerToken)
295
+ if("cards" in cards) {
296
+ const cardsMapped: Card[] = cards.cards.map(mapCards)
297
+ this.loadCardsList(cardsMapped, customerToken)
298
+ }
299
+ }
300
+ }
301
+
302
+ async #handleRadioButtonClick (radio: HTMLElement) {
303
+ const containerForm: HTMLElement | null = document.querySelector(".container-form");
304
+ if(containerForm) {
305
+ containerForm.style.display = radio.id === "new" ? "block" : "none";
306
+ }
307
+ if(radio.id === "new") {
308
+ if(this.radioChecked !== radio.id) {
309
+ await this.#mountForm();
310
+ }
311
+ } else {
312
+ this.#unmountForm();
313
+ }
314
+ this.radioChecked = radio.id;
315
+ }
316
+
317
+ #handleCustomer(customer: Customer) {
318
+ if (!customer) return
319
+
320
+ this.firstName = customer?.firstName
321
+ this.lastName = customer?.lastName
322
+ this.country = customer?.country
323
+ this.address = customer?.street
324
+ this.city = customer?.city
325
+ this.state = customer?.state
326
+ this.postCode = customer?.postCode
327
+ this.email = customer?.email
328
+ this.phone = customer?.phone
329
+ }
330
+
331
+ setCartItems (items: OrderItem[]) {
332
+ this.cartItems = items
333
+ }
334
+
335
+ setCustomerEmail (email: string) {
336
+ this.email = email
337
+ }
338
+
339
+ setPaymentData (data?: PaymentData) {
340
+ if (!data) return
341
+ this.paymentData = data
342
+ }
343
+
344
+ setCartTotal (total: string | number) {
345
+ this.cartTotal = total
346
+ this.#updatePayButton()
347
+ }
348
+
349
+ #updatePayButton() {
350
+ const payButton = document.querySelector(`#${this.collectorIds.tonderPayButton}`);
351
+ if (!payButton) return
352
+ payButton.textContent = `Pagar $${this.cartTotal}`;
353
+ }
354
+
355
+ setCallback (cb: any) {
356
+ this.cb = cb
357
+ }
358
+
359
+ injectCheckout() {
360
+ if (this.injected) return
361
+ this.process3ds.verifyTransactionStatus()
362
+ const injectInterval = setInterval(() => {
363
+ const queryElement = document.querySelector(`#${this.containerId}`)
364
+ if (queryElement) {
365
+ queryElement.innerHTML = cardTemplate(this.customStyles ? true : false, this.collectorIds, this.isEnrollmentCard)
366
+ this.#mountTonder();
367
+ clearInterval(injectInterval);
368
+ this.injected = true
369
+ }
370
+ }, 500);
371
+ }
372
+
373
+ loadCardsList (cards: Card[], token: string) {
374
+ if(this.cardsInjected) return;
375
+ const injectInterval = setInterval(() => {
376
+ const queryElement = document.querySelector(`#${this.collectorIds.cardsListContainer}`);
377
+ if (queryElement && this.injected) {
378
+ queryElement.innerHTML = cardItemsTemplate(this.customStyles ? true : false, cards)
379
+ clearInterval(injectInterval)
380
+ this.#mountRadioButtons(token)
381
+ this.cardsInjected = true
382
+ }
383
+ }, 500);
384
+ }
385
+
386
+ async #fetchMerchantData() {
387
+ this.merchantData = await this.liteCheckout.getBusiness();
388
+ return this.merchantData
389
+ }
390
+
391
+ async getCustomer(email: string) {
392
+ return await this.liteCheckout.customerRegister(email);
393
+ }
394
+
395
+ async #mountTonder() {
396
+ if(this.isEnrollmentCard){
397
+ this.#mountSaveCardButton()
398
+ }else{
399
+ this.#mountPayButton()
400
+ }
401
+
402
+ const result = await this.#fetchMerchantData();
403
+
404
+ if(result && "vault_id" in result) {
405
+
406
+ const { vault_id, vault_url } = result;
407
+
408
+ if(this.email && !this.isEnrollmentCard) {
409
+
410
+ const customerResponse : CustomerRegisterResponse | ErrorResponse = await this.getCustomer(this.email);
411
+
412
+ if("auth_token" in customerResponse) {
413
+
414
+ const { auth_token } = customerResponse
415
+
416
+ const cards = await this.liteCheckout.getCustomerCards(auth_token);
417
+
418
+ if("cards" in cards) {
419
+
420
+ const cardsMapped: Card[] = cards.cards.map(mapCards)
421
+
422
+ this.loadCardsList(cardsMapped, auth_token)
423
+
424
+ }
425
+
426
+ }
427
+
428
+ }
429
+
430
+ this.collectContainer = await initSkyflow(
431
+ vault_id,
432
+ vault_url,
433
+ this.baseUrl,
434
+ this.abortController.signal,
435
+ this.customStyles,
436
+ this.collectorIds,
437
+ this.apiKeyTonder
438
+ );
439
+
440
+ }
441
+
442
+ }
443
+
444
+ removeCheckout() {
445
+
446
+ this.injected = false
447
+ this.cardsInjected = false
448
+ // Cancel all requests
449
+ this.abortController.abort();
450
+ this.abortController = new AbortController();
451
+ clearInterval(this.injectInterval);
452
+ console.log("InlineCheckout removed from DOM and cleaned up.");
453
+
454
+ }
455
+
456
+ #unmountForm () {
457
+
458
+ this.injected = false
459
+
460
+ if(this.collectContainer) {
461
+ if("unmount" in this.collectContainer.elements.cardHolderNameElement) this.collectContainer.elements.cardHolderNameElement.unmount()
462
+ if("unmount" in this.collectContainer.elements.cardNumberElement) this.collectContainer.elements.cardNumberElement.unmount()
463
+ if("unmount" in this.collectContainer.elements.expiryYearElement) this.collectContainer.elements.expiryYearElement.unmount()
464
+ if("unmount" in this.collectContainer.elements.expiryMonthElement) this.collectContainer.elements.expiryMonthElement.unmount()
465
+ if("unmount" in this.collectContainer.elements.cvvElement) this.collectContainer.elements.cvvElement.unmount()
466
+ }
467
+
468
+ }
469
+
470
+ async #mountForm () {
471
+
472
+ const result = await this.#fetchMerchantData();
473
+
474
+ if(result && "vault_id" in result) {
475
+
476
+ const { vault_id, vault_url } = result;
477
+
478
+ this.collectContainer = await initSkyflow(
479
+ vault_id,
480
+ vault_url,
481
+ this.baseUrl,
482
+ this.abortController.signal,
483
+ this.customStyles,
484
+ this.collectorIds,
485
+ this.apiKeyTonder
486
+ );
487
+
488
+ }
489
+
490
+ }
491
+
492
+ async #getCardTokens(tonderButton: string){
493
+ console.log("this.collectContainer: ", this.collectContainer)
494
+ if(this.collectContainer && "container" in this.collectContainer && "collect" in this.collectContainer.container) {
495
+ try {
496
+ const collectResponseSkyflowTonder: any = await this.collectContainer?.container.collect();
497
+ return await collectResponseSkyflowTonder["records"][0]["fields"];
498
+ } catch (error) {
499
+ showError("Por favor, verifica todos los campos de tu tarjeta", this.collectorIds.msgError, tonderButton)
500
+ throw error;
501
+ }
502
+ } else {
503
+ showError("Por favor, verifica todos los campos de tu tarjeta", this.collectorIds.msgError, tonderButton)
504
+ }
505
+ }
506
+ async #updateSaveCardButton(disabled = false, text = "Guardar"){
507
+ try {
508
+
509
+ const selector: any = document.querySelector(`#${this.collectorIds.tonderSaveCardButton}`);
510
+
511
+ if(selector){
512
+ selector.disabled = disabled;
513
+ selector.innerHTML = text
514
+ }
515
+
516
+ } catch (error) { }
517
+ }
518
+ async #validateAndSaveCard(){
519
+
520
+ if(this.email){
521
+ try{
522
+ if(this.merchantData && "openpay_keys" in this.merchantData) {
523
+ const cardTokensSkyflowTonder: any = await this.#getCardTokens(this.collectorIds.tonderSaveCardButton);
524
+ const customerResponse : CustomerRegisterResponse | ErrorResponse = await this.getCustomer(this.email);
525
+
526
+ if("auth_token" in customerResponse) {
527
+ const { auth_token, id: cutomerId } = customerResponse;
528
+ console.log("cardTokensSkyflowTonder: ", cardTokensSkyflowTonder)
529
+ await this.liteCheckout.registerCustomerCard(auth_token, { skyflow_id: cardTokensSkyflowTonder.skyflow_id });
530
+ showMessage("Tarjeta registrada con éxito", this.collectorIds.msgNotification);
531
+ }
532
+ }else{
533
+ showError("No se han configurado los datos del proveedor de servicio", this.collectorIds.msgError, this.collectorIds.tonderSaveCardButton)
534
+ }
535
+ } catch (error) {
536
+ console.log(error);
537
+ showError("Ha ocurrido un error", this.collectorIds.msgError, this.collectorIds.tonderSaveCardButton)
538
+ throw error;
539
+ } finally {}
540
+ }
541
+
542
+ }
543
+
544
+ async saveCard(){
545
+ return new Promise(async(resolve, reject) => {
546
+ try{
547
+ this.#updateSaveCardButton(true, "Guardando...")
548
+ await this.#validateAndSaveCard()
549
+ this.#updateSaveCardButton(false)
550
+ resolve("Tarjeta registrada con éxito");
551
+ }catch(error){
552
+ reject(error);
553
+ }
554
+ })
555
+
556
+ }
557
+ async #checkout() {
558
+
559
+ try {
560
+
561
+ try {
562
+
563
+ const selector: any = document.querySelector(`#${this.collectorIds.tonderPayButton}`);
564
+
565
+ if(selector){
566
+ selector.disabled = true;
567
+ selector.innerHTML = "Cargando..."
568
+ }
569
+
570
+ } catch (error) { }
571
+
572
+ if(this.merchantData) {
573
+
574
+ if("openpay_keys" in this.merchantData) {
575
+
576
+ const { openpay_keys, reference, business } = this.merchantData
577
+
578
+ const total = Number(this.cartTotal)
579
+
580
+ let cardTokensSkyflowTonder: any = null;
581
+
582
+ if(this.radioChecked === "new") {
583
+
584
+ if(this.collectContainer && "container" in this.collectContainer && "collect" in this.collectContainer.container) {
585
+ try {
586
+ const collectResponseSkyflowTonder: any = await this.collectContainer?.container.collect();
587
+ cardTokensSkyflowTonder = await collectResponseSkyflowTonder["records"][0]["fields"];
588
+ } catch (error) {
589
+ showError("Por favor, verifica todos los campos de tu tarjeta", this.collectorIds.msgError, this.collectorIds.tonderPayButton)
590
+ throw error;
591
+ }
592
+ } else {
593
+ showError("Por favor, verifica todos los campos de tu tarjeta", this.collectorIds.msgError, this.collectorIds.tonderPayButton)
594
+ }
595
+
596
+ } else {
597
+
598
+ cardTokensSkyflowTonder = {
599
+ skyflow_id: this.radioChecked
600
+ }
601
+
602
+ }
603
+
604
+ let deviceSessionIdTonder: any;
605
+
606
+ if (openpay_keys.merchant_id && openpay_keys.public_key) {
607
+ deviceSessionIdTonder = await this.liteCheckout.getOpenpayDeviceSessionID(
608
+ openpay_keys.merchant_id,
609
+ openpay_keys.public_key,
610
+ this.isOpenPaySandbox
611
+ );
612
+ }
613
+
614
+ if(this.email) {
615
+
616
+ const customerResponse : CustomerRegisterResponse | ErrorResponse = await this.getCustomer(this.email);
617
+
618
+ if("auth_token" in customerResponse) {
619
+
620
+ const { auth_token, id: cutomerId } = customerResponse;
621
+
622
+ const saveCard: HTMLElement | null = document.getElementById("save-checkout-card");
623
+
624
+ if(saveCard && "checked" in saveCard && saveCard.checked) {
625
+
626
+ await this.liteCheckout.registerCustomerCard(auth_token, { skyflow_id: cardTokensSkyflowTonder.skyflow_id });
627
+
628
+ this.cardsInjected = false;
629
+
630
+ const cards = await this.liteCheckout.getCustomerCards(auth_token);
631
+
632
+ if("cards" in cards) {
633
+
634
+ const cardsMapped: Card[] = cards.cards.map((card) => mapCards(card))
635
+
636
+ this.loadCardsList(cardsMapped, auth_token)
637
+
638
+ }
639
+
640
+ showMessage("Tarjeta registrada con éxito", this.collectorIds.msgNotification);
641
+
642
+ }
643
+
644
+ const orderItems: CreateOrderRequest = {
645
+ business: this.apiKeyTonder,
646
+ client: auth_token,
647
+ billing_address_id: null,
648
+ shipping_address_id: null,
649
+ amount: total,
650
+ status: "A",
651
+ reference: reference,
652
+ is_oneclick: true,
653
+ items: this.cartItems,
654
+ };
655
+
656
+ const jsonResponseOrder = await this.liteCheckout.createOrder(
657
+ orderItems
658
+ );
659
+
660
+ // Create payment
661
+ const now = new Date();
662
+ const dateString = now.toISOString();
663
+
664
+ if("id" in jsonResponseOrder) {
665
+
666
+ const paymentItems: CreatePaymentRequest = {
667
+ business_pk: business.pk,
668
+ amount: total,
669
+ date: dateString,
670
+ order_id: jsonResponseOrder.id,
671
+ client_id: cutomerId
672
+ };
673
+
674
+ const jsonResponsePayment = await this.liteCheckout.createPayment(
675
+ paymentItems
676
+ );
677
+
678
+ // Checkout router
679
+ const routerItems: StartCheckoutRequest = {
680
+ card: cardTokensSkyflowTonder,
681
+ name: this.firstName,
682
+ last_name: this.lastName ?? "",
683
+ email_client: this.email,
684
+ phone_number: this.phone,
685
+ return_url: this.returnUrl,
686
+ id_product: "no_id",
687
+ quantity_product: 1,
688
+ id_ship: "0",
689
+ instance_id_ship: "0",
690
+ amount: total,
691
+ title_ship: "shipping",
692
+ description: "transaction",
693
+ device_session_id: deviceSessionIdTonder ? deviceSessionIdTonder : null,
694
+ token_id: "",
695
+ order_id: ("id" in jsonResponseOrder) && jsonResponseOrder.id,
696
+ business_id: business.pk,
697
+ payment_id: ("pk" in jsonResponsePayment) && jsonResponsePayment.pk,
698
+ source: 'sdk',
699
+ metadata: this.metadata,
700
+ browser_info: getBrowserInfo(),
701
+ currency: this.currency
702
+ };
703
+
704
+ const jsonResponseRouter = await this.liteCheckout.startCheckoutRouter(
705
+ routerItems
706
+ );
707
+
708
+ if (jsonResponseRouter) {
709
+ try {
710
+ const selector: any = document.querySelector(`#${this.collectorIds.tonderPayButton}`);
711
+ if(selector) {
712
+ selector.disabled = false;
713
+ }
714
+ } catch {}
715
+ return jsonResponseRouter;
716
+ } else {
717
+ showError("No se ha podido procesar el pago", this.collectorIds.msgError, this.collectorIds.tonderPayButton)
718
+ return false;
719
+ }
720
+ }
721
+ }
722
+ }
723
+ }
724
+ } else {
725
+ showError("No se han configurado los datos del proveedor de servicio", this.collectorIds.msgError, this.collectorIds.tonderPayButton)
726
+ }
727
+ } catch (error) {
728
+ console.log(error);
729
+ showError("Ha ocurrido un error", this.collectorIds.msgError, this.collectorIds.tonderPayButton)
730
+ throw error;
731
+ } finally {
732
+ this.fetchingPayment = false;
733
+ }
734
+ };
623
735
  }