@tonder.io/ionic-full-sdk 0.0.61 → 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/README.md +39 -4
- package/dist/classes/3dsHandler.d.ts +39 -0
- package/dist/classes/checkout.d.ts +31 -0
- package/dist/helpers/sdkInfo.d.ts +4 -0
- package/dist/index.js +1 -1
- package/dist/shared/constants/messages.d.ts +2 -0
- package/dist/shared/enums/ErrorKeyEnum.d.ts +6 -0
- package/dist/shared/utils/appError.d.ts +35 -0
- package/dist/types/inlineCheckout.d.ts +0 -1
- package/package.json +3 -2
- package/rollup.config.js +11 -1
- package/src/classes/inlineCheckout.ts +143 -45
- package/src/helpers/sdkInfo.ts +11 -0
- package/src/shared/constants/messages.ts +13 -0
- package/src/shared/enums/ErrorKeyEnum.ts +6 -0
- package/src/shared/utils/appError.ts +304 -0
- package/src/types/inlineCheckout.ts +0 -2
- package/tsconfig.json +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tonder.io/ionic-full-sdk",
|
|
3
|
-
"version": "0.0.
|
|
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,13 +10,14 @@
|
|
|
10
10
|
"author": "",
|
|
11
11
|
"license": "ISC",
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@tonder.io/ionic-lite-sdk": "
|
|
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",
|
|
17
17
|
"skyflow-js": "2.5.0"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
|
+
"@rollup/plugin-replace": "^6.0.3",
|
|
20
21
|
"@rollup/plugin-terser": "^0.4.4",
|
|
21
22
|
"@rollup/plugin-typescript": "^11.1.6",
|
|
22
23
|
"@types/crypto-js": "^4.2.2",
|
package/rollup.config.js
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
const typescript = require('@rollup/plugin-typescript');
|
|
2
2
|
const terser = require('@rollup/plugin-terser');
|
|
3
3
|
const postcss = require('rollup-plugin-postcss');
|
|
4
|
+
const replace = require('@rollup/plugin-replace');
|
|
5
|
+
const packageJson = require('./package.json');
|
|
4
6
|
|
|
5
7
|
module.exports = {
|
|
6
8
|
input: './src/index.ts',
|
|
7
|
-
output:
|
|
9
|
+
output:
|
|
10
|
+
{
|
|
8
11
|
dir: 'dist',
|
|
9
12
|
format: 'es',
|
|
10
13
|
plugins: [terser()]
|
|
11
14
|
},
|
|
12
15
|
plugins: [
|
|
16
|
+
replace({
|
|
17
|
+
preventAssignment: true,
|
|
18
|
+
values: {
|
|
19
|
+
'__SDK_NAME__': JSON.stringify(packageJson.name),
|
|
20
|
+
'__SDK_VERSION__': JSON.stringify(packageJson.version)
|
|
21
|
+
}
|
|
22
|
+
}),
|
|
13
23
|
typescript({
|
|
14
24
|
exclude: ["tests/**", "jest.config.ts"]
|
|
15
25
|
}),
|
|
@@ -5,15 +5,18 @@ 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
|
-
import {BaseInlineCheckout} from "@tonder.io/ionic-lite-sdk";
|
|
9
|
+
import { BaseInlineCheckout } from "@tonder.io/ionic-lite-sdk";
|
|
11
10
|
import {IProcessPaymentRequest} from "@tonder.io/ionic-lite-sdk/dist/types/checkout";
|
|
12
11
|
import {ISaveCardInternalResponse, ISaveCardSkyflowRequest} from "@tonder.io/ionic-lite-sdk/dist/types/card";
|
|
13
12
|
import {ITonderPaymentMethod} from "@tonder.io/ionic-lite-sdk/dist/types/paymentMethod";
|
|
14
13
|
import type { SkyflowCollectFields, SkyflowCollectResponse } from "@tonder.io/ionic-lite-sdk/dist/types/cardOnFile";
|
|
15
14
|
import {IEvents, IInlineCheckoutOptions, IInlineCustomizationOptions, InCollectorContainer} from "../types/commons";
|
|
16
15
|
import {IInlineCheckout} from "../types/inlineCheckout";
|
|
16
|
+
import { buildPublicAppError } from "../shared/utils/appError";
|
|
17
|
+
import { ErrorKeyEnum } from "../shared/enums/ErrorKeyEnum";
|
|
18
|
+
import { MESSAGES_EN, MESSAGES_ES } from "../shared/constants/messages";
|
|
19
|
+
import { SDK_INFO } from "../helpers/sdkInfo";
|
|
17
20
|
import get from "lodash.get";
|
|
18
21
|
// @ts-ignore
|
|
19
22
|
import Accordion from "accordion-js";
|
|
@@ -63,15 +66,18 @@ export class InlineCheckout extends BaseInlineCheckout<IInlineCustomizationOptio
|
|
|
63
66
|
customization,
|
|
64
67
|
events
|
|
65
68
|
}: IInlineCheckoutOptions) {
|
|
66
|
-
|
|
69
|
+
const baseOptions = {
|
|
67
70
|
apiKey: apiKey,
|
|
68
71
|
returnUrl: returnUrl,
|
|
69
72
|
mode: mode,
|
|
70
73
|
callBack: callBack,
|
|
71
74
|
customization: customization,
|
|
72
75
|
tdsIframeId: collectorIds && 'tdsIframe' in collectorIds ? collectorIds?.tdsIframe : "tdsIframe",
|
|
73
|
-
tonderPayButtonId: collectorIds ? collectorIds?.tonderPayButton : "tonderPayButton"
|
|
74
|
-
|
|
76
|
+
tonderPayButtonId: collectorIds ? collectorIds?.tonderPayButton : "tonderPayButton",
|
|
77
|
+
sdkInfo: SDK_INFO,
|
|
78
|
+
};
|
|
79
|
+
super(baseOptions);
|
|
80
|
+
|
|
75
81
|
this.renderPaymentButton = renderPaymentButton || false;
|
|
76
82
|
this.renderSaveCardButton = renderSaveCardButton;
|
|
77
83
|
this.customStyles = styles
|
|
@@ -334,6 +340,14 @@ export class InlineCheckout extends BaseInlineCheckout<IInlineCustomizationOptio
|
|
|
334
340
|
this.#mountPayButton(container_radio_id)
|
|
335
341
|
}catch (e){
|
|
336
342
|
console.error("Ha ocurrido un error", e);
|
|
343
|
+
this.reportSdkError(e, {
|
|
344
|
+
feature: "handle-open-card-accordion",
|
|
345
|
+
process_id: container_radio_id,
|
|
346
|
+
metadata: {
|
|
347
|
+
step: "#handleOpenCardAccordion",
|
|
348
|
+
type,
|
|
349
|
+
},
|
|
350
|
+
});
|
|
337
351
|
}
|
|
338
352
|
await this.#handleRadioButtonClick(radio_card, null, type);
|
|
339
353
|
}
|
|
@@ -386,8 +400,8 @@ export class InlineCheckout extends BaseInlineCheckout<IInlineCustomizationOptio
|
|
|
386
400
|
async #handlePaymentClick() {
|
|
387
401
|
try {
|
|
388
402
|
await this.payment({} as IProcessPaymentRequest);
|
|
389
|
-
} catch (error) {
|
|
390
|
-
console.error("Payment error:", error);
|
|
403
|
+
} catch (error: any) {
|
|
404
|
+
console.error("Payment error:", error?.message, error?.code, error?.details);
|
|
391
405
|
}
|
|
392
406
|
}
|
|
393
407
|
|
|
@@ -677,7 +691,7 @@ export class InlineCheckout extends BaseInlineCheckout<IInlineCustomizationOptio
|
|
|
677
691
|
try{
|
|
678
692
|
if(this.merchantData!) {
|
|
679
693
|
const cardTokensSkyflowTonder: any = await this.#getCardTokens(this.collectorIds.tonderSaveCardButton!);
|
|
680
|
-
const customerResponse : CustomerRegisterResponse
|
|
694
|
+
const customerResponse : CustomerRegisterResponse = await this._getCustomer() as CustomerRegisterResponse;
|
|
681
695
|
|
|
682
696
|
if("auth_token" in customerResponse && this.secureToken) {
|
|
683
697
|
// @ts-ignore
|
|
@@ -702,23 +716,35 @@ export class InlineCheckout extends BaseInlineCheckout<IInlineCustomizationOptio
|
|
|
702
716
|
|
|
703
717
|
// 2. Process card on file
|
|
704
718
|
const cardOnFile = await this._initializeCardOnFile();
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
719
|
+
let result: { subscriptionId: string };
|
|
720
|
+
try {
|
|
721
|
+
result = await cardOnFile.process({
|
|
722
|
+
cardTokens: {
|
|
723
|
+
name: cardTokensSkyflowTonder.cardholder_name,
|
|
724
|
+
number: cardTokensSkyflowTonder.card_number,
|
|
725
|
+
expiryMonth: cardTokensSkyflowTonder.expiration_month,
|
|
726
|
+
expiryYear: cardTokensSkyflowTonder.expiration_year,
|
|
727
|
+
cvv: cardTokensSkyflowTonder.cvv,
|
|
728
|
+
},
|
|
729
|
+
cardBin,
|
|
730
|
+
contactDetails: {
|
|
731
|
+
firstName: first_name || '',
|
|
732
|
+
lastName: last_name || '',
|
|
733
|
+
email: email || '',
|
|
734
|
+
},
|
|
735
|
+
customerId: authToken,
|
|
736
|
+
currency: this.currency || 'MXN',
|
|
737
|
+
});
|
|
738
|
+
} catch (processError) {
|
|
739
|
+
throw buildPublicAppError(
|
|
740
|
+
{
|
|
741
|
+
errorCode: ErrorKeyEnum.CARD_ON_FILE_DECLINED,
|
|
742
|
+
lockErrorCode: true,
|
|
743
|
+
message: MESSAGES_EN[ErrorKeyEnum.CARD_ON_FILE_DECLINED],
|
|
744
|
+
},
|
|
745
|
+
processError,
|
|
746
|
+
);
|
|
747
|
+
}
|
|
722
748
|
|
|
723
749
|
// 3. Save card with subscription_id
|
|
724
750
|
await this.#handleSaveCard(
|
|
@@ -749,8 +775,21 @@ export class InlineCheckout extends BaseInlineCheckout<IInlineCustomizationOptio
|
|
|
749
775
|
if(cardId && auth_token){
|
|
750
776
|
await this._removeCustomerCard(auth_token, this.merchantData!.business.pk, cardId)
|
|
751
777
|
}
|
|
778
|
+
this.reportSdkError(error, {
|
|
779
|
+
feature: "save-card",
|
|
780
|
+
process_id: cardId,
|
|
781
|
+
metadata: {
|
|
782
|
+
step: "#validateAndSaveCard",
|
|
783
|
+
},
|
|
784
|
+
});
|
|
752
785
|
const exist_msg_error = typeof error === "object" && error !== null && "message" in error;
|
|
753
|
-
|
|
786
|
+
const visualMessage =
|
|
787
|
+
(error as any)?.code === ErrorKeyEnum.CARD_ON_FILE_DECLINED
|
|
788
|
+
? MESSAGES_ES[ErrorKeyEnum.CARD_ON_FILE_DECLINED]
|
|
789
|
+
: exist_msg_error
|
|
790
|
+
? (error?.message as string)
|
|
791
|
+
: "Ha ocurrido un error";
|
|
792
|
+
showError(visualMessage, this.radioChecked, this.collectorIds.msgError)
|
|
754
793
|
throw error;
|
|
755
794
|
} finally {}
|
|
756
795
|
}
|
|
@@ -786,23 +825,46 @@ export class InlineCheckout extends BaseInlineCheckout<IInlineCustomizationOptio
|
|
|
786
825
|
throw new Error("Card BIN not returned from save card");
|
|
787
826
|
}
|
|
788
827
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
828
|
+
let result: { subscriptionId: string };
|
|
829
|
+
try {
|
|
830
|
+
result = await cardOnFile.process({
|
|
831
|
+
cardTokens: {
|
|
832
|
+
name: cardFields.cardholder_name,
|
|
833
|
+
number: cardFields.card_number,
|
|
834
|
+
expiryMonth: cardFields.expiration_month,
|
|
835
|
+
expiryYear: cardFields.expiration_year,
|
|
836
|
+
cvv: cardFields.cvv,
|
|
837
|
+
},
|
|
838
|
+
cardBin,
|
|
839
|
+
contactDetails: {
|
|
840
|
+
firstName: first_name || '',
|
|
841
|
+
lastName: last_name || '',
|
|
842
|
+
email: email || '',
|
|
843
|
+
},
|
|
844
|
+
customerId: auth_token,
|
|
845
|
+
currency: this.currency || 'MXN',
|
|
846
|
+
});
|
|
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",
|
|
802
854
|
},
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
855
|
+
});
|
|
856
|
+
throw buildPublicAppError(
|
|
857
|
+
{
|
|
858
|
+
errorCode: ErrorKeyEnum.CARD_ON_FILE_DECLINED,
|
|
859
|
+
lockErrorCode: true,
|
|
860
|
+
message: MESSAGES_EN[ErrorKeyEnum.CARD_ON_FILE_DECLINED],
|
|
861
|
+
details: {
|
|
862
|
+
cardId,
|
|
863
|
+
},
|
|
864
|
+
},
|
|
865
|
+
processError,
|
|
866
|
+
);
|
|
867
|
+
}
|
|
806
868
|
|
|
807
869
|
// Update saved card with subscription_id
|
|
808
870
|
await this.#handleSaveCard(
|
|
@@ -817,7 +879,25 @@ export class InlineCheckout extends BaseInlineCheckout<IInlineCustomizationOptio
|
|
|
817
879
|
|
|
818
880
|
return { subscriptionId: result.subscriptionId, cardId: saveCardResponse.skyflow_id };
|
|
819
881
|
}catch (error) {
|
|
820
|
-
|
|
882
|
+
this.reportSdkError(error, {
|
|
883
|
+
feature: "payment",
|
|
884
|
+
process_id: cardId,
|
|
885
|
+
metadata: {
|
|
886
|
+
step: "#processCardOnFilePayment",
|
|
887
|
+
error,
|
|
888
|
+
},
|
|
889
|
+
});
|
|
890
|
+
throw buildPublicAppError(
|
|
891
|
+
{
|
|
892
|
+
errorCode: ErrorKeyEnum.CARD_ON_FILE_DECLINED,
|
|
893
|
+
lockErrorCode: true,
|
|
894
|
+
message: MESSAGES_EN[ErrorKeyEnum.CARD_ON_FILE_DECLINED],
|
|
895
|
+
details: {
|
|
896
|
+
cardId,
|
|
897
|
+
},
|
|
898
|
+
},
|
|
899
|
+
error,
|
|
900
|
+
);
|
|
821
901
|
}
|
|
822
902
|
}
|
|
823
903
|
|
|
@@ -906,7 +986,18 @@ export class InlineCheckout extends BaseInlineCheckout<IInlineCustomizationOptio
|
|
|
906
986
|
await this._removeCustomerCard(auth_token, this.merchantData!.business.pk, cardIdSaved)
|
|
907
987
|
await this.#loadCardsList(auth_token)
|
|
908
988
|
}
|
|
909
|
-
|
|
989
|
+
this.reportSdkError(error, {
|
|
990
|
+
feature: "payment",
|
|
991
|
+
process_id: cardIdSaved || this.radioChecked || this.getCustomerId(),
|
|
992
|
+
metadata: {
|
|
993
|
+
step: "_checkout",
|
|
994
|
+
},
|
|
995
|
+
});
|
|
996
|
+
const visualMessage =
|
|
997
|
+
(error as any)?.code === ErrorKeyEnum.CARD_ON_FILE_DECLINED
|
|
998
|
+
? MESSAGES_ES[ErrorKeyEnum.CARD_ON_FILE_DECLINED]
|
|
999
|
+
: "Ha ocurrido un error";
|
|
1000
|
+
showError(visualMessage, this.radioChecked, this.collectorIds.msgError)
|
|
910
1001
|
throw error;
|
|
911
1002
|
} finally {
|
|
912
1003
|
this.#updatePayButton({cardId: this.radioChecked, disabled: false});
|
|
@@ -916,7 +1007,7 @@ export class InlineCheckout extends BaseInlineCheckout<IInlineCustomizationOptio
|
|
|
916
1007
|
async #handleSaveCard(
|
|
917
1008
|
authToken: string,
|
|
918
1009
|
businessId: string | number,
|
|
919
|
-
cardTokens: ISaveCardSkyflowRequest
|
|
1010
|
+
cardTokens: ISaveCardSkyflowRequest,
|
|
920
1011
|
options: { forceSave?: boolean; showNotification?: boolean; appOrigin?: boolean; refreshCards?: boolean } = {}
|
|
921
1012
|
): Promise<ISaveCardInternalResponse | undefined> {
|
|
922
1013
|
// Validate required data
|
|
@@ -944,6 +1035,13 @@ export class InlineCheckout extends BaseInlineCheckout<IInlineCustomizationOptio
|
|
|
944
1035
|
showError("No se han configurado los datos de seguridad para guardar tarjeta", this.radioChecked, this.collectorIds.msgError);
|
|
945
1036
|
}
|
|
946
1037
|
} catch (error) {
|
|
1038
|
+
this.reportSdkError(error, {
|
|
1039
|
+
feature: "save-card",
|
|
1040
|
+
process_id: cardTokens.skyflow_id || this.getCustomerId(),
|
|
1041
|
+
metadata: {
|
|
1042
|
+
step: "#handleSaveCard",
|
|
1043
|
+
},
|
|
1044
|
+
});
|
|
947
1045
|
const exist_msg_error = typeof error === "object" && error !== null && "message" in error;
|
|
948
1046
|
if (exist_msg_error && error?.message !== 'Error') {
|
|
949
1047
|
showError(error?.message as string, this.radioChecked, this.collectorIds.msgError);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK information injected from package.json at build time
|
|
3
|
+
* Values are replaced by Rollup's replace plugin
|
|
4
|
+
*/
|
|
5
|
+
declare const __SDK_NAME__: string;
|
|
6
|
+
declare const __SDK_VERSION__: string;
|
|
7
|
+
|
|
8
|
+
export const SDK_INFO = Object.freeze({
|
|
9
|
+
name: __SDK_NAME__,
|
|
10
|
+
version: __SDK_VERSION__,
|
|
11
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ErrorKeyEnum } from "../enums/ErrorKeyEnum";
|
|
2
|
+
|
|
3
|
+
export const MESSAGES_EN: Record<string, string> = {
|
|
4
|
+
[ErrorKeyEnum.CARD_ON_FILE_DECLINED]:
|
|
5
|
+
"Transaction declined. Please verify your card details.",
|
|
6
|
+
[ErrorKeyEnum.UNKNOWN_ERROR]: 'An unexpected error occurred.',
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const MESSAGES_ES: Record<string, string> = {
|
|
10
|
+
[ErrorKeyEnum.CARD_ON_FILE_DECLINED]:
|
|
11
|
+
"Transaccion declinada. Verique los datos de su tarjeta.",
|
|
12
|
+
[ErrorKeyEnum.UNKNOWN_ERROR]: 'Ocurrió un error inesperado.',
|
|
13
|
+
};
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { MESSAGES_EN } from '../constants/messages';
|
|
2
|
+
import { ErrorKeyEnum } from '../enums/ErrorKeyEnum';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_SYSTEM_ERROR = 'APP_INTERNAL_001';
|
|
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
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface IAppErrorInput {
|
|
12
|
+
errorCode: string;
|
|
13
|
+
message?: string;
|
|
14
|
+
statusCode?: number;
|
|
15
|
+
details?: Record<string, unknown>;
|
|
16
|
+
originalError?: unknown;
|
|
17
|
+
lockErrorCode?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class AppError extends Error {
|
|
21
|
+
public readonly status: 'error' = 'error';
|
|
22
|
+
public readonly code: string;
|
|
23
|
+
public readonly statusCode: number;
|
|
24
|
+
public readonly originalError?: unknown;
|
|
25
|
+
public details: Record<string, unknown>;
|
|
26
|
+
|
|
27
|
+
constructor(error: IAppErrorInput) {
|
|
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
|
+
);
|
|
40
|
+
|
|
41
|
+
super(resolvedMessage);
|
|
42
|
+
|
|
43
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
44
|
+
|
|
45
|
+
this.name = 'TonderError';
|
|
46
|
+
this.code = error.errorCode;
|
|
47
|
+
this.statusCode = resolvedStatusCode;
|
|
48
|
+
this.originalError = error.originalError;
|
|
49
|
+
this.details = {
|
|
50
|
+
code: error.errorCode,
|
|
51
|
+
statusCode: resolvedStatusCode,
|
|
52
|
+
systemError: resolvedSystemError,
|
|
53
|
+
};
|
|
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
|
+
|
|
63
|
+
Error.captureStackTrace?.(this, AppError);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private static isRecord(value: unknown): value is Record<string, unknown> {
|
|
67
|
+
return typeof value === 'object' && value !== null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public static parseStatusCode(code: unknown): number {
|
|
71
|
+
const parsed = Number(code);
|
|
72
|
+
if (!Number.isFinite(parsed)) return 500;
|
|
73
|
+
if (parsed < 100 || parsed > 599) return 500;
|
|
74
|
+
return Math.trunc(parsed);
|
|
75
|
+
}
|
|
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
|
+
|
|
115
|
+
public static isHttpStatusCode(value: unknown): boolean {
|
|
116
|
+
const parsed = Number(value);
|
|
117
|
+
return Number.isFinite(parsed) && parsed >= 100 && parsed <= 599;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
public static normalizeSystemError(value: unknown): string | null {
|
|
121
|
+
if (typeof value !== 'string') return null;
|
|
122
|
+
const trimmed = value.trim();
|
|
123
|
+
return trimmed || null;
|
|
124
|
+
}
|
|
125
|
+
|
|
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
|
+
}
|
|
154
|
+
|
|
155
|
+
for (const candidate of candidates) {
|
|
156
|
+
const resolved = AppError.normalizeSystemError(candidate);
|
|
157
|
+
if (resolved) return resolved;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return DEFAULT_SYSTEM_ERROR;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export interface IBuildPublicAppErrorInput {
|
|
165
|
+
errorCode: string;
|
|
166
|
+
message?: string;
|
|
167
|
+
details?: Record<string, unknown>;
|
|
168
|
+
statusCode?: number;
|
|
169
|
+
response?: Response;
|
|
170
|
+
lockErrorCode?: boolean;
|
|
171
|
+
}
|
|
172
|
+
|
|
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(
|
|
217
|
+
data: IBuildPublicAppErrorInput,
|
|
218
|
+
error?: unknown,
|
|
219
|
+
): AppError {
|
|
220
|
+
if (!data?.errorCode) {
|
|
221
|
+
throw new Error('buildPublicAppError requires errorCode');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const explicitSystemError = data.details?.systemError;
|
|
225
|
+
const hasExplicitOverrides =
|
|
226
|
+
!!data.message ||
|
|
227
|
+
data.statusCode !== undefined ||
|
|
228
|
+
explicitSystemError !== undefined;
|
|
229
|
+
|
|
230
|
+
if (isTonderAppErrorLike(error)) {
|
|
231
|
+
if (data.lockErrorCode) {
|
|
232
|
+
markErrorCodeLocked(error as unknown as Record<string, unknown>);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (!hasExplicitOverrides) {
|
|
236
|
+
if (data.errorCode === error.code || hasLockedErrorCode(error)) {
|
|
237
|
+
return error;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
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);
|
|
259
|
+
|
|
260
|
+
const originalError: Record<string, unknown> = {
|
|
261
|
+
status: data.response.status,
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
if (data.response.statusText) {
|
|
265
|
+
originalError.statusText = data.response.statusText;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (body !== undefined) {
|
|
269
|
+
originalError.body = body;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return new AppError({
|
|
273
|
+
errorCode: data.errorCode,
|
|
274
|
+
message: data.message,
|
|
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,
|
|
282
|
+
});
|
|
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
|
*/
|