@tonder.io/ionic-full-sdk 0.0.62-beta.DEV-2106.1 → 0.0.62-beta.DEV-2106.b837b8f

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.b837b8f",
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,16 +845,7 @@ export class InlineCheckout extends BaseInlineCheckout<IInlineCustomizationOptio
841
845
  currency: this.currency || 'MXN',
842
846
  });
843
847
  } catch (processError) {
844
- throw buildPublicAppError(
845
- {
846
- errorCode: ErrorKeyEnum.CARD_ON_FILE_DECLINED,
847
- message: MESSAGES_EN[ErrorKeyEnum.CARD_ON_FILE_DECLINED],
848
- details: {
849
- cardId,
850
- },
851
- },
852
- processError,
853
- );
848
+ throw processError;
854
849
  }
855
850
 
856
851
  // Update saved card with subscription_id
@@ -871,18 +866,10 @@ export class InlineCheckout extends BaseInlineCheckout<IInlineCustomizationOptio
871
866
  process_id: cardId,
872
867
  metadata: {
873
868
  step: "#processCardOnFilePayment",
869
+ error,
874
870
  },
875
871
  });
876
- throw buildPublicAppError(
877
- {
878
- errorCode: ErrorKeyEnum.CARD_ON_FILE_DECLINED,
879
- message: MESSAGES_EN[ErrorKeyEnum.CARD_ON_FILE_DECLINED],
880
- details: {
881
- cardId,
882
- },
883
- },
884
- error,
885
- );
872
+ return { subscriptionId: null, cardId };
886
873
  }
887
874
  }
888
875
 
@@ -916,7 +903,13 @@ export class InlineCheckout extends BaseInlineCheckout<IInlineCustomizationOptio
916
903
  subscriptionCard = { subscriptionId: cofResponse.subscriptionId };
917
904
  cardIdSaved = cofResponse.cardId;
918
905
  if(!cardIdSaved || !subscriptionCard.subscriptionId) {
919
- throw new Error("Error processing card-on-file subscription");
906
+ throw buildPublicAppError(
907
+ {
908
+ errorCode: ErrorKeyEnum.CARD_ON_FILE_DECLINED,
909
+ lockErrorCode: true,
910
+ message: MESSAGES_EN[ErrorKeyEnum.CARD_ON_FILE_DECLINED],
911
+ },
912
+ );
920
913
  }
921
914
  cardOnFileProcessed = true;
922
915
  }
@@ -966,18 +959,16 @@ export class InlineCheckout extends BaseInlineCheckout<IInlineCustomizationOptio
966
959
  showError("No se han configurado los datos del proveedor de servicio", this.radioChecked, this.collectorIds.msgError)
967
960
  }
968
961
  } 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){
962
+ if(cardIdSaved && auth_token){
972
963
  this.cardsInjected = false
973
- await this._removeCustomerCard(auth_token, this.merchantData!.business.pk, cardIdToCleanup)
964
+ await this._removeCustomerCard(auth_token, this.merchantData!.business.pk, cardIdSaved)
974
965
  await this.#loadCardsList(auth_token)
975
966
  }
976
967
  this.reportSdkError(error, {
977
968
  feature: "payment",
978
- process_id: cardIdToCleanup || this.radioChecked || this.getCustomerId(),
969
+ process_id: cardIdSaved || this.radioChecked || this.getCustomerId(),
979
970
  metadata: {
980
- step: "card_on_file",
971
+ step: "_checkout",
981
972
  },
982
973
  });
983
974
  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
  */