@tonder.io/ionic-lite-sdk 0.0.32-beta → 0.0.33-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.
Files changed (34) hide show
  1. package/.gitlab-ci.yml +28 -28
  2. package/README.md +202 -193
  3. package/dist/classes/3dsHandler.d.ts +36 -0
  4. package/dist/classes/liteCheckout.d.ts +13 -4
  5. package/dist/index.js +1 -1
  6. package/dist/types/requests.d.ts +3 -0
  7. package/dist/types/responses.d.ts +1 -0
  8. package/jest.config.ts +14 -14
  9. package/package.json +38 -38
  10. package/rollup.config.js +16 -16
  11. package/src/classes/3dsHandler.ts +254 -0
  12. package/src/classes/errorResponse.ts +16 -16
  13. package/src/classes/liteCheckout.ts +535 -462
  14. package/src/helpers/utils.ts +12 -12
  15. package/src/index.ts +4 -4
  16. package/src/types/commons.ts +62 -62
  17. package/src/types/requests.ts +93 -89
  18. package/src/types/responses.ts +188 -187
  19. package/src/types/skyflow.ts +17 -17
  20. package/tests/classes/liteCheckout.test.ts +57 -57
  21. package/tests/methods/createOrder.test.ts +142 -142
  22. package/tests/methods/createPayment.test.ts +122 -122
  23. package/tests/methods/customerRegister.test.ts +119 -119
  24. package/tests/methods/getBusiness.test.ts +115 -115
  25. package/tests/methods/getCustomerCards.test.ts +117 -117
  26. package/tests/methods/getOpenpayDeviceSessionID.test.ts +94 -94
  27. package/tests/methods/getSkyflowToken.test.ts +154 -154
  28. package/tests/methods/getVaultToken.test.ts +106 -106
  29. package/tests/methods/registerCustomerCard.test.ts +117 -117
  30. package/tests/methods/startCheckoutRouter.test.ts +119 -119
  31. package/tests/methods/startCheckoutRouterFull.test.ts +138 -138
  32. package/tests/utils/defaultMock.ts +20 -20
  33. package/tests/utils/mockClasses.ts +651 -649
  34. package/tsconfig.json +18 -18
