@tonder.io/ionic-lite-sdk 0.0.35-beta.7 → 0.0.36-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.
Files changed (85) hide show
  1. package/.gitlab-ci.yml +28 -28
  2. package/README.md +221 -202
  3. package/dist/classes/errorResponse.d.ts +1 -1
  4. package/dist/classes/liteCheckout.d.ts +32 -56
  5. package/dist/data/api.d.ts +1 -1
  6. package/dist/helpers/utils.d.ts +4 -8
  7. package/dist/index.d.ts +1 -2
  8. package/dist/index.js +1 -1
  9. package/dist/types/commons.d.ts +0 -37
  10. package/dist/types/requests.d.ts +3 -12
  11. package/dist/types/responses.d.ts +3 -0
  12. package/jest.config.ts +14 -14
  13. package/package.json +38 -38
  14. package/rollup.config.js +16 -16
  15. package/src/classes/3dsHandler.ts +237 -237
  16. package/src/classes/errorResponse.ts +16 -16
  17. package/src/classes/liteCheckout.ts +575 -598
  18. package/src/data/api.ts +20 -20
  19. package/src/helpers/constants.ts +63 -63
  20. package/src/helpers/mercadopago.ts +15 -15
  21. package/src/helpers/utils.ts +320 -120
  22. package/src/index.ts +4 -10
  23. package/src/types/commons.ts +83 -125
  24. package/src/types/requests.ts +105 -114
  25. package/src/types/responses.ts +193 -189
  26. package/src/types/skyflow.ts +17 -17
  27. package/tests/classes/liteCheckout.test.ts +57 -57
  28. package/tests/methods/createOrder.test.ts +142 -142
  29. package/tests/methods/createPayment.test.ts +122 -122
  30. package/tests/methods/customerRegister.test.ts +119 -119
  31. package/tests/methods/getBusiness.test.ts +115 -115
  32. package/tests/methods/getCustomerCards.test.ts +119 -113
  33. package/tests/methods/getOpenpayDeviceSessionID.test.ts +95 -0
  34. package/tests/methods/getSkyflowToken.test.ts +155 -0
  35. package/tests/methods/getVaultToken.test.ts +107 -0
  36. package/tests/methods/registerCustomerCard.test.ts +117 -117
  37. package/tests/methods/startCheckoutRouter.test.ts +119 -119
  38. package/tests/methods/startCheckoutRouterFull.test.ts +138 -138
  39. package/tests/utils/defaultMock.ts +20 -21
  40. package/tests/utils/mockClasses.ts +656 -659
  41. package/tsconfig.json +18 -18
  42. package/.idea/aws.xml +0 -17
  43. package/.idea/inspectionProfiles/Project_Default.xml +0 -6
  44. package/.idea/prettier.xml +0 -6
  45. package/.idea/vcs.xml +0 -6
  46. package/.idea/workspace.xml +0 -132
  47. package/dist/classes/BaseInlineCheckout.d.ts +0 -45
  48. package/dist/data/businessApi.d.ts +0 -2
  49. package/dist/data/cardApi.d.ts +0 -4
  50. package/dist/data/checkoutApi.d.ts +0 -4
  51. package/dist/data/customerApi.d.ts +0 -2
  52. package/dist/data/openPayApi.d.ts +0 -1
  53. package/dist/data/paymentMethodApi.d.ts +0 -5
  54. package/dist/data/skyflowApi.d.ts +0 -1
  55. package/dist/helpers/skyflow.d.ts +0 -3
  56. package/dist/helpers/validations.d.ts +0 -6
  57. package/dist/shared/catalog/paymentMethodsCatalog.d.ts +0 -1
  58. package/dist/shared/constants/messages.d.ts +0 -11
  59. package/dist/shared/constants/paymentMethodAPM.d.ts +0 -62
  60. package/dist/shared/constants/tonderUrl.d.ts +0 -7
  61. package/dist/types/card.d.ts +0 -29
  62. package/dist/types/checkout.d.ts +0 -103
  63. package/dist/types/customer.d.ts +0 -12
  64. package/dist/types/paymentMethod.d.ts +0 -22
  65. package/src/classes/BaseInlineCheckout.ts +0 -356
  66. package/src/data/businessApi.ts +0 -18
  67. package/src/data/cardApi.ts +0 -89
  68. package/src/data/checkoutApi.ts +0 -87
  69. package/src/data/customerApi.ts +0 -31
  70. package/src/data/openPayApi.ts +0 -12
  71. package/src/data/paymentMethodApi.ts +0 -37
  72. package/src/data/skyflowApi.ts +0 -20
  73. package/src/helpers/skyflow.ts +0 -91
  74. package/src/helpers/validations.ts +0 -55
  75. package/src/shared/catalog/paymentMethodsCatalog.ts +0 -248
  76. package/src/shared/constants/messages.ts +0 -11
  77. package/src/shared/constants/paymentMethodAPM.ts +0 -63
  78. package/src/shared/constants/tonderUrl.ts +0 -8
  79. package/src/types/card.ts +0 -34
  80. package/src/types/checkout.ts +0 -118
  81. package/src/types/customer.ts +0 -12
  82. package/src/types/index.d.ts +0 -10
  83. package/src/types/liteInlineCheckout.d.ts +0 -191
  84. package/src/types/paymentMethod.ts +0 -24
  85. package/src/types/validations.d.ts +0 -11
