@tonder.io/ionic-full-sdk 0.0.62-beta.DEV-2106.1 → 0.0.62-beta.DEV-2106.99e3729

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tonder.io/ionic-full-sdk",
3
- "version": "0.0.62-beta.DEV-2106.1",
3
+ "version": "0.0.62-beta.DEV-2106.99e3729",
4
4
  "description": "Tonder ionic full SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -10,7 +10,7 @@
10
10
  "author": "",
11
11
  "license": "ISC",
12
12
  "dependencies": {
13
- "@tonder.io/ionic-lite-sdk": "0.0.68-beta.DEV-2106.1",
13
+ "@tonder.io/ionic-lite-sdk": "0.0.68-beta.DEV-2106.48d18dd",
14
14
  "accordion-js": "^3.4.0",
15
15
  "crypto-js": "^4.1.1",
16
16
  "lodash.get": "^4.4.2",
package/rollup.config.js CHANGED
@@ -6,7 +6,8 @@ const packageJson = require('./package.json');
6
6
 
7
7
  module.exports = {
8
8
  input: './src/index.ts',
9
- output: {
9
+ output:
10
+ {
10
11
  dir: 'dist',
11
12
  format: 'es',
12
13
  plugins: [terser()]
@@ -5,7 +5,6 @@ import {
5
5
  mapCards,
6
6
  } from '../helpers/utils';
7
7
  import {initSkyflow, initUpdateSkyflow} from '../helpers/skyflow'
8
- import { ErrorResponse } from '@tonder.io/ionic-lite-sdk/dist/classes/errorResponse';
9
8
  import { CustomerRegisterResponse } from '@tonder.io/ionic-lite-sdk/dist/types/responses';
10
9
  import { BaseInlineCheckout } from "@tonder.io/ionic-lite-sdk";
11
10
  import {IProcessPaymentRequest} from "@tonder.io/ionic-lite-sdk/dist/types/checkout";
@@ -17,6 +16,7 @@ import {IInlineCheckout} from "../types/inlineCheckout";
17
16
  import { buildPublicAppError } from "../shared/utils/appError";
18
17
  import { ErrorKeyEnum } from "../shared/enums/ErrorKeyEnum";
19
18
  import { MESSAGES_EN, MESSAGES_ES } from "../shared/constants/messages";
19
+ import { SDK_INFO } from "../helpers/sdkInfo";
20
20
  import get from "lodash.get";
21
21
  // @ts-ignore
22
22
  import Accordion from "accordion-js";
@@ -66,15 +66,18 @@ export class InlineCheckout extends BaseInlineCheckout<IInlineCustomizationOptio
66
66
  customization,
67
67
  events
68
68
  }: IInlineCheckoutOptions) {
69
- super({
69
+ const baseOptions = {
70
70
  apiKey: apiKey,
71
71
  returnUrl: returnUrl,
72
72
  mode: mode,
73
73
  callBack: callBack,
74
74
  customization: customization,
75
75
  tdsIframeId: collectorIds && 'tdsIframe' in collectorIds ? collectorIds?.tdsIframe : "tdsIframe",
76
- tonderPayButtonId: collectorIds ? collectorIds?.tonderPayButton : "tonderPayButton"
77
- });
76
+ tonderPayButtonId: collectorIds ? collectorIds?.tonderPayButton : "tonderPayButton",
77
+ sdkInfo: SDK_INFO,
78
+ };
79
+ super(baseOptions);
80
+
78
81
  this.renderPaymentButton = renderPaymentButton || false;
79
82
  this.renderSaveCardButton = renderSaveCardButton;
80
83
  this.customStyles = styles
@@ -397,8 +400,8 @@ export class InlineCheckout extends BaseInlineCheckout<IInlineCustomizationOptio
397
400
  async #handlePaymentClick() {
398
401
  try {
399
402
  await this.payment({} as IProcessPaymentRequest);
400
- } catch (error) {
401
- console.error("Payment error:", error);
403
+ } catch (error: any) {
404
+ console.error("Payment error:", error?.message, error?.code, error?.details);
402
405
  }
403
406
  }
404
407
 
@@ -688,7 +691,7 @@ export class InlineCheckout extends BaseInlineCheckout<IInlineCustomizationOptio
688
691
  try{
689
692
  if(this.merchantData!) {
690
693
  const cardTokensSkyflowTonder: any = await this.#getCardTokens(this.collectorIds.tonderSaveCardButton!);
691
- const customerResponse : CustomerRegisterResponse | ErrorResponse = await this._getCustomer() as CustomerRegisterResponse;
694
+ const customerResponse : CustomerRegisterResponse = await this._getCustomer() as CustomerRegisterResponse;
692
695
 
693
696
  if("auth_token" in customerResponse && this.secureToken) {
694
697
  // @ts-ignore
@@ -736,6 +739,7 @@ export class InlineCheckout extends BaseInlineCheckout<IInlineCustomizationOptio
736
739
  throw buildPublicAppError(
737
740
  {
738
741
  errorCode: ErrorKeyEnum.CARD_ON_FILE_DECLINED,
742
+ lockErrorCode: true,
739
743
  message: MESSAGES_EN[ErrorKeyEnum.CARD_ON_FILE_DECLINED],
740
744
  },
741
745
  processError,
@@ -841,9 +845,18 @@ export class InlineCheckout extends BaseInlineCheckout<IInlineCustomizationOptio
841
845
  currency: this.currency || 'MXN',
842
846
  });
843
847
  } catch (processError) {
848
+ console.error("Error processing card-on-file payment=====", processError);
849
+ this.reportSdkError(processError, {
850
+ feature: "payment",
851
+ process_id: cardId,
852
+ metadata: {
853
+ step: "#processCardOnFilePayment_cardOnFile.process",
854
+ },
855
+ });
844
856
  throw buildPublicAppError(
845
857
  {
846
858
  errorCode: ErrorKeyEnum.CARD_ON_FILE_DECLINED,
859
+ lockErrorCode: true,
847
860
  message: MESSAGES_EN[ErrorKeyEnum.CARD_ON_FILE_DECLINED],
848
861
  details: {
849
862
  cardId,
@@ -871,11 +884,13 @@ export class InlineCheckout extends BaseInlineCheckout<IInlineCustomizationOptio
871
884
  process_id: cardId,
872
885
  metadata: {
873
886
  step: "#processCardOnFilePayment",
887
+ error,
874
888
  },
875
889
  });
876
890
  throw buildPublicAppError(
877
891
  {
878
892
  errorCode: ErrorKeyEnum.CARD_ON_FILE_DECLINED,
893
+ lockErrorCode: true,
879
894
  message: MESSAGES_EN[ErrorKeyEnum.CARD_ON_FILE_DECLINED],
880
895
  details: {
881
896
  cardId,
@@ -966,18 +981,16 @@ export class InlineCheckout extends BaseInlineCheckout<IInlineCustomizationOptio
966
981
  showError("No se han configurado los datos del proveedor de servicio", this.radioChecked, this.collectorIds.msgError)
967
982
  }
968
983
  } catch (error) {
969
- const cardIdFromError = (error as any)?.details?.cardId as string | null | undefined;
970
- const cardIdToCleanup = cardIdSaved || cardIdFromError || null;
971
- if(cardIdToCleanup && auth_token){
984
+ if(cardIdSaved && auth_token){
972
985
  this.cardsInjected = false
973
- await this._removeCustomerCard(auth_token, this.merchantData!.business.pk, cardIdToCleanup)
986
+ await this._removeCustomerCard(auth_token, this.merchantData!.business.pk, cardIdSaved)
974
987
  await this.#loadCardsList(auth_token)
975
988
  }
976
989
  this.reportSdkError(error, {
977
990
  feature: "payment",
978
- process_id: cardIdToCleanup || this.radioChecked || this.getCustomerId(),
991
+ process_id: cardIdSaved || this.radioChecked || this.getCustomerId(),
979
992
  metadata: {
980
- step: "card_on_file",
993
+ step: "_checkout",
981
994
  },
982
995
  });
983
996
  const visualMessage =
@@ -3,9 +3,11 @@ import { ErrorKeyEnum } from "../enums/ErrorKeyEnum";
3
3
  export const MESSAGES_EN: Record<string, string> = {
4
4
  [ErrorKeyEnum.CARD_ON_FILE_DECLINED]:
5
5
  "Transaction declined. Please verify your card details.",
6
+ [ErrorKeyEnum.UNKNOWN_ERROR]: 'An unexpected error occurred.',
6
7
  };
7
8
 
8
9
  export const MESSAGES_ES: Record<string, string> = {
9
10
  [ErrorKeyEnum.CARD_ON_FILE_DECLINED]:
10
11
  "Transaccion declinada. Verique los datos de su tarjeta.",
12
+ [ErrorKeyEnum.UNKNOWN_ERROR]: 'Ocurrió un error inesperado.',
11
13
  };
@@ -2,4 +2,5 @@ export enum ErrorKeyEnum {
2
2
  SAVE_CARD_ERROR = "SAVE_CARD_ERROR",
3
3
  PAYMENT_PROCESS_ERROR = "PAYMENT_PROCESS_ERROR",
4
4
  CARD_ON_FILE_DECLINED = "CARD_ON_FILE_DECLINED",
5
+ UNKNOWN_ERROR = 'UNKNOWN_ERROR'
5
6
  }
@@ -1,22 +1,42 @@
1
+ import { MESSAGES_EN } from '../constants/messages';
2
+ import { ErrorKeyEnum } from '../enums/ErrorKeyEnum';
3
+
1
4
  const DEFAULT_SYSTEM_ERROR = 'APP_INTERNAL_001';
2
- const DEFAULT_MESSAGE = 'An unexpected error occurred.';
5
+ const LOCK_ERROR_CODE_KEY = '__tonderLockErrorCode__';
6
+
7
+ function isRecord(value: unknown): value is Record<string, unknown> {
8
+ return typeof value === 'object' && value !== null;
9
+ }
3
10
 
4
11
  export interface IAppErrorInput {
5
12
  errorCode: string;
6
13
  message?: string;
7
- details?: Record<string, unknown>;
8
14
  statusCode?: number;
15
+ details?: Record<string, unknown>;
16
+ originalError?: unknown;
17
+ lockErrorCode?: boolean;
9
18
  }
10
19
 
11
20
  export class AppError extends Error {
12
21
  public readonly status: 'error' = 'error';
13
22
  public readonly code: string;
14
23
  public readonly statusCode: number;
24
+ public readonly originalError?: unknown;
15
25
  public details: Record<string, unknown>;
16
26
 
17
27
  constructor(error: IAppErrorInput) {
18
- const resolvedStatusCode = AppError.parseStatusCode(error.statusCode);
19
- const resolvedMessage = error.message || DEFAULT_MESSAGE;
28
+ const resolvedStatusCode = AppError.resolveStatusCode(
29
+ error.statusCode,
30
+ error.originalError,
31
+ );
32
+ const resolvedMessage = AppError.resolveMessage(
33
+ error.errorCode,
34
+ error.message,
35
+ );
36
+ const resolvedSystemError = AppError.resolveSystemError(
37
+ error.details?.systemError,
38
+ error.originalError,
39
+ );
20
40
 
21
41
  super(resolvedMessage);
22
42
 
@@ -25,22 +45,28 @@ export class AppError extends Error {
25
45
  this.name = 'TonderError';
26
46
  this.code = error.errorCode;
27
47
  this.statusCode = resolvedStatusCode;
28
-
29
- const baseDetails = {
30
- ...(error.details || {}),
31
- };
32
-
33
- const resolvedSystemError = AppError.extractSystemError(baseDetails);
48
+ this.originalError = error.originalError;
34
49
  this.details = {
35
- ...baseDetails,
36
50
  code: error.errorCode,
37
51
  statusCode: resolvedStatusCode,
38
52
  systemError: resolvedSystemError,
39
53
  };
40
54
 
55
+ if (error.lockErrorCode) {
56
+ Object.defineProperty(this, LOCK_ERROR_CODE_KEY, {
57
+ value: true,
58
+ enumerable: false,
59
+ configurable: true,
60
+ });
61
+ }
62
+
41
63
  Error.captureStackTrace?.(this, AppError);
42
64
  }
43
65
 
66
+ private static isRecord(value: unknown): value is Record<string, unknown> {
67
+ return typeof value === 'object' && value !== null;
68
+ }
69
+
44
70
  public static parseStatusCode(code: unknown): number {
45
71
  const parsed = Number(code);
46
72
  if (!Number.isFinite(parsed)) return 500;
@@ -48,28 +74,86 @@ export class AppError extends Error {
48
74
  return Math.trunc(parsed);
49
75
  }
50
76
 
77
+ public static resolveStatusCode(
78
+ explicitStatusCode: unknown,
79
+ originalError?: unknown,
80
+ ): number {
81
+ const candidates: unknown[] = [explicitStatusCode];
82
+
83
+ if (AppError.isRecord(originalError)) {
84
+ candidates.push(
85
+ originalError.statusCode,
86
+ originalError.status,
87
+ );
88
+
89
+ const body = AppError.isRecord(originalError.body)
90
+ ? originalError.body
91
+ : null;
92
+ if (body) {
93
+ candidates.push(body.statusCode, body.status);
94
+ }
95
+ }
96
+
97
+ for (const candidate of candidates) {
98
+ if (AppError.isHttpStatusCode(candidate)) {
99
+ return Math.trunc(Number(candidate));
100
+ }
101
+ }
102
+
103
+ return 500;
104
+ }
105
+
106
+ public static resolveMessage(errorCode: string, message?: string): string {
107
+ if (message) return message;
108
+ return (
109
+ MESSAGES_EN[errorCode] ||
110
+ MESSAGES_EN[ErrorKeyEnum.UNKNOWN_ERROR] ||
111
+ 'An unexpected error occurred.'
112
+ );
113
+ }
114
+
51
115
  public static isHttpStatusCode(value: unknown): boolean {
52
116
  const parsed = Number(value);
53
117
  return Number.isFinite(parsed) && parsed >= 100 && parsed <= 599;
54
118
  }
55
119
 
56
- public static resolveSystemError(value: unknown): string | null {
120
+ public static normalizeSystemError(value: unknown): string | null {
57
121
  if (typeof value !== 'string') return null;
58
- const normalized = value.trim();
59
- if (!normalized) return null;
60
- return normalized;
122
+ const trimmed = value.trim();
123
+ return trimmed || null;
61
124
  }
62
125
 
63
- public static extractSystemError(details: Record<string, unknown>): string {
64
- const cause = details?.cause as any;
65
- const candidates: unknown[] = [
66
- details?.systemError,
67
- cause?.body?.code,
68
- cause?.details?.code,
69
- ];
126
+ public static resolveSystemError(
127
+ explicitSystemError: unknown,
128
+ originalError?: unknown,
129
+ ): string {
130
+ const candidates: unknown[] = [explicitSystemError];
131
+
132
+ if (AppError.isRecord(originalError)) {
133
+ const details = AppError.isRecord(originalError.details)
134
+ ? originalError.details
135
+ : null;
136
+ const body = AppError.isRecord(originalError.body)
137
+ ? originalError.body
138
+ : null;
139
+ const bodyDetails = body && AppError.isRecord(body.details)
140
+ ? body.details
141
+ : null;
142
+
143
+ candidates.push(
144
+ originalError.systemError,
145
+ originalError.code,
146
+ details?.systemError,
147
+ details?.code,
148
+ body?.systemError,
149
+ body?.code,
150
+ bodyDetails?.systemError,
151
+ bodyDetails?.code,
152
+ );
153
+ }
70
154
 
71
155
  for (const candidate of candidates) {
72
- const resolved = AppError.resolveSystemError(candidate);
156
+ const resolved = AppError.normalizeSystemError(candidate);
73
157
  if (resolved) return resolved;
74
158
  }
75
159
 
@@ -82,50 +166,139 @@ export interface IBuildPublicAppErrorInput {
82
166
  message?: string;
83
167
  details?: Record<string, unknown>;
84
168
  statusCode?: number;
169
+ response?: Response;
170
+ lockErrorCode?: boolean;
85
171
  }
86
172
 
87
- export function buildPublicAppError(
173
+ function markErrorCodeLocked(error: Record<string, unknown>): void {
174
+ Object.defineProperty(error, LOCK_ERROR_CODE_KEY, {
175
+ value: true,
176
+ enumerable: false,
177
+ configurable: true,
178
+ });
179
+ }
180
+
181
+ function hasLockedErrorCode(error: unknown): boolean {
182
+ return isRecord(error) && error[LOCK_ERROR_CODE_KEY] === true;
183
+ }
184
+
185
+ function isTonderAppErrorLike(error: unknown): error is AppError {
186
+ return (
187
+ isRecord(error) &&
188
+ error.name === 'TonderError' &&
189
+ typeof error.code === 'string'
190
+ );
191
+ }
192
+
193
+ function getOriginalError(error: unknown): unknown {
194
+ if (error instanceof AppError) {
195
+ return error.originalError !== undefined ? error.originalError : error;
196
+ }
197
+
198
+ return error;
199
+ }
200
+
201
+ async function parseResponseBody(response: Response): Promise<unknown> {
202
+ try {
203
+ return await response.clone().json();
204
+ } catch (_) {
205
+ // Ignore JSON parsing errors and fallback to text.
206
+ }
207
+
208
+ try {
209
+ const text = await response.text();
210
+ return text || undefined;
211
+ } catch (_) {
212
+ return undefined;
213
+ }
214
+ }
215
+
216
+ function buildPublicAppErrorSync(
88
217
  data: IBuildPublicAppErrorInput,
89
- error?: any,
218
+ error?: unknown,
90
219
  ): AppError {
91
220
  if (!data?.errorCode) {
92
221
  throw new Error('buildPublicAppError requires errorCode');
93
222
  }
94
223
 
95
- const hasDataDetails = !!data.details && Object.keys(data.details).length > 0;
96
- const hasOverrides =
97
- data.errorCode !== error?.code ||
224
+ const explicitSystemError = data.details?.systemError;
225
+ const hasExplicitOverrides =
98
226
  !!data.message ||
99
227
  data.statusCode !== undefined ||
100
- hasDataDetails;
228
+ explicitSystemError !== undefined;
229
+
230
+ if (isTonderAppErrorLike(error)) {
231
+ if (data.lockErrorCode) {
232
+ markErrorCodeLocked(error as unknown as Record<string, unknown>);
233
+ }
101
234
 
102
- if (error instanceof AppError && !hasOverrides) {
103
- return error;
235
+ if (!hasExplicitOverrides) {
236
+ if (data.errorCode === error.code || hasLockedErrorCode(error)) {
237
+ return error;
238
+ }
239
+ }
104
240
  }
105
241
 
106
- const resolvedStatusCode =
107
- data.statusCode ??
108
- (AppError.isHttpStatusCode(error?.statusCode)
109
- ? Number(error.statusCode)
110
- : AppError.isHttpStatusCode(error?.code)
111
- ? Number(error.code)
112
- : AppError.isHttpStatusCode(error?.status)
113
- ? Number(error.status)
114
- : 500);
242
+ return new AppError({
243
+ errorCode: data.errorCode,
244
+ message: data.message,
245
+ statusCode: data.statusCode,
246
+ details:
247
+ explicitSystemError !== undefined
248
+ ? { systemError: explicitSystemError }
249
+ : undefined,
250
+ originalError: getOriginalError(error),
251
+ lockErrorCode: data.lockErrorCode || hasLockedErrorCode(error),
252
+ });
253
+ }
254
+
255
+ async function buildPublicAppErrorFromHttpResponse(
256
+ data: IBuildPublicAppErrorInput & { response: Response },
257
+ ): Promise<AppError> {
258
+ const body = await parseResponseBody(data.response);
115
259
 
116
- const resolvedDetails: Record<string, unknown> = {
117
- ...(error?.details || {}),
118
- ...(data.details || {}),
260
+ const originalError: Record<string, unknown> = {
261
+ status: data.response.status,
119
262
  };
120
263
 
121
- if (resolvedDetails.cause === undefined && error !== undefined) {
122
- resolvedDetails.cause = error;
264
+ if (data.response.statusText) {
265
+ originalError.statusText = data.response.statusText;
266
+ }
267
+
268
+ if (body !== undefined) {
269
+ originalError.body = body;
123
270
  }
124
271
 
125
272
  return new AppError({
126
273
  errorCode: data.errorCode,
127
274
  message: data.message,
128
- details: resolvedDetails,
129
- statusCode: resolvedStatusCode,
275
+ statusCode: data.statusCode ?? data.response.status,
276
+ details:
277
+ data.details?.systemError !== undefined
278
+ ? { systemError: data.details.systemError }
279
+ : undefined,
280
+ originalError,
281
+ lockErrorCode: data.lockErrorCode,
130
282
  });
131
283
  }
284
+
285
+ export function buildPublicAppError(
286
+ data: IBuildPublicAppErrorInput & { response: Response },
287
+ error?: unknown,
288
+ ): Promise<AppError>;
289
+ export function buildPublicAppError(
290
+ data: IBuildPublicAppErrorInput,
291
+ error?: unknown,
292
+ ): AppError;
293
+ export function buildPublicAppError(
294
+ data: IBuildPublicAppErrorInput,
295
+ error?: unknown,
296
+ ): AppError | Promise<AppError> {
297
+ if (data?.response) {
298
+ return buildPublicAppErrorFromHttpResponse(
299
+ data as IBuildPublicAppErrorInput & { response: Response },
300
+ );
301
+ }
302
+
303
+ return buildPublicAppErrorSync(data, error);
304
+ }
@@ -1,7 +1,6 @@
1
1
  import {IConfigureCheckout} from "@tonder.io/ionic-lite-sdk/dist/types/commons";
2
2
  import {
3
3
  IProcessPaymentRequest,
4
- IStartCheckoutErrorResponse,
5
4
  IStartCheckoutResponse
6
5
  } from "@tonder.io/ionic-lite-sdk/dist/types/checkout";
7
6
  import {ITransaction} from "@tonder.io/ionic-lite-sdk/dist/types/transaction";
@@ -32,7 +31,6 @@ export interface IInlineCheckout {
32
31
  *
33
32
  * @throws {Error} Throws an error if the checkout process fails. The error object contains
34
33
  * additional `details` property with the response from the server if available.
35
- * @property {IStartCheckoutErrorResponse} error.details - The response body from the server when an error occurs.
36
34
  *
37
35
  * @public
38
36
  */