@@ -1,462 +1,535 @@
1
- import Skyflow from "skyflow-js";
2
- import CollectContainer from "skyflow-js/types/core/external/collect/collect-container";
3
- import CollectElement from "skyflow-js/types/core/external/collect/collect-element";
4
- import { Business } from "../types/commons";
5
- import { CreateOrderRequest, CreatePaymentRequest, RegisterCustomerCardRequest, StartCheckoutRequest, TokensRequest, StartCheckoutFullRequest } from "../types/requests";
6
- import { GetBusinessResponse, CustomerRegisterResponse, CreateOrderResponse, CreatePaymentResponse, StartCheckoutResponse, GetVaultTokenResponse, IErrorResponse, GetCustomerCardsResponse, RegisterCustomerCardResponse } from "../types/responses";
7
- import { ErrorResponse } from "./errorResponse";
8
- import { getBrowserInfo } from "../helpers/utils";
9
-
10
- declare global {
11
- interface Window {
12
- OpenPay: any;
13
- }
14
- }
15
-
16
- export type LiteCheckoutConstructor = {
17
- signal: AbortSignal;
18
- baseUrlTonder: string;
19
- apiKeyTonder: string;
20
- };
21
-
22
- export class LiteCheckout implements LiteCheckoutConstructor {
23
- signal: AbortSignal;
24
- baseUrlTonder: string;
25
- apiKeyTonder: string;
26
-
27
- constructor({
28
- signal,
29
- baseUrlTonder,
30
- apiKeyTonder,
31
- }: LiteCheckoutConstructor) {
32
- this.baseUrlTonder = baseUrlTonder;
33
- this.signal = signal;
34
- this.apiKeyTonder = apiKeyTonder;
35
- }
36
-
37
- async getOpenpayDeviceSessionID(
38
- merchant_id: string,
39
- public_key: string,
40
- is_sandbox: boolean
41
- ): Promise<string | ErrorResponse> {
42
- try {
43
- let openpay = await window.OpenPay;
44
- openpay.setId(merchant_id);
45
- openpay.setApiKey(public_key);
46
- openpay.setSandboxMode(is_sandbox);
47
- return await openpay.deviceData.setup({
48
- signal: this.signal,
49
- }) as string;
50
- } catch (e) {
51
- throw this.buildErrorResponseFromCatch(e);
52
- }
53
- }
54
-
55
- async getBusiness(): Promise<GetBusinessResponse | ErrorResponse> {
56
- try {
57
- const getBusiness = await fetch(
58
- `${this.baseUrlTonder}/api/v1/payments/business/${this.apiKeyTonder}`,
59
- {
60
- headers: {
61
- Authorization: `Token ${this.apiKeyTonder}`,
62
- },
63
- signal: this.signal,
64
- }
65
- );
66
-
67
- if (getBusiness.ok) return (await getBusiness.json()) as Business;
68
-
69
- throw await this.buildErrorResponse(getBusiness);
70
- } catch (e) {
71
- throw this.buildErrorResponseFromCatch(e);
72
- }
73
- }
74
-
75
- async customerRegister(email: string): Promise<CustomerRegisterResponse | ErrorResponse> {
76
- try {
77
- const url = `${this.baseUrlTonder}/api/v1/customer/`;
78
- const data = { email: email };
79
- const response = await fetch(url, {
80
- method: "POST",
81
- headers: {
82
- "Content-Type": "application/json",
83
- Authorization: `Token ${this.apiKeyTonder}`,
84
- },
85
- signal: this.signal,
86
- body: JSON.stringify(data),
87
- });
88
-
89
- if (response.ok) return await response.json() as CustomerRegisterResponse;
90
- throw await this.buildErrorResponse(response);
91
- } catch (e) {
92
- throw this.buildErrorResponseFromCatch(e);
93
- }
94
- }
95
-
96
- async createOrder(orderItems: CreateOrderRequest): Promise<CreateOrderResponse | ErrorResponse> {
97
- try {
98
- const url = `${this.baseUrlTonder}/api/v1/orders/`;
99
- const data = orderItems;
100
- const response = await fetch(url, {
101
- method: "POST",
102
- headers: {
103
- "Content-Type": "application/json",
104
- Authorization: `Token ${this.apiKeyTonder}`,
105
- },
106
- body: JSON.stringify(data),
107
- });
108
- if (response.ok) return await response.json() as CreateOrderResponse;
109
- throw await this.buildErrorResponse(response);
110
- } catch (e) {
111
- throw this.buildErrorResponseFromCatch(e);
112
- }
113
- }
114
-
115
- async createPayment(paymentItems: CreatePaymentRequest): Promise<CreatePaymentResponse | ErrorResponse> {
116
- try {
117
- const url = `${this.baseUrlTonder}/api/v1/business/${paymentItems.business_pk}/payments/`;
118
- const data = paymentItems;
119
- const response = await fetch(url, {
120
- method: "POST",
121
- headers: {
122
- "Content-Type": "application/json",
123
- Authorization: `Token ${this.apiKeyTonder}`,
124
- },
125
- body: JSON.stringify(data),
126
- });
127
- if (response.ok) return await response.json() as CreatePaymentResponse;
128
- throw await this.buildErrorResponse(response);
129
- } catch (e) {
130
- throw this.buildErrorResponseFromCatch(e);
131
- }
132
- }
133
-
134
- async startCheckoutRouter(routerData: StartCheckoutRequest): Promise<StartCheckoutResponse | ErrorResponse> {
135
- try {
136
- const url = `${this.baseUrlTonder}/api/v1/checkout-router/`;
137
- const data = routerData;
138
- const response = await fetch(url, {
139
- method: "POST",
140
- headers: {
141
- "Content-Type": "application/json",
142
- Authorization: `Token ${this.apiKeyTonder}`,
143
- },
144
- body: JSON.stringify(data),
145
- });
146
- if (response.ok) return await response.json() as StartCheckoutResponse;
147
- throw await this.buildErrorResponse(response);
148
- } catch (e) {
149
- throw this.buildErrorResponseFromCatch(e);
150
- }
151
- }
152
-
153
- async startCheckoutRouterFull(routerFullData: StartCheckoutFullRequest): Promise<StartCheckoutResponse | ErrorResponse> {
154
-
155
- try {
156
-
157
- const {
158
- order,
159
- total,
160
- customer,
161
- skyflowTokens,
162
- return_url,
163
- isSandbox,
164
- metadata,
165
- currency
166
- } = routerFullData;
167
-
168
- const merchantResult = await this.getBusiness();
169
-
170
- const customerResult : CustomerRegisterResponse | ErrorResponse = await this.customerRegister(customer.email);
171
-
172
- if(customerResult && "auth_token" in customerResult && merchantResult && "reference" in merchantResult) {
173
-
174
- const orderData: CreateOrderRequest = {
175
- business: this.apiKeyTonder,
176
- client: customerResult.auth_token,
177
- billing_address_id: null,
178
- shipping_address_id: null,
179
- amount: total,
180
- reference: merchantResult.reference,
181
- is_oneclick: true,
182
- items: order.items,
183
- };
184
-
185
- const orderResult = await this.createOrder(orderData);
186
-
187
- const now = new Date();
188
-
189
- const dateString = now.toISOString();
190
-
191
- if("id" in orderResult && "id" in customerResult && "business" in merchantResult) {
192
-
193
- const paymentItems: CreatePaymentRequest = {
194
- business_pk: merchantResult.business.pk,
195
- amount: total,
196
- date: dateString,
197
- order_id: orderResult.id,
198
- client_id: customerResult.id
199
- };
200
-
201
- const paymentResult = await this.createPayment(
202
- paymentItems
203
- );
204
-
205
- let deviceSessionIdTonder: any;
206
-
207
- const { openpay_keys, business } = merchantResult
208
-
209
- if (openpay_keys.merchant_id && openpay_keys.public_key) {
210
- deviceSessionIdTonder = await this.getOpenpayDeviceSessionID(
211
- openpay_keys.merchant_id,
212
- openpay_keys.public_key,
213
- isSandbox
214
- );
215
- }
216
-
217
- const routerItems: StartCheckoutRequest = {
218
- card: skyflowTokens,
219
- name: customer.name,
220
- last_name: customer.lastname,
221
- email_client: customer.email,
222
- phone_number: customer.phone,
223
- return_url: return_url,
224
- id_product: "no_id",
225
- quantity_product: 1,
226
- id_ship: "0",
227
- instance_id_ship: "0",
228
- amount: total,
229
- title_ship: "shipping",
230
- description: "transaction",
231
- device_session_id: deviceSessionIdTonder ? deviceSessionIdTonder : null,
232
- token_id: "",
233
- order_id: ("id" in orderResult) && orderResult.id,
234
- business_id: business.pk,
235
- payment_id: ("pk" in paymentResult) && paymentResult.pk,
236
- source: 'sdk',
237
- metadata: metadata,
238
- browser_info: getBrowserInfo(),
239
- currency: currency
240
- };
241
-
242
- const checkoutResult = await this.startCheckoutRouter(routerItems);
243
-
244
- return checkoutResult;
245
-
246
- } else {
247
-
248
- throw new ErrorResponse({
249
- code: "500",
250
- body: orderResult as any,
251
- name: "Keys error",
252
- message: "Order response errors"
253
- } as IErrorResponse)
254
-
255
- }
256
-
257
- } else {
258
-
259
- throw new ErrorResponse({
260
- code: "500",
261
- body: merchantResult as any,
262
- name: "Keys error",
263
- message: "Merchant or customer reposne errors"
264
- } as IErrorResponse)
265
-
266
- }
267
- } catch (e) {
268
-
269
- throw this.buildErrorResponseFromCatch(e);
270
-
271
- }
272
- }
273
-
274
- async getSkyflowTokens({
275
- vault_id,
276
- vault_url,
277
- data,
278
- }: TokensRequest): Promise<any | ErrorResponse> {
279
- const skyflow = Skyflow.init({
280
- vaultID: vault_id,
281
- vaultURL: vault_url,
282
- getBearerToken: async () => await this.getVaultToken(),
283
- options: {
284
- logLevel: Skyflow.LogLevel.ERROR,
285
- env: Skyflow.Env.DEV,
286
- },
287
- });
288
-
289
- const collectContainer: CollectContainer = skyflow.container(
290
- Skyflow.ContainerType.COLLECT
291
- ) as CollectContainer;
292
-
293
- const fieldPromises = await this.getFieldsPromise(data, collectContainer);
294
-
295
- const result = await Promise.all(fieldPromises);
296
-
297
- const mountFail = result.some((item: boolean) => !item);
298
-
299
- if (mountFail) {
300
- throw this.buildErrorResponseFromCatch(Error("Ocurrió un error al montar los campos de la tarjeta"));
301
- } else {
302
- try {
303
- const collectResponseSkyflowTonder = await collectContainer.collect() as any;
304
- if (collectResponseSkyflowTonder) return collectResponseSkyflowTonder["records"][0]["fields"];
305
- throw this.buildErrorResponseFromCatch(Error("Por favor, verifica todos los campos de tu tarjeta"))
306
- } catch (error) {
307
- throw this.buildErrorResponseFromCatch(error);
308
- }
309
- }
310
- }
311
-
312
- async getVaultToken(): Promise<string> {
313
- try {
314
- const response = await fetch(`${this.baseUrlTonder}/api/v1/vault-token/`, {
315
- method: "GET",
316
- headers: {
317
- Authorization: `Token ${this.apiKeyTonder}`,
318
- },
319
- signal: this.signal,
320
- });
321
- if (response.ok) return (await response.json() as GetVaultTokenResponse)?.token;
322
- throw new Error(`HTTPCODE: ${response.status}`)
323
- } catch (e) {
324
- throw new Error(`Failed to retrieve bearer token; ${typeof e == "string" ? e : (e as Error).message}`)
325
- }
326
- }
327
-
328
- async getFieldsPromise(data: any, collectContainer: CollectContainer): Promise<Promise<boolean>[]> {
329
- const fields = await this.getFields(data, collectContainer);
330
- if (!fields) return [];
331
-
332
- return fields.map((field: { element: CollectElement, key: string }) => {
333
- return new Promise((resolve) => {
334
- const div = document.createElement("div");
335
- div.hidden = true;
336
- div.id = `id-${field.key}`;
337
- document.querySelector(`body`)?.appendChild(div);
338
- setTimeout(() => {
339
- field.element.mount(`#id-${field.key}`);
340
- setInterval(() => {
341
- if (field.element.isMounted()) {
342
- const value = data[field.key];
343
- field.element.update({ value: value });
344
- return resolve(field.element.isMounted());
345
- }
346
- }, 120);
347
- }, 120);
348
- });
349
- })
350
- }
351
-
352
- async registerCustomerCard(customerToken: string, data: RegisterCustomerCardRequest): Promise<RegisterCustomerCardResponse | ErrorResponse> {
353
- try {
354
- const response = await fetch(`${this.baseUrlTonder}/api/v1/cards/`, {
355
- method: 'POST',
356
- headers: {
357
- 'Authorization': `Token ${customerToken}`,
358
- 'Content-Type': 'application/json'
359
- },
360
- signal: this.signal,
361
- body: JSON.stringify(data)
362
- });
363
-
364
- if (response.ok) return await response.json() as RegisterCustomerCardResponse;
365
- throw await this.buildErrorResponse(response);
366
- } catch (error) {
367
- throw this.buildErrorResponseFromCatch(error);
368
- }
369
- }
370
-
371
- async getCustomerCards(customerToken: string, query: string = ""): Promise<GetCustomerCardsResponse | ErrorResponse> {
372
- try {
373
- const response = await fetch(`${this.baseUrlTonder}/api/v1/cards/${query}`, {
374
- method: 'GET',
375
- headers: {
376
- 'Authorization': `Token ${customerToken}`,
377
- 'Content-Type': 'application/json'
378
- },
379
- signal: this.signal,
380
- });
381
-
382
- if (response.ok) return await response.json() as GetCustomerCardsResponse;
383
- throw await this.buildErrorResponse(response);
384
- } catch (error) {
385
- throw this.buildErrorResponseFromCatch(error);
386
- }
387
- }
388
-
389
- async deleteCustomerCard(customerToken: string, skyflowId: string = ""): Promise<Boolean | ErrorResponse> {
390
- try {
391
- const response = await fetch(`${this.baseUrlTonder}/api/v1/cards/${skyflowId}`, {
392
- method: 'DELETE',
393
- headers: {
394
- 'Authorization': `Token ${customerToken}`,
395
- 'Content-Type': 'application/json'
396
- },
397
- signal: this.signal,
398
- });
399
-
400
- if (response.ok) return true;
401
- throw await this.buildErrorResponse(response);
402
- } catch (error) {
403
- throw this.buildErrorResponseFromCatch(error);
404
- }
405
- }
406
-
407
- private buildErrorResponseFromCatch(e: any): ErrorResponse {
408
-
409
- const error = new ErrorResponse({
410
- code: e?.status ? e.status : e.code,
411
- body: e?.body,
412
- name: e ? typeof e == "string" ? "catch" : (e as Error).name : "Error",
413
- message: e ? (typeof e == "string" ? e : (e as Error).message) : "Error",
414
- stack: typeof e == "string" ? undefined : (e as Error).stack,
415
- })
416
-
417
- return error;
418
- }
419
-
420
- private async buildErrorResponse(
421
- response: Response,
422
- stack: string | undefined = undefined
423
- ): Promise<ErrorResponse> {
424
-
425
- let body, status, message = "Error";
426
-
427
- if(response && "json" in response) {
428
- body = await response?.json();
429
- }
430
-
431
- if(response && "status" in response) {
432
- status = response.status.toString();
433
- }
434
-
435
- if(response && "text" in response) {
436
- message = await response.text();
437
- }
438
-
439
- const error = new ErrorResponse({
440
- code: status,
441
- body: body,
442
- name: status,
443
- message: message,
444
- stack,
445
- } as IErrorResponse)
446
-
447
- return error;
448
- }
449
-
450
- private async getFields(data: any, collectContainer: CollectContainer): Promise<{ element: CollectElement, key: string }[]> {
451
- return await Promise.all(
452
- Object.keys(data).map(async (key) => {
453
- const cardHolderNameElement = await collectContainer.create({
454
- table: "cards",
455
- column: key,
456
- type: Skyflow.ElementType.INPUT_FIELD,
457
- });
458
- return { element: cardHolderNameElement, key: key };
459
- })
460
- )
461
- }
462
- }
1
+ import Skyflow from "skyflow-js";
2
+ import CollectContainer from "skyflow-js/types/core/external/collect/collect-container";
3
+ import CollectElement from "skyflow-js/types/core/external/collect/collect-element";
4
+ import { Business } from "../types/commons";
5
+ import { CreateOrderRequest, CreatePaymentRequest, RegisterCustomerCardRequest, StartCheckoutRequest, TokensRequest, StartCheckoutFullRequest, StartCheckoutIdRequest } from "../types/requests";
6
+ import { GetBusinessResponse, CustomerRegisterResponse, CreateOrderResponse, CreatePaymentResponse, StartCheckoutResponse, GetVaultTokenResponse, IErrorResponse, GetCustomerCardsResponse, RegisterCustomerCardResponse } from "../types/responses";
7
+ import { ErrorResponse } from "./errorResponse";
8
+ import { getBrowserInfo } from "../helpers/utils";
9
+ import { ThreeDSHandler } from "./3dsHandler";
10
+
11
+ declare global {
12
+ interface Window {
13
+ OpenPay: any;
14
+ }
15
+ }
16
+
17
+ export type LiteCheckoutConstructor = {
18
+ signal: AbortSignal;
19
+ baseUrlTonder: string;
20
+ apiKeyTonder: string;
21
+ successUrl?: string;
22
+ };
23
+
24
+ export class LiteCheckout implements LiteCheckoutConstructor {
25
+ signal: AbortSignal;
26
+ baseUrlTonder: string;
27
+ apiKeyTonder: string;
28
+ process3ds: ThreeDSHandler;
29
+ successUrl?: string
30
+
31
+ constructor({
32
+ signal,
33
+ baseUrlTonder,
34
+ apiKeyTonder,
35
+ successUrl,
36
+ }: LiteCheckoutConstructor) {
37
+ this.baseUrlTonder = baseUrlTonder;
38
+ this.signal = signal;
39
+ this.apiKeyTonder = apiKeyTonder;
40
+ this.successUrl = successUrl;
41
+
42
+ this.process3ds = new ThreeDSHandler({
43
+ apiKey: this.apiKeyTonder,
44
+ baseUrl: this.baseUrlTonder,
45
+ successUrl: successUrl
46
+ })
47
+ }
48
+
49
+ async getOpenpayDeviceSessionID(
50
+ merchant_id: string,
51
+ public_key: string,
52
+ is_sandbox: boolean
53
+ ): Promise<string | ErrorResponse> {
54
+ try {
55
+ let openpay = await window.OpenPay;
56
+ openpay.setId(merchant_id);
57
+ openpay.setApiKey(public_key);
58
+ openpay.setSandboxMode(is_sandbox);
59
+ return await openpay.deviceData.setup({
60
+ signal: this.signal,
61
+ }) as string;
62
+ } catch (e) {
63
+ throw this.buildErrorResponseFromCatch(e);
64
+ }
65
+ }
66
+
67
+ async getBusiness(): Promise<GetBusinessResponse | ErrorResponse> {
68
+ try {
69
+ const getBusiness = await fetch(
70
+ `${this.baseUrlTonder}/api/v1/payments/business/${this.apiKeyTonder}`,
71
+ {
72
+ headers: {
73
+ Authorization: `Token ${this.apiKeyTonder}`,
74
+ },
75
+ signal: this.signal,
76
+ }
77
+ );
78
+
79
+ if (getBusiness.ok) return (await getBusiness.json()) as Business;
80
+
81
+ throw await this.buildErrorResponse(getBusiness);
82
+ } catch (e) {
83
+ throw this.buildErrorResponseFromCatch(e);
84
+ }
85
+ }
86
+
87
+ async verify3dsTransaction () {
88
+ const result3ds = await this.process3ds.verifyTransactionStatus()
89
+ const resultCheckout = await this.resumeCheckout(result3ds)
90
+ this.process3ds.setPayload(resultCheckout)
91
+ if (resultCheckout && 'is_route_finished' in resultCheckout && 'provider' in resultCheckout && resultCheckout.provider === 'tonder') {
92
+ return resultCheckout
93
+ }
94
+ return this.handle3dsRedirect(resultCheckout)
95
+ }
96
+
97
+ async resumeCheckout(response: any) {
98
+ if (["Failed", "Declined", "Cancelled"].includes(response?.status)) {
99
+ const routerItems = {
100
+ // TODO: Replace this with reponse.checkout_id
101
+ checkout_id: this.process3ds.getCurrentCheckoutId(),
102
+ };
103
+ const routerResponse = await this.handleCheckoutRouter(
104
+ routerItems
105
+ );
106
+ return routerResponse
107
+ }
108
+ return response
109
+ }
110
+
111
+ async handle3dsRedirect(response: ErrorResponse | StartCheckoutResponse | false | undefined) {
112
+ const iframe = response && 'next_action' in response ? response?.next_action?.iframe_resources?.iframe:null
113
+
114
+ if (iframe) {
115
+ this.process3ds.loadIframe()!.then(() => {
116
+ //TODO: Check if this will be necessary on the frontend side
117
+ // after some the tests in production, since the 3DS process
118
+ // doesn't works properly on the sandbox environment
119
+ // setTimeout(() => {
120
+ // process3ds.verifyTransactionStatus();
121
+ // }, 10000);
122
+ this.process3ds.verifyTransactionStatus();
123
+ }).catch((error: any) => {
124
+ console.log('Error loading iframe:', error)
125
+ })
126
+ } else {
127
+ const redirectUrl = this.process3ds.getRedirectUrl()
128
+ if (redirectUrl) {
129
+ this.process3ds.redirectToChallenge()
130
+ } else {
131
+ return response;
132
+ }
133
+ }
134
+ }
135
+
136
+ async customerRegister(email: string): Promise<CustomerRegisterResponse | ErrorResponse> {
137
+ try {
138
+ const url = `${this.baseUrlTonder}/api/v1/customer/`;
139
+ const data = { email: email };
140
+ const response = await fetch(url, {
141
+ method: "POST",
142
+ headers: {
143
+ "Content-Type": "application/json",
144
+ Authorization: `Token ${this.apiKeyTonder}`,
145
+ },
146
+ signal: this.signal,
147
+ body: JSON.stringify(data),
148
+ });
149
+
150
+ if (response.ok) return await response.json() as CustomerRegisterResponse;
151
+ throw await this.buildErrorResponse(response);
152
+ } catch (e) {
153
+ throw this.buildErrorResponseFromCatch(e);
154
+ }
155
+ }
156
+
157
+ async createOrder(orderItems: CreateOrderRequest): Promise<CreateOrderResponse | ErrorResponse> {
158
+ try {
159
+ const url = `${this.baseUrlTonder}/api/v1/orders/`;
160
+ const data = orderItems;
161
+ const response = await fetch(url, {
162
+ method: "POST",
163
+ headers: {
164
+ "Content-Type": "application/json",
165
+ Authorization: `Token ${this.apiKeyTonder}`,
166
+ },
167
+ body: JSON.stringify(data),
168
+ });
169
+ if (response.ok) return await response.json() as CreateOrderResponse;
170
+ throw await this.buildErrorResponse(response);
171
+ } catch (e) {
172
+ throw this.buildErrorResponseFromCatch(e);
173
+ }
174
+ }
175
+
176
+ async createPayment(paymentItems: CreatePaymentRequest): Promise<CreatePaymentResponse | ErrorResponse> {
177
+ try {
178
+ const url = `${this.baseUrlTonder}/api/v1/business/${paymentItems.business_pk}/payments/`;
179
+ const data = paymentItems;
180
+ const response = await fetch(url, {
181
+ method: "POST",
182
+ headers: {
183
+ "Content-Type": "application/json",
184
+ Authorization: `Token ${this.apiKeyTonder}`,
185
+ },
186
+ body: JSON.stringify(data),
187
+ });
188
+ if (response.ok) return await response.json() as CreatePaymentResponse;
189
+ throw await this.buildErrorResponse(response);
190
+ } catch (e) {
191
+ throw this.buildErrorResponseFromCatch(e);
192
+ }
193
+ }
194
+ async handleCheckoutRouter(routerData: StartCheckoutRequest | StartCheckoutIdRequest){
195
+ try {
196
+ const url = `${this.baseUrlTonder}/api/v1/checkout-router/`;
197
+ const data = routerData;
198
+ const response = await fetch(url, {
199
+ method: "POST",
200
+ headers: {
201
+ "Content-Type": "application/json",
202
+ Authorization: `Token ${this.apiKeyTonder}`,
203
+ },
204
+ body: JSON.stringify(data),
205
+ });
206
+ if (response.ok) return await response.json() as StartCheckoutResponse;
207
+ throw await this.buildErrorResponse(response);
208
+ } catch (e) {
209
+ throw this.buildErrorResponseFromCatch(e);
210
+ }
211
+ }
212
+
213
+ async startCheckoutRouter(routerData: StartCheckoutRequest | StartCheckoutIdRequest): Promise<StartCheckoutResponse | ErrorResponse | undefined> {
214
+ const checkoutResult = await this.handleCheckoutRouter(routerData);
215
+ const payload = await this.init3DSRedirect(checkoutResult)
216
+ if(payload)
217
+ return checkoutResult;
218
+ }
219
+
220
+ async startCheckoutRouterFull(routerFullData: StartCheckoutFullRequest): Promise<StartCheckoutResponse | ErrorResponse | undefined> {
221
+
222
+ try {
223
+
224
+ const {
225
+ order,
226
+ total,
227
+ customer,
228
+ skyflowTokens,
229
+ return_url,
230
+ isSandbox,
231
+ metadata,
232
+ currency
233
+ } = routerFullData;
234
+
235
+ const merchantResult = await this.getBusiness();
236
+
237
+ const customerResult : CustomerRegisterResponse | ErrorResponse = await this.customerRegister(customer.email);
238
+
239
+ if(customerResult && "auth_token" in customerResult && merchantResult && "reference" in merchantResult) {
240
+
241
+ const orderData: CreateOrderRequest = {
242
+ business: this.apiKeyTonder,
243
+ client: customerResult.auth_token,
244
+ billing_address_id: null,
245
+ shipping_address_id: null,
246
+ amount: total,
247
+ reference: merchantResult.reference,
248
+ is_oneclick: true,
249
+ items: order.items,
250
+ };
251
+
252
+ const orderResult = await this.createOrder(orderData);
253
+
254
+ const now = new Date();
255
+
256
+ const dateString = now.toISOString();
257
+
258
+ if("id" in orderResult && "id" in customerResult && "business" in merchantResult) {
259
+
260
+ const paymentItems: CreatePaymentRequest = {
261
+ business_pk: merchantResult.business.pk,
262
+ amount: total,
263
+ date: dateString,
264
+ order_id: orderResult.id,
265
+ client_id: customerResult.id
266
+ };
267
+
268
+ const paymentResult = await this.createPayment(
269
+ paymentItems
270
+ );
271
+
272
+ let deviceSessionIdTonder: any;
273
+
274
+ const { openpay_keys, business } = merchantResult
275
+
276
+ if (openpay_keys.merchant_id && openpay_keys.public_key) {
277
+ deviceSessionIdTonder = await this.getOpenpayDeviceSessionID(
278
+ openpay_keys.merchant_id,
279
+ openpay_keys.public_key,
280
+ isSandbox
281
+ );
282
+ }
283
+
284
+ const routerItems: StartCheckoutRequest = {
285
+ card: skyflowTokens,
286
+ name: customer.name,
287
+ last_name: customer.lastname,
288
+ email_client: customer.email,
289
+ phone_number: customer.phone,
290
+ return_url: return_url,
291
+ id_product: "no_id",
292
+ quantity_product: 1,
293
+ id_ship: "0",
294
+ instance_id_ship: "0",
295
+ amount: total,
296
+ title_ship: "shipping",
297
+ description: "transaction",
298
+ device_session_id: deviceSessionIdTonder ? deviceSessionIdTonder : null,
299
+ token_id: "",
300
+ order_id: ("id" in orderResult) && orderResult.id,
301
+ business_id: business.pk,
302
+ payment_id: ("pk" in paymentResult) && paymentResult.pk,
303
+ source: 'sdk',
304
+ metadata: metadata,
305
+ browser_info: getBrowserInfo(),
306
+ currency: currency
307
+ };
308
+
309
+ const checkoutResult = await this.handleCheckoutRouter(routerItems);
310
+ const payload = await this.init3DSRedirect(checkoutResult)
311
+ if(payload)
312
+ return checkoutResult;
313
+ } else {
314
+
315
+ throw new ErrorResponse({
316
+ code: "500",
317
+ body: orderResult as any,
318
+ name: "Keys error",
319
+ message: "Order response errors"
320
+ } as IErrorResponse)
321
+
322
+ }
323
+
324
+ } else {
325
+
326
+ throw new ErrorResponse({
327
+ code: "500",
328
+ body: merchantResult as any,
329
+ name: "Keys error",
330
+ message: "Merchant or customer reposne errors"
331
+ } as IErrorResponse)
332
+
333
+ }
334
+ } catch (e) {
335
+
336
+ throw this.buildErrorResponseFromCatch(e);
337
+
338
+ }
339
+ }
340
+
341
+ async init3DSRedirect(checkoutResult: ErrorResponse | StartCheckoutResponse){
342
+ this.process3ds.setPayload(checkoutResult)
343
+ this.process3ds.saveCheckoutId(checkoutResult && 'checkout_id' in checkoutResult ? checkoutResult.checkout_id:"")
344
+ return await this.handle3dsRedirect(checkoutResult)
345
+ }
346
+
347
+ async getSkyflowTokens({
348
+ vault_id,
349
+ vault_url,
350
+ data,
351
+ }: TokensRequest): Promise<any | ErrorResponse> {
352
+ const skyflow = Skyflow.init({
353
+ vaultID: vault_id,
354
+ vaultURL: vault_url,
355
+ getBearerToken: async () => await this.getVaultToken(),
356
+ options: {
357
+ logLevel: Skyflow.LogLevel.ERROR,
358
+ env: Skyflow.Env.DEV,
359
+ },
360
+ });
361
+
362
+ const collectContainer: CollectContainer = skyflow.container(
363
+ Skyflow.ContainerType.COLLECT
364
+ ) as CollectContainer;
365
+
366
+ const fieldPromises = await this.getFieldsPromise(data, collectContainer);
367
+
368
+ const result = await Promise.all(fieldPromises);
369
+
370
+ const mountFail = result.some((item: boolean) => !item);
371
+
372
+ if (mountFail) {
373
+ throw this.buildErrorResponseFromCatch(Error("Ocurrió un error al montar los campos de la tarjeta"));
374
+ } else {
375
+ try {
376
+ const collectResponseSkyflowTonder = await collectContainer.collect() as any;
377
+ if (collectResponseSkyflowTonder) return collectResponseSkyflowTonder["records"][0]["fields"];
378
+ throw this.buildErrorResponseFromCatch(Error("Por favor, verifica todos los campos de tu tarjeta"))
379
+ } catch (error) {
380
+ throw this.buildErrorResponseFromCatch(error);
381
+ }
382
+ }
383
+ }
384
+
385
+ async getVaultToken(): Promise<string> {
386
+ try {
387
+ const response = await fetch(`${this.baseUrlTonder}/api/v1/vault-token/`, {
388
+ method: "GET",
389
+ headers: {
390
+ Authorization: `Token ${this.apiKeyTonder}`,
391
+ },
392
+ signal: this.signal,
393
+ });
394
+ if (response.ok) return (await response.json() as GetVaultTokenResponse)?.token;
395
+ throw new Error(`HTTPCODE: ${response.status}`)
396
+ } catch (e) {
397
+ throw new Error(`Failed to retrieve bearer token; ${typeof e == "string" ? e : (e as Error).message}`)
398
+ }
399
+ }
400
+
401
+ async getFieldsPromise(data: any, collectContainer: CollectContainer): Promise<Promise<boolean>[]> {
402
+ const fields = await this.getFields(data, collectContainer);
403
+ if (!fields) return [];
404
+
405
+ return fields.map((field: { element: CollectElement, key: string }) => {
406
+ return new Promise((resolve) => {
407
+ const div = document.createElement("div");
408
+ div.hidden = true;
409
+ div.id = `id-${field.key}`;
410
+ document.querySelector(`body`)?.appendChild(div);
411
+ setTimeout(() => {
412
+ field.element.mount(`#id-${field.key}`);
413
+ setInterval(() => {
414
+ if (field.element.isMounted()) {
415
+ const value = data[field.key];
416
+ field.element.update({ value: value });
417
+ return resolve(field.element.isMounted());
418
+ }
419
+ }, 120);
420
+ }, 120);
421
+ });
422
+ })
423
+ }
424
+
425
+ async registerCustomerCard(customerToken: string, data: RegisterCustomerCardRequest): Promise<RegisterCustomerCardResponse | ErrorResponse> {
426
+ try {
427
+ const response = await fetch(`${this.baseUrlTonder}/api/v1/cards/`, {
428
+ method: 'POST',
429
+ headers: {
430
+ 'Authorization': `Token ${customerToken}`,
431
+ 'Content-Type': 'application/json'
432
+ },
433
+ signal: this.signal,
434
+ body: JSON.stringify(data)
435
+ });
436
+
437
+ if (response.ok) return await response.json() as RegisterCustomerCardResponse;
438
+ throw await this.buildErrorResponse(response);
439
+ } catch (error) {
440
+ throw this.buildErrorResponseFromCatch(error);
441
+ }
442
+ }
443
+
444
+ async getCustomerCards(customerToken: string, query: string = ""): Promise<GetCustomerCardsResponse | ErrorResponse> {
445
+ try {
446
+ const response = await fetch(`${this.baseUrlTonder}/api/v1/cards/${query}`, {
447
+ method: 'GET',
448
+ headers: {
449
+ 'Authorization': `Token ${customerToken}`,
450
+ 'Content-Type': 'application/json'
451
+ },
452
+ signal: this.signal,
453
+ });
454
+
455
+ if (response.ok) return await response.json() as GetCustomerCardsResponse;
456
+ throw await this.buildErrorResponse(response);
457
+ } catch (error) {
458
+ throw this.buildErrorResponseFromCatch(error);
459
+ }
460
+ }
461
+
462
+ async deleteCustomerCard(customerToken: string, skyflowId: string = ""): Promise<Boolean | ErrorResponse> {
463
+ try {
464
+ const response = await fetch(`${this.baseUrlTonder}/api/v1/cards/${skyflowId}`, {
465
+ method: 'DELETE',
466
+ headers: {
467
+ 'Authorization': `Token ${customerToken}`,
468
+ 'Content-Type': 'application/json'
469
+ },
470
+ signal: this.signal,
471
+ });
472
+
473
+ if (response.ok) return true;
474
+ throw await this.buildErrorResponse(response);
475
+ } catch (error) {
476
+ throw this.buildErrorResponseFromCatch(error);
477
+ }
478
+ }
479
+
480
+ private buildErrorResponseFromCatch(e: any): ErrorResponse {
481
+
482
+ const error = new ErrorResponse({
483
+ code: e?.status ? e.status : e.code,
484
+ body: e?.body,
485
+ name: e ? typeof e == "string" ? "catch" : (e as Error).name : "Error",
486
+ message: e ? (typeof e == "string" ? e : (e as Error).message) : "Error",
487
+ stack: typeof e == "string" ? undefined : (e as Error).stack,
488
+ })
489
+
490
+ return error;
491
+ }
492
+
493
+ private async buildErrorResponse(
494
+ response: Response,
495
+ stack: string | undefined = undefined
496
+ ): Promise<ErrorResponse> {
497
+
498
+ let body, status, message = "Error";
499
+
500
+ if(response && "json" in response) {
501
+ body = await response?.json();
502
+ }
503
+
504
+ if(response && "status" in response) {
505
+ status = response.status.toString();
506
+ }
507
+
508
+ if(response && "text" in response) {
509
+ message = await response.text();
510
+ }
511
+
512
+ const error = new ErrorResponse({
513
+ code: status,
514
+ body: body,
515
+ name: status,
516
+ message: message,
517
+ stack,
518
+ } as IErrorResponse)
519
+
520
+ return error;
521
+ }
522
+
523
+ private async getFields(data: any, collectContainer: CollectContainer): Promise<{ element: CollectElement, key: string }[]> {
524
+ return await Promise.all(
525
+ Object.keys(data).map(async (key) => {
526
+ const cardHolderNameElement = await collectContainer.create({
527
+ table: "cards",
528
+ column: key,
529
+ type: Skyflow.ElementType.INPUT_FIELD,
530
+ });
531
+ return { element: cardHolderNameElement, key: key };
532
+ })
533
+ )
534
+ }
535
+ }