@@ -1,598 +1,575 @@
1
- import { fetchBusiness } from "../data/businessApi";
2
-
3
- declare const MP_DEVICE_SESSION_ID: string | undefined;
4
- import { ErrorResponse } from "./errorResponse";
5
- import {
6
- buildErrorResponse,
7
- buildErrorResponseFromCatch,
8
- getBrowserInfo,
9
- getBusinessId,
10
- formatPublicErrorResponse,
11
- getCardType,
12
- } from "../helpers/utils";
13
- import { getCustomerAPMs } from "../data/api";
14
- import { BaseInlineCheckout } from "./BaseInlineCheckout";
15
- import {
16
- fetchCustomerCards,
17
- removeCustomerCard,
18
- saveCustomerCard,
19
- } from "../data/cardApi";
20
- import { MESSAGES } from "../shared/constants/messages";
21
- import { getSkyflowTokens } from "../helpers/skyflow";
22
- import { fetchCustomerAPMs } from "../data/paymentMethodApi";
23
- import { startCheckoutRouter } from "../data/checkoutApi";
24
- import { getOpenpayDeviceSessionID } from "../data/openPayApi";
25
- import { getPaymentMethodDetails } from "../shared/catalog/paymentMethodsCatalog";
26
- import {
27
- APM,
28
- IInlineCheckoutBaseOptions,
29
- TonderAPM,
30
- RegisterCustomerCardResponse,
31
- ISaveCardRequest,
32
- ICustomerCardsResponse,
33
- ISaveCardResponse,
34
- IPaymentMethod,
35
- GetBusinessResponse,
36
- TokensRequest,
37
- ICardFields,
38
- CustomerRegisterResponse,
39
- StartCheckoutFullRequest,
40
- CreateOrderRequest,
41
- CreatePaymentRequest,
42
- StartCheckoutRequest,
43
- IErrorResponse,
44
- StartCheckoutResponse,
45
- StartCheckoutIdRequest,
46
- CreatePaymentResponse,
47
- CreateOrderResponse,
48
- RegisterCustomerCardRequest,
49
- } from "../types";
50
-
51
- declare global {
52
- interface Window {
53
- OpenPay: any;
54
- }
55
- }
56
-
57
- export interface LiteCheckoutConstructor extends IInlineCheckoutBaseOptions {}
58
-
59
- export class LiteCheckout extends BaseInlineCheckout {
60
- activeAPMs: APM[] = [];
61
-
62
- constructor({ apiKey, mode, returnUrl, callBack }: LiteCheckoutConstructor) {
63
- super({ mode, apiKey, returnUrl, callBack });
64
- }
65
-
66
- async injectCheckout() {
67
- await this._initializeCheckout();
68
- }
69
-
70
- async getCustomerCards(): Promise<ICustomerCardsResponse> {
71
- try {
72
- await this._fetchMerchantData();
73
- const { auth_token } = await this._getCustomer();
74
- const response = await fetchCustomerCards(
75
- this.baseUrl,
76
- auth_token,
77
- this.merchantData!.business.pk,
78
- );
79
-
80
- return {
81
- ...response,
82
- cards: response.cards.map((ic) => ({
83
- ...ic,
84
- icon: getCardType(ic.fields.card_scheme),
85
- })),
86
- };
87
- } catch (error) {
88
- throw formatPublicErrorResponse(
89
- {
90
- message: MESSAGES.getCardsError,
91
- },
92
- error,
93
- );
94
- }
95
- }
96
-
97
- async saveCustomerCard(card: ISaveCardRequest): Promise<ISaveCardResponse> {
98
- try {
99
- await this._fetchMerchantData();
100
- const { auth_token } = await this._getCustomer();
101
- const { vault_id, vault_url, business } = this.merchantData!;
102
-
103
- const skyflowTokens = await getSkyflowTokens({
104
- vault_id: vault_id,
105
- vault_url: vault_url,
106
- data: card,
107
- baseUrl: this.baseUrl,
108
- apiKey: this.apiKeyTonder,
109
- });
110
-
111
- return await saveCustomerCard(
112
- this.baseUrl,
113
- auth_token,
114
- business?.pk,
115
- skyflowTokens,
116
- );
117
- } catch (error) {
118
- throw formatPublicErrorResponse(
119
- {
120
- message: MESSAGES.saveCardError,
121
- },
122
- error,
123
- );
124
- }
125
- }
126
-
127
- async removeCustomerCard(skyflowId: string): Promise<string> {
128
- try {
129
- await this._fetchMerchantData();
130
- const { auth_token } = await this._getCustomer();
131
- const { business } = this.merchantData!;
132
-
133
- return await removeCustomerCard(
134
- this.baseUrl,
135
- auth_token,
136
- skyflowId,
137
- business?.pk,
138
- );
139
- } catch (error) {
140
- throw formatPublicErrorResponse(
141
- {
142
- message: MESSAGES.removeCardError,
143
- },
144
- error,
145
- );
146
- }
147
- }
148
-
149
- async getCustomerPaymentMethods(): Promise<IPaymentMethod[]> {
150
- try {
151
- const response = await fetchCustomerAPMs(this.baseUrl, this.apiKeyTonder);
152
-
153
- const apms_results =
154
- response && "results" in response && response["results"].length > 0
155
- ? response["results"]
156
- : [];
157
-
158
- return apms_results
159
- .filter((apmItem) => apmItem.category.toLowerCase() !== "cards")
160
- .map((apmItem) => {
161
- const apm = {
162
- id: apmItem.pk,
163
- payment_method: apmItem.payment_method,
164
- priority: apmItem.priority,
165
- category: apmItem.category,
166
- ...getPaymentMethodDetails(apmItem.payment_method),
167
- };
168
- return apm;
169
- })
170
- .sort((a, b) => a.priority - b.priority);
171
- } catch (error) {
172
- throw formatPublicErrorResponse(
173
- {
174
- message: MESSAGES.getPaymentMethodsError,
175
- },
176
- error,
177
- );
178
- }
179
- }
180
-
181
- async getBusiness(): Promise<GetBusinessResponse> {
182
- try {
183
- return await fetchBusiness(
184
- this.baseUrl,
185
- this.apiKeyTonder,
186
- this.abortController.signal,
187
- );
188
- } catch (e) {
189
- throw formatPublicErrorResponse(
190
- {
191
- message: MESSAGES.getBusinessError,
192
- },
193
- e,
194
- );
195
- }
196
- }
197
-
198
- // TODO: DEPRECATED
199
- async getSkyflowTokens({
200
- vault_id,
201
- vault_url,
202
- data,
203
- }: TokensRequest): Promise<any | ErrorResponse> {
204
- return await getSkyflowTokens({
205
- vault_id: vault_id,
206
- vault_url: vault_url,
207
- data,
208
- baseUrl: this.baseUrl,
209
- apiKey: this.apiKeyTonder,
210
- });
211
- }
212
-
213
- _setCartTotal(total: string) {
214
- this.cartTotal = total;
215
- }
216
-
217
- async _checkout({
218
- card,
219
- payment_method,
220
- isSandbox,
221
- }: {
222
- card?: ICardFields | string;
223
- payment_method?: string;
224
- isSandbox?: boolean;
225
- }) {
226
- await this._fetchMerchantData();
227
- const customer = await this._getCustomer(this.abortController.signal);
228
- const { vault_id, vault_url } = this.merchantData!;
229
- let skyflowTokens;
230
- if (!payment_method || payment_method !== "" || payment_method === null) {
231
- if (typeof card === "string") {
232
- skyflowTokens = {
233
- skyflow_id: card,
234
- };
235
- } else {
236
- skyflowTokens = await getSkyflowTokens({
237
- vault_id: vault_id,
238
- vault_url: vault_url,
239
- data: { ...card, card_number: card!.card_number.replace(/\s+/g, "") },
240
- baseUrl: this.baseUrl,
241
- apiKey: this.apiKeyTonder,
242
- });
243
- }
244
- }
245
-
246
- return await this._handleCheckout({
247
- card: skyflowTokens,
248
- payment_method,
249
- customer,
250
- isSandbox,
251
- });
252
- }
253
-
254
- // TODO: DEPRECATED
255
- /**
256
- * @deprecated This method is deprecated and will be removed in a future release.
257
- * It is no longer necessary to use this method as customer registration is now automatically handled
258
- * during the payment process or when using card management methods.
259
- */
260
- async customerRegister(
261
- email: string,
262
- ): Promise<CustomerRegisterResponse | ErrorResponse> {
263
- try {
264
- const url = `${this.baseUrl}/api/v1/customer/`;
265
- const data = { email: email };
266
- const response = await fetch(url, {
267
- method: "POST",
268
- headers: {
269
- "Content-Type": "application/json",
270
- Authorization: `Token ${this.apiKeyTonder}`,
271
- },
272
- signal: this.abortController.signal,
273
- body: JSON.stringify(data),
274
- });
275
-
276
- if (response.ok)
277
- return (await response.json()) as CustomerRegisterResponse;
278
- throw await buildErrorResponse(response);
279
- } catch (e) {
280
- throw buildErrorResponseFromCatch(e);
281
- }
282
- }
283
-
284
- // TODO: DEPRECATED
285
- /**
286
- * @deprecated This method is deprecated and will be removed in a future release.
287
- * It is no longer necessary to use this method as order creation is now automatically
288
- * handled when making a payment through the `payment` function.
289
- */
290
- async createOrder(
291
- orderItems: CreateOrderRequest,
292
- ): Promise<CreateOrderResponse | ErrorResponse> {
293
- try {
294
- const url = `${this.baseUrl}/api/v1/orders/`;
295
- const data = orderItems;
296
- const response = await fetch(url, {
297
- method: "POST",
298
- headers: {
299
- "Content-Type": "application/json",
300
- Authorization: `Token ${this.apiKeyTonder}`,
301
- },
302
- body: JSON.stringify(data),
303
- });
304
- if (response.ok) return (await response.json()) as CreateOrderResponse;
305
- throw await buildErrorResponse(response);
306
- } catch (e) {
307
- throw buildErrorResponseFromCatch(e);
308
- }
309
- }
310
-
311
- // TODO: DEPRECATED
312
- /**
313
- * @deprecated This method is deprecated and will be removed in a future release.
314
- * It is no longer necessary to use this method as payment creation is now automatically
315
- * handled when making a payment through the `payment` function.
316
- */
317
- async createPayment(
318
- paymentItems: CreatePaymentRequest,
319
- ): Promise<CreatePaymentResponse | ErrorResponse> {
320
- try {
321
- const url = `${this.baseUrl}/api/v1/business/${paymentItems.business_pk}/payments/`;
322
- const data = paymentItems;
323
- const response = await fetch(url, {
324
- method: "POST",
325
- headers: {
326
- "Content-Type": "application/json",
327
- Authorization: `Token ${this.apiKeyTonder}`,
328
- },
329
- body: JSON.stringify(data),
330
- });
331
- if (response.ok) return (await response.json()) as CreatePaymentResponse;
332
- throw await buildErrorResponse(response);
333
- } catch (e) {
334
- throw buildErrorResponseFromCatch(e);
335
- }
336
- }
337
-
338
- // TODO: DEPRECATED
339
- /**
340
- * @deprecated This method is deprecated and will be removed in a future release.
341
- * Use the {@link payment} method
342
- */
343
- async startCheckoutRouter(
344
- routerData: StartCheckoutRequest | StartCheckoutIdRequest,
345
- ): Promise<StartCheckoutResponse | ErrorResponse | undefined> {
346
- const checkoutResult = await startCheckoutRouter(
347
- this.baseUrl,
348
- this.apiKeyTonder,
349
- routerData,
350
- );
351
- const payload = await this.init3DSRedirect(checkoutResult);
352
- if (payload) return checkoutResult;
353
- }
354
-
355
- // TODO: DEPRECATED
356
- async init3DSRedirect(checkoutResult: ErrorResponse | StartCheckoutResponse) {
357
- this.process3ds.setPayload(checkoutResult);
358
- return await this._handle3dsRedirect(checkoutResult);
359
- }
360
-
361
- // TODO: DEPRECATED
362
- /**
363
- * @deprecated This method is deprecated and will be removed in a future release.
364
- * Use the {@link payment} method
365
- */
366
- async startCheckoutRouterFull(
367
- routerFullData: StartCheckoutFullRequest,
368
- ): Promise<StartCheckoutResponse | ErrorResponse | undefined> {
369
- try {
370
- const {
371
- order,
372
- total,
373
- customer,
374
- skyflowTokens,
375
- return_url,
376
- isSandbox,
377
- metadata,
378
- currency,
379
- payment_method,
380
- } = routerFullData;
381
-
382
- const merchantResult = await this._fetchMerchantData();
383
-
384
- const customerResult: CustomerRegisterResponse | ErrorResponse =
385
- await this.customerRegister(customer.email);
386
-
387
- if (
388
- customerResult &&
389
- "auth_token" in customerResult &&
390
- merchantResult &&
391
- "reference" in merchantResult
392
- ) {
393
- const orderData: CreateOrderRequest = {
394
- business: this.apiKeyTonder,
395
- client: customerResult.auth_token,
396
- billing_address_id: null,
397
- shipping_address_id: null,
398
- amount: total,
399
- reference: merchantResult.reference,
400
- is_oneclick: true,
401
- items: order.items,
402
- };
403
-
404
- const orderResult = await this.createOrder(orderData);
405
-
406
- const now = new Date();
407
-
408
- const dateString = now.toISOString();
409
-
410
- if (
411
- "id" in orderResult &&
412
- "id" in customerResult &&
413
- "business" in merchantResult
414
- ) {
415
- const paymentItems: CreatePaymentRequest = {
416
- business_pk: merchantResult.business.pk,
417
- amount: total,
418
- date: dateString,
419
- order_id: orderResult.id,
420
- client_id: customerResult.id,
421
- };
422
-
423
- const paymentResult = await this.createPayment(paymentItems);
424
-
425
- let deviceSessionIdTonder: any;
426
-
427
- const { openpay_keys, business } = merchantResult;
428
-
429
- if (openpay_keys.merchant_id && openpay_keys.public_key) {
430
- deviceSessionIdTonder = await getOpenpayDeviceSessionID(
431
- openpay_keys.merchant_id,
432
- openpay_keys.public_key,
433
- isSandbox,
434
- );
435
- }
436
-
437
- const routerItems: StartCheckoutRequest = {
438
- name: customer.name,
439
- last_name: customer.lastname,
440
- email_client: customer.email,
441
- phone_number: customer.phone,
442
- return_url: return_url,
443
- id_product: "no_id",
444
- quantity_product: 1,
445
- id_ship: "0",
446
- instance_id_ship: "0",
447
- amount: total,
448
- title_ship: "shipping",
449
- description: "transaction",
450
- device_session_id: deviceSessionIdTonder
451
- ? deviceSessionIdTonder
452
- : null,
453
- token_id: "",
454
- order_id: "id" in orderResult && orderResult.id,
455
- business_id: business.pk,
456
- payment_id: "pk" in paymentResult && paymentResult.pk,
457
- source: "sdk",
458
- metadata: metadata,
459
- browser_info: getBrowserInfo(),
460
- currency: currency,
461
- ...(!!payment_method
462
- ? { payment_method }
463
- : { card: skyflowTokens }),
464
- ...(typeof MP_DEVICE_SESSION_ID !== "undefined"
465
- ? { mp_device_session_id: MP_DEVICE_SESSION_ID }
466
- : {}),
467
- };
468
-
469
- const checkoutResult = await startCheckoutRouter(
470
- this.baseUrl,
471
- this.apiKeyTonder,
472
- routerItems,
473
- );
474
- const payload = await this.init3DSRedirect(checkoutResult);
475
- if (payload) return checkoutResult;
476
- } else {
477
- throw new ErrorResponse({
478
- code: "500",
479
- body: orderResult as any,
480
- name: "Keys error",
481
- message: "Order response errors",
482
- } as IErrorResponse);
483
- }
484
- } else {
485
- throw new ErrorResponse({
486
- code: "500",
487
- body: merchantResult as any,
488
- name: "Keys error",
489
- message: "Merchant or customer reposne errors",
490
- } as IErrorResponse);
491
- }
492
- } catch (e) {
493
- throw buildErrorResponseFromCatch(e);
494
- }
495
- }
496
-
497
- // TODO: DEPRECATED
498
- /**
499
- * @deprecated This method is deprecated and will be removed in a future release.
500
- * Use the {@link saveCustomerCard} method
501
- */
502
- async registerCustomerCard(
503
- customerToken: string,
504
- data: RegisterCustomerCardRequest,
505
- ): Promise<RegisterCustomerCardResponse | ErrorResponse> {
506
- try {
507
- await this._fetchMerchantData();
508
-
509
- const response = await fetch(
510
- `${this.baseUrl}/api/v1/business/${getBusinessId(this.merchantData)}/cards/`,
511
- {
512
- method: "POST",
513
- headers: {
514
- Authorization: `Token ${customerToken}`,
515
- "Content-Type": "application/json",
516
- },
517
- body: JSON.stringify({ ...data }),
518
- },
519
- );
520
-
521
- if (response.ok)
522
- return (await response.json()) as RegisterCustomerCardResponse;
523
- throw await buildErrorResponse(response);
524
- } catch (error) {
525
- throw buildErrorResponseFromCatch(error);
526
- }
527
- }
528
-
529
- // TODO: DEPRECATED
530
- /**
531
- * @deprecated This method is deprecated and will be removed in a future release.
532
- * Use the {@link removeCustomerCard} method
533
- */
534
- async deleteCustomerCard(
535
- customerToken: string,
536
- skyflowId: string = "",
537
- ): Promise<Boolean | ErrorResponse> {
538
- try {
539
- await this._fetchMerchantData();
540
- const response = await fetch(
541
- `${this.baseUrl}/api/v1/business/${getBusinessId(this.merchantData)}/cards/${skyflowId}`,
542
- {
543
- method: "DELETE",
544
- headers: {
545
- Authorization: `Token ${customerToken}`,
546
- "Content-Type": "application/json",
547
- },
548
- signal: this.abortController.signal,
549
- },
550
- );
551
-
552
- if (response.ok) return true;
553
- throw await buildErrorResponse(response);
554
- } catch (error) {
555
- throw buildErrorResponseFromCatch(error);
556
- }
557
- }
558
-
559
- // TODO: DEPRECATED
560
- /**
561
- * @deprecated This method is deprecated and will be removed in a future release.
562
- * Use the {@link getCustomerPaymentMethods} method
563
- */
564
- async getActiveAPMs(): Promise<APM[]> {
565
- try {
566
- const apms_response = await getCustomerAPMs(
567
- this.baseUrl,
568
- this.apiKeyTonder,
569
- );
570
- const apms_results =
571
- apms_response &&
572
- apms_response["results"] &&
573
- apms_response["results"].length > 0
574
- ? apms_response["results"]
575
- : [];
576
- this.activeAPMs = apms_results
577
- .filter(
578
- (apmItem: TonderAPM) => apmItem.category.toLowerCase() !== "cards",
579
- )
580
- .map((apmItem: TonderAPM) => {
581
- const apm: APM = {
582
- id: apmItem.pk,
583
- payment_method: apmItem.payment_method,
584
- priority: apmItem.priority,
585
- category: apmItem.category,
586
- ...getPaymentMethodDetails(apmItem.payment_method),
587
- };
588
- return apm;
589
- })
590
- .sort((a: APM, b: APM) => a.priority - b.priority);
591
-
592
- return this.activeAPMs;
593
- } catch (e) {
594
- console.error("Error getting APMS", e);
595
- return [];
596
- }
597
- }
598
- }
1
+ import {injectMercadoPagoSecurity} from "../helpers/mercadopago";
2
+
3
+ declare const MP_DEVICE_SESSION_ID: string | undefined;
4
+
5
+ import Skyflow from "skyflow-js";
6
+ import CollectContainer from "skyflow-js/types/core/external/collect/collect-container";
7
+ import CollectElement from "skyflow-js/types/core/external/collect/collect-element";
8
+ import { APM, Business, TonderAPM } from "../types/commons";
9
+ import { CreateOrderRequest, CreatePaymentRequest, RegisterCustomerCardRequest, StartCheckoutRequest, TokensRequest, StartCheckoutFullRequest, StartCheckoutIdRequest } from "../types/requests";
10
+ import { GetSecureTokenResponse, GetBusinessResponse, CustomerRegisterResponse, CreateOrderResponse, CreatePaymentResponse, StartCheckoutResponse, GetVaultTokenResponse, IErrorResponse, GetCustomerCardsResponse, RegisterCustomerCardResponse } from "../types/responses";
11
+ import { ErrorResponse } from "./errorResponse";
12
+ import { buildErrorResponse, buildErrorResponseFromCatch, getBrowserInfo, getPaymentMethodDetails, getBusinessId } from "../helpers/utils";
13
+ import { ThreeDSHandler } from "./3dsHandler";
14
+ import { getCustomerAPMs } from "../data/api";
15
+
16
+ declare global {
17
+ interface Window {
18
+ OpenPay: any;
19
+ }
20
+ }
21
+
22
+ export type LiteCheckoutConstructor = {
23
+ signal: AbortSignal;
24
+ baseUrlTonder: string;
25
+ publicApiKeyTonder: string;
26
+ };
27
+
28
+ export class LiteCheckout implements LiteCheckoutConstructor {
29
+ signal: AbortSignal;
30
+ baseUrlTonder: string;
31
+ publicApiKeyTonder: string;
32
+ process3ds: ThreeDSHandler;
33
+ activeAPMs: APM[] = []
34
+ merchantData?: Business | ErrorResponse;
35
+
36
+ constructor({
37
+ signal,
38
+ baseUrlTonder,
39
+ publicApiKeyTonder,
40
+ }: LiteCheckoutConstructor) {
41
+ this.baseUrlTonder = baseUrlTonder;
42
+ this.signal = signal;
43
+ this.publicApiKeyTonder = publicApiKeyTonder;
44
+ this.process3ds = new ThreeDSHandler({
45
+ apiKey: this.publicApiKeyTonder,
46
+ baseUrl: this.baseUrlTonder,
47
+ })
48
+ this.#init()
49
+ }
50
+
51
+ async #init(){
52
+ this.getActiveAPMs()
53
+ const { mercado_pago } = await this.#fetchMerchantData() as Business
54
+ if (!!mercado_pago && mercado_pago.active){
55
+ injectMercadoPagoSecurity()
56
+ }
57
+ }
58
+
59
+ async getOpenpayDeviceSessionID(
60
+ merchant_id: string,
61
+ public_key: string,
62
+ is_sandbox: boolean
63
+ ): Promise<string | ErrorResponse> {
64
+ try {
65
+ let openpay = await window.OpenPay;
66
+ openpay.setId(merchant_id);
67
+ openpay.setApiKey(public_key);
68
+ openpay.setSandboxMode(is_sandbox);
69
+ return await openpay.deviceData.setup({
70
+ signal: this.signal,
71
+ }) as string;
72
+ } catch (e) {
73
+ throw buildErrorResponseFromCatch(e);
74
+ }
75
+ }
76
+ async #fetchMerchantData() {
77
+ try {
78
+ if (!this.merchantData){
79
+ this.merchantData = await this.getBusiness();
80
+ }
81
+ return this.merchantData
82
+ }catch(e){
83
+ return this.merchantData
84
+ }
85
+ }
86
+
87
+ async getBusiness(): Promise<GetBusinessResponse | ErrorResponse> {
88
+ try {
89
+ const getBusiness = await fetch(
90
+ `${this.baseUrlTonder}/api/v1/payments/business/${this.publicApiKeyTonder}`,
91
+ {
92
+ headers: {
93
+ Authorization: `Token ${this.publicApiKeyTonder}`,
94
+ },
95
+ signal: this.signal,
96
+ }
97
+ );
98
+
99
+ if (getBusiness.ok) return (await getBusiness.json()) as Business;
100
+
101
+ throw await buildErrorResponse(getBusiness);
102
+ } catch (e) {
103
+ throw buildErrorResponseFromCatch(e);
104
+ }
105
+ }
106
+
107
+ async verify3dsTransaction () {
108
+ const result3ds = await this.process3ds.verifyTransactionStatus()
109
+ const resultCheckout = await this.resumeCheckout(result3ds)
110
+ this.process3ds.setPayload(resultCheckout)
111
+ return this.handle3dsRedirect(resultCheckout)
112
+ }
113
+
114
+ async resumeCheckout(response: any) {
115
+ // Stop the routing process if the transaction is either hard declined or successful
116
+ if (response?.decline?.error_type === "Hard") {
117
+ return response
118
+ }
119
+
120
+ if (["Success", "Authorized"].includes(response?.transaction_status)) {
121
+ return response;
122
+ }
123
+
124
+ if (response) {
125
+ const routerItems = {
126
+ checkout_id: response?.checkout?.id,
127
+ };
128
+ try {
129
+ const routerResponse = await this.handleCheckoutRouter(
130
+ routerItems
131
+ );
132
+ return routerResponse
133
+ }catch (error){
134
+ // throw error
135
+ }
136
+ return response
137
+ }
138
+ }
139
+
140
+ async handle3dsRedirect(response: ErrorResponse | StartCheckoutResponse | false | undefined) {
141
+ const iframe = response && 'next_action' in response ? response?.next_action?.iframe_resources?.iframe:null
142
+ if (iframe) {
143
+ this.process3ds.loadIframe()!.then(() => {
144
+ //TODO: Check if this will be necessary on the frontend side
145
+ // after some the tests in production, since the 3DS process
146
+ // doesn't works properly on the sandbox environment
147
+ // setTimeout(() => {
148
+ // process3ds.verifyTransactionStatus();
149
+ // }, 10000);
150
+ this.process3ds.verifyTransactionStatus();
151
+ }).catch((error: any) => {
152
+ console.log('Error loading iframe:', error)
153
+ })
154
+ } else {
155
+ const redirectUrl = this.process3ds.getRedirectUrl()
156
+ if (redirectUrl) {
157
+ this.process3ds.redirectToChallenge()
158
+ } else {
159
+ return response;
160
+ }
161
+ }
162
+ }
163
+
164
+ async customerRegister(email: string): Promise<CustomerRegisterResponse | ErrorResponse> {
165
+ try {
166
+ const url = `${this.baseUrlTonder}/api/v1/customer/`;
167
+ const data = { email: email };
168
+ const response = await fetch(url, {
169
+ method: "POST",
170
+ headers: {
171
+ "Content-Type": "application/json",
172
+ Authorization: `Token ${this.publicApiKeyTonder}`,
173
+ },
174
+ signal: this.signal,
175
+ body: JSON.stringify(data),
176
+ });
177
+
178
+ if (response.ok) return await response.json() as CustomerRegisterResponse;
179
+ throw await buildErrorResponse(response);
180
+ } catch (e) {
181
+ throw buildErrorResponseFromCatch(e);
182
+ }
183
+ }
184
+
185
+ async createOrder(orderItems: CreateOrderRequest): Promise<CreateOrderResponse | ErrorResponse> {
186
+ try {
187
+ const url = `${this.baseUrlTonder}/api/v1/orders/`;
188
+ const data = orderItems;
189
+ const response = await fetch(url, {
190
+ method: "POST",
191
+ headers: {
192
+ "Content-Type": "application/json",
193
+ Authorization: `Token ${this.publicApiKeyTonder}`,
194
+ },
195
+ body: JSON.stringify(data),
196
+ });
197
+ if (response.ok) return await response.json() as CreateOrderResponse;
198
+ throw await buildErrorResponse(response);
199
+ } catch (e) {
200
+ throw buildErrorResponseFromCatch(e);
201
+ }
202
+ }
203
+
204
+ async createPayment(paymentItems: CreatePaymentRequest): Promise<CreatePaymentResponse | ErrorResponse> {
205
+ try {
206
+ const url = `${this.baseUrlTonder}/api/v1/business/${paymentItems.business_pk}/payments/`;
207
+ const data = paymentItems;
208
+ const response = await fetch(url, {
209
+ method: "POST",
210
+ headers: {
211
+ "Content-Type": "application/json",
212
+ Authorization: `Token ${this.publicApiKeyTonder}`,
213
+ },
214
+ body: JSON.stringify(data),
215
+ });
216
+ if (response.ok) return await response.json() as CreatePaymentResponse;
217
+ throw await buildErrorResponse(response);
218
+ } catch (e) {
219
+ throw buildErrorResponseFromCatch(e);
220
+ }
221
+ }
222
+ async handleCheckoutRouter(routerData: StartCheckoutRequest | StartCheckoutIdRequest){
223
+ try {
224
+ const url = `${this.baseUrlTonder}/api/v1/checkout-router/`;
225
+ const data = routerData;
226
+ const response = await fetch(url, {
227
+ method: "POST",
228
+ headers: {
229
+ "Content-Type": "application/json",
230
+ Authorization: `Token ${this.publicApiKeyTonder}`,
231
+ },
232
+ body: JSON.stringify({...data, ...(typeof MP_DEVICE_SESSION_ID !== "undefined" ? {mp_device_session_id: MP_DEVICE_SESSION_ID}:{})}),
233
+ });
234
+ if (response.ok) return await response.json() as StartCheckoutResponse;
235
+ throw await buildErrorResponse(response);
236
+ } catch (e) {
237
+ throw buildErrorResponseFromCatch(e);
238
+ }
239
+ }
240
+
241
+ async startCheckoutRouter(routerData: StartCheckoutRequest | StartCheckoutIdRequest): Promise<StartCheckoutResponse | ErrorResponse | undefined> {
242
+ const checkoutResult = await this.handleCheckoutRouter(routerData);
243
+ const payload = await this.init3DSRedirect(checkoutResult)
244
+ if(payload)
245
+ return checkoutResult;
246
+ }
247
+
248
+ async startCheckoutRouterFull(routerFullData: StartCheckoutFullRequest): Promise<StartCheckoutResponse | ErrorResponse | undefined> {
249
+
250
+ try {
251
+
252
+ const {
253
+ order,
254
+ total,
255
+ customer,
256
+ skyflowTokens,
257
+ return_url,
258
+ isSandbox,
259
+ metadata,
260
+ currency,
261
+ payment_method
262
+ } = routerFullData;
263
+
264
+ const merchantResult = await this.getBusiness();
265
+
266
+ const customerResult : CustomerRegisterResponse | ErrorResponse = await this.customerRegister(customer.email);
267
+
268
+ if(customerResult && "auth_token" in customerResult && merchantResult && "reference" in merchantResult) {
269
+
270
+ const orderData: CreateOrderRequest = {
271
+ business: this.publicApiKeyTonder,
272
+ client: customerResult.auth_token,
273
+ billing_address_id: null,
274
+ shipping_address_id: null,
275
+ amount: total,
276
+ reference: merchantResult.reference,
277
+ is_oneclick: true,
278
+ items: order.items,
279
+ };
280
+
281
+ const orderResult = await this.createOrder(orderData);
282
+
283
+ const now = new Date();
284
+
285
+ const dateString = now.toISOString();
286
+
287
+ if("id" in orderResult && "id" in customerResult && "business" in merchantResult) {
288
+
289
+ const paymentItems: CreatePaymentRequest = {
290
+ business_pk: merchantResult.business.pk,
291
+ amount: total,
292
+ date: dateString,
293
+ order_id: orderResult.id,
294
+ client_id: customerResult.id
295
+ };
296
+
297
+ const paymentResult = await this.createPayment(
298
+ paymentItems
299
+ );
300
+
301
+ let deviceSessionIdTonder: any;
302
+
303
+ const { openpay_keys, business } = merchantResult
304
+
305
+ if (openpay_keys.merchant_id && openpay_keys.public_key) {
306
+ deviceSessionIdTonder = await this.getOpenpayDeviceSessionID(
307
+ openpay_keys.merchant_id,
308
+ openpay_keys.public_key,
309
+ isSandbox
310
+ );
311
+ }
312
+
313
+ const routerItems: StartCheckoutRequest = {
314
+ name: customer.name,
315
+ last_name: customer.lastname,
316
+ email_client: customer.email,
317
+ phone_number: customer.phone,
318
+ return_url: return_url,
319
+ id_product: "no_id",
320
+ quantity_product: 1,
321
+ id_ship: "0",
322
+ instance_id_ship: "0",
323
+ amount: total,
324
+ title_ship: "shipping",
325
+ description: "transaction",
326
+ device_session_id: deviceSessionIdTonder ? deviceSessionIdTonder : null,
327
+ token_id: "",
328
+ order_id: ("id" in orderResult) && orderResult.id,
329
+ business_id: business.pk,
330
+ payment_id: ("pk" in paymentResult) && paymentResult.pk,
331
+ source: 'sdk',
332
+ metadata: metadata,
333
+ browser_info: getBrowserInfo(),
334
+ currency: currency,
335
+ ...( !!payment_method
336
+ ? {payment_method}
337
+ : {card: skyflowTokens}
338
+ ),
339
+ ...(typeof MP_DEVICE_SESSION_ID !== "undefined" ? {mp_device_session_id: MP_DEVICE_SESSION_ID}:{})
340
+ };
341
+
342
+ const checkoutResult = await this.handleCheckoutRouter(routerItems);
343
+ const payload = await this.init3DSRedirect(checkoutResult)
344
+ if(payload)
345
+ return checkoutResult;
346
+ } else {
347
+
348
+ throw new ErrorResponse({
349
+ code: "500",
350
+ body: orderResult as any,
351
+ name: "Keys error",
352
+ message: "Order response errors"
353
+ } as IErrorResponse)
354
+
355
+ }
356
+
357
+ } else {
358
+
359
+ throw new ErrorResponse({
360
+ code: "500",
361
+ body: merchantResult as any,
362
+ name: "Keys error",
363
+ message: "Merchant or customer reposne errors"
364
+ } as IErrorResponse)
365
+
366
+ }
367
+ } catch (e) {
368
+
369
+ throw buildErrorResponseFromCatch(e);
370
+
371
+ }
372
+ }
373
+
374
+ async init3DSRedirect(checkoutResult: ErrorResponse | StartCheckoutResponse){
375
+ this.process3ds.setPayload(checkoutResult)
376
+ return await this.handle3dsRedirect(checkoutResult)
377
+ }
378
+
379
+ async getSkyflowTokens({
380
+ vault_id,
381
+ vault_url,
382
+ data,
383
+ }: TokensRequest): Promise<any | ErrorResponse> {
384
+ const skyflow = Skyflow.init({
385
+ vaultID: vault_id,
386
+ vaultURL: vault_url,
387
+ getBearerToken: async () => await this.getVaultToken(),
388
+ options: {
389
+ logLevel: Skyflow.LogLevel.ERROR,
390
+ env: Skyflow.Env.DEV,
391
+ },
392
+ });
393
+
394
+ const collectContainer: CollectContainer = skyflow.container(
395
+ Skyflow.ContainerType.COLLECT
396
+ ) as CollectContainer;
397
+
398
+ const fieldPromises = await this.getFieldsPromise(data, collectContainer);
399
+
400
+ const result = await Promise.all(fieldPromises);
401
+
402
+ const mountFail = result.some((item: boolean) => !item);
403
+
404
+ if (mountFail) {
405
+ throw buildErrorResponseFromCatch(Error("Ocurrió un error al montar los campos de la tarjeta"));
406
+ } else {
407
+ try {
408
+ const collectResponseSkyflowTonder = await collectContainer.collect() as any;
409
+ if (collectResponseSkyflowTonder) return collectResponseSkyflowTonder["records"][0]["fields"];
410
+ throw buildErrorResponseFromCatch(Error("Por favor, verifica todos los campos de tu tarjeta"))
411
+ } catch (error) {
412
+ throw buildErrorResponseFromCatch(error);
413
+ }
414
+ }
415
+ }
416
+
417
+ async getVaultToken(): Promise<string> {
418
+ try {
419
+ const response = await fetch(`${this.baseUrlTonder}/api/v1/vault-token/`, {
420
+ method: "GET",
421
+ headers: {
422
+ Authorization: `Token ${this.publicApiKeyTonder}`,
423
+ },
424
+ signal: this.signal,
425
+ });
426
+ if (response.ok) return (await response.json() as GetVaultTokenResponse)?.token;
427
+ throw new Error(`HTTPCODE: ${response.status}`)
428
+ } catch (e) {
429
+ throw new Error(`Failed to retrieve bearer token; ${typeof e == "string" ? e : (e as Error).message}`)
430
+ }
431
+ }
432
+
433
+ async getFieldsPromise(data: any, collectContainer: CollectContainer): Promise<Promise<boolean>[]> {
434
+ const fields = await this.getFields(data, collectContainer);
435
+ if (!fields) return [];
436
+
437
+ return fields.map((field: { element: CollectElement, key: string }) => {
438
+ return new Promise((resolve) => {
439
+ const div = document.createElement("div");
440
+ div.hidden = true;
441
+ div.id = `id-${field.key}`;
442
+ document.querySelector(`body`)?.appendChild(div);
443
+ setTimeout(() => {
444
+ field.element.mount(`#id-${field.key}`);
445
+ setInterval(() => {
446
+ if (field.element.isMounted()) {
447
+ const value = data[field.key];
448
+ field.element.update({ value: value });
449
+ return resolve(field.element.isMounted());
450
+ }
451
+ }, 120);
452
+ }, 120);
453
+ });
454
+ })
455
+ }
456
+
457
+ async registerCustomerCard(secureToken: string, customerToken: string, data: RegisterCustomerCardRequest): Promise<RegisterCustomerCardResponse | ErrorResponse> {
458
+ try {
459
+ await this.#fetchMerchantData()
460
+
461
+ const response = await fetch(`${this.baseUrlTonder}/api/v1/business/${getBusinessId(this.merchantData)}/cards/`, {
462
+ method: 'POST',
463
+ headers: {
464
+ 'Authorization': `Bearer ${secureToken}`,
465
+ 'User-token': customerToken,
466
+ 'Content-Type': 'application/json'
467
+ },
468
+ signal: this.signal,
469
+ body: JSON.stringify({...data})
470
+ });
471
+
472
+ if (response.ok) return await response.json() as RegisterCustomerCardResponse;
473
+ throw await buildErrorResponse(response);
474
+ } catch (error) {
475
+ throw buildErrorResponseFromCatch(error);
476
+ }
477
+ }
478
+
479
+ async getCustomerCards(customerToken: string, bearerToken: string): Promise<GetCustomerCardsResponse | ErrorResponse> {
480
+ try {
481
+ await this.#fetchMerchantData()
482
+
483
+ const response = await fetch(`${this.baseUrlTonder}/api/v1/business/${getBusinessId(this.merchantData)}/cards`, {
484
+ method: 'GET',
485
+ headers: {
486
+ 'Authorization': `Bearer ${bearerToken}`,
487
+ 'User-Token': customerToken,
488
+ 'Content-Type': 'application/json',
489
+ 'Accept': 'application/json'
490
+ },
491
+ signal: this.signal,
492
+ });
493
+
494
+ if (response.ok) return await response.json() as GetCustomerCardsResponse;
495
+ throw await buildErrorResponse(response);
496
+ } catch (error) {
497
+ throw buildErrorResponseFromCatch(error);
498
+ }
499
+ }
500
+
501
+ async deleteCustomerCard(customerToken: string, skyflowId: string = ""): Promise<Boolean | ErrorResponse> {
502
+ try {
503
+ await this.#fetchMerchantData()
504
+ const response = await fetch(`${this.baseUrlTonder}/api/v1/business/${getBusinessId(this.merchantData)}/cards/${skyflowId}`, {
505
+ method: 'DELETE',
506
+ headers: {
507
+ 'Authorization': `Token ${customerToken}`,
508
+ 'Content-Type': 'application/json'
509
+ },
510
+ signal: this.signal,
511
+ });
512
+
513
+ if (response.ok) return true;
514
+ throw await buildErrorResponse(response);
515
+ } catch (error) {
516
+ throw buildErrorResponseFromCatch(error);
517
+ }
518
+ }
519
+
520
+ private async getFields(data: any, collectContainer: CollectContainer): Promise<{ element: CollectElement, key: string }[]> {
521
+ return await Promise.all(
522
+ Object.keys(data).map(async (key) => {
523
+ const cardHolderNameElement = await collectContainer.create({
524
+ table: "cards",
525
+ column: key,
526
+ type: Skyflow.ElementType.INPUT_FIELD,
527
+ });
528
+ return { element: cardHolderNameElement, key: key };
529
+ })
530
+ )
531
+ }
532
+
533
+ async getActiveAPMs(): Promise<APM[]> {
534
+ try {
535
+ const apms_response = await getCustomerAPMs(this.baseUrlTonder, this.publicApiKeyTonder);
536
+ const apms_results = apms_response && apms_response['results'] && apms_response['results'].length > 0 ? apms_response['results'] : []
537
+ this.activeAPMs = apms_results
538
+ .filter((apmItem: TonderAPM) =>
539
+ apmItem.category.toLowerCase() !== 'cards')
540
+ .map((apmItem: TonderAPM) => {
541
+ const apm: APM = {
542
+ id: apmItem.pk,
543
+ payment_method: apmItem.payment_method,
544
+ priority: apmItem.priority,
545
+ category: apmItem.category,
546
+ ...getPaymentMethodDetails(apmItem.payment_method,)
547
+ }
548
+ return apm;
549
+ }).sort((a: APM, b: APM) => a.priority - b.priority);
550
+
551
+ return this.activeAPMs
552
+ } catch (e) {
553
+ console.error("Error getting APMS", e);
554
+ return [];
555
+ }
556
+ }
557
+
558
+ async getSecureToken(token: string): Promise<GetSecureTokenResponse | ErrorResponse> {
559
+ try {
560
+ const response = await fetch(`${this.baseUrlTonder}/api/secure-token/`, {
561
+ method: 'POST',
562
+ headers: {
563
+ 'Authorization': `Token ${token}`,
564
+ 'Content-Type': 'application/json'
565
+ },
566
+ signal: this.signal,
567
+ });
568
+
569
+ if (response.ok) return await response.json() as GetSecureTokenResponse;
570
+ throw await buildErrorResponse(response);
571
+ } catch (error) {
572
+ throw buildErrorResponseFromCatch(error);
573
+ }
574
+ }
575
+ }