@yosle/tropipayjs 0.1.22 → 0.1.24
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/.nvmrc +1 -1
- package/.vscode/launch.json +18 -18
- package/LICENSE +9 -9
- package/README.md +152 -152
- package/api/TropipayAPI.d.ts +2 -0
- package/docs/readme.md +3 -3
- package/globals.d.ts +75 -75
- package/index.d.ts +11 -6
- package/index.js +910 -870
- package/interfaces/countries/index.d.ts +1 -1
- package/interfaces/index.d.ts +9 -6
- package/jest.config.js +5 -5
- package/package.json +49 -49
- package/paymentcard/paymentcard.spec.d.ts +1 -1
- package/rollup.config.js +24 -24
- package/src/api/TropipayAPI.ts +383 -338
- package/src/config/TropipayConfig.ts +2 -2
- package/src/constants/TropipayConstants.ts +2 -2
- package/src/depositAccount/depositAccounts.ts +139 -139
- package/src/hooks/TropipayHooks.ts +154 -154
- package/src/index.ts +28 -28
- package/src/interfaces/depositaccounts/index.ts +70 -70
- package/src/interfaces/index.js +1 -1
- package/src/interfaces/index.ts +201 -197
- package/src/interfaces/mediation/index.ts +22 -22
- package/src/interfaces/paymentlink/index.d.ts +36 -36
- package/src/interfaces/paymentlink/index.js +2 -2
- package/src/interfaces/paymentlink/index.ts +91 -91
- package/src/mediationPaymentCard/MediationPaymentCard.ts +42 -42
- package/src/paymentcard/PaymentCard.ts +123 -123
- package/src/paymentcard/paymentcard.spec.ts +79 -79
- package/src/utils/TropipayUtils.ts +126 -126
- package/src/utils/errors.ts +92 -92
- package/tsconfig.json +96 -96
|
@@ -1,79 +1,79 @@
|
|
|
1
|
-
import { Tropipay } from "../../src/api/TropipayAPI";
|
|
2
|
-
import { PaymentLinkPayload } from "../interfaces";
|
|
3
|
-
import { TropipayJSException } from "../utils/errors";
|
|
4
|
-
import PaymentCard from "./PaymentCard";
|
|
5
|
-
|
|
6
|
-
jest.mock("axios");
|
|
7
|
-
jest.mock("../../src/api/TropipayAPI");
|
|
8
|
-
|
|
9
|
-
describe("Create PaymentCard", () => {
|
|
10
|
-
describe("create", () => {
|
|
11
|
-
it("should create a payment link", async () => {
|
|
12
|
-
const payload: PaymentLinkPayload = {
|
|
13
|
-
amount: 200,
|
|
14
|
-
client: null,
|
|
15
|
-
concept: "Concept",
|
|
16
|
-
currency: "EUR",
|
|
17
|
-
description: "Description",
|
|
18
|
-
directPayment: true,
|
|
19
|
-
expirationDays: 30,
|
|
20
|
-
favorite: false,
|
|
21
|
-
reasonId: 45,
|
|
22
|
-
reference: "Reference",
|
|
23
|
-
serviceDate: "2022-01-01",
|
|
24
|
-
urlFailed: "urlFailed",
|
|
25
|
-
urlNotification: "urlNotification",
|
|
26
|
-
urlSuccess: "urlSuccess",
|
|
27
|
-
singleUse: false,
|
|
28
|
-
paymentMethods: ["EXT"],
|
|
29
|
-
lang: "es",
|
|
30
|
-
};
|
|
31
|
-
const expectedPaymentLink = {
|
|
32
|
-
id: "1f8b0600-0b62-11ef-b879-9d16dab40e58",
|
|
33
|
-
reference: "08025834659",
|
|
34
|
-
concept: "NFT Purchase",
|
|
35
|
-
description: "Golden Monkey NFT character",
|
|
36
|
-
amount: 500,
|
|
37
|
-
currency: "EUR",
|
|
38
|
-
singleUse: true,
|
|
39
|
-
favorite: false,
|
|
40
|
-
reasonId: 4,
|
|
41
|
-
reasonDes: null,
|
|
42
|
-
expirationDays: 0,
|
|
43
|
-
userId: "a9546500-65c0-11ed-97e6-2ffd2c53074d",
|
|
44
|
-
lang: "es",
|
|
45
|
-
imageBase: null,
|
|
46
|
-
state: 1,
|
|
47
|
-
urlSuccess: null,
|
|
48
|
-
urlFailed: null,
|
|
49
|
-
urlNotification: null,
|
|
50
|
-
expirationDate: null,
|
|
51
|
-
serviceDate: null,
|
|
52
|
-
hasClient: false,
|
|
53
|
-
credentialId: 129710,
|
|
54
|
-
force3ds: false,
|
|
55
|
-
saveToken: true,
|
|
56
|
-
origin: 2,
|
|
57
|
-
paymentcardType: 1,
|
|
58
|
-
updatedAt: "2024-05-06T04:35:54.352Z",
|
|
59
|
-
createdAt: "2024-05-06T04:35:53.568Z",
|
|
60
|
-
qrImage:
|
|
61
|
-
"data:image/png;base64,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",
|
|
62
|
-
shortUrl: "https://tppay.me/lvugy80i",
|
|
63
|
-
paymentUrl: null,
|
|
64
|
-
rawUrlPayment: null,
|
|
65
|
-
giftcard: null,
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const tropipayInstance = new Tropipay({
|
|
69
|
-
clientId: "clientId",
|
|
70
|
-
clientSecret: "clientSecret",
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
const result = await tropipayInstance.paymentCards.create(payload);
|
|
74
|
-
|
|
75
|
-
// expect(result).toEqual(expectedPaymentLink);
|
|
76
|
-
expect(true).toBeDefined();
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
});
|
|
1
|
+
import { Tropipay } from "../../src/api/TropipayAPI";
|
|
2
|
+
import { PaymentLinkPayload } from "../interfaces";
|
|
3
|
+
import { TropipayJSException } from "../utils/errors";
|
|
4
|
+
import PaymentCard from "./PaymentCard";
|
|
5
|
+
|
|
6
|
+
jest.mock("axios");
|
|
7
|
+
jest.mock("../../src/api/TropipayAPI");
|
|
8
|
+
|
|
9
|
+
describe("Create PaymentCard", () => {
|
|
10
|
+
describe("create", () => {
|
|
11
|
+
it("should create a payment link", async () => {
|
|
12
|
+
const payload: PaymentLinkPayload = {
|
|
13
|
+
amount: 200,
|
|
14
|
+
client: null,
|
|
15
|
+
concept: "Concept",
|
|
16
|
+
currency: "EUR",
|
|
17
|
+
description: "Description",
|
|
18
|
+
directPayment: true,
|
|
19
|
+
expirationDays: 30,
|
|
20
|
+
favorite: false,
|
|
21
|
+
reasonId: 45,
|
|
22
|
+
reference: "Reference",
|
|
23
|
+
serviceDate: "2022-01-01",
|
|
24
|
+
urlFailed: "urlFailed",
|
|
25
|
+
urlNotification: "urlNotification",
|
|
26
|
+
urlSuccess: "urlSuccess",
|
|
27
|
+
singleUse: false,
|
|
28
|
+
paymentMethods: ["EXT"],
|
|
29
|
+
lang: "es",
|
|
30
|
+
};
|
|
31
|
+
const expectedPaymentLink = {
|
|
32
|
+
id: "1f8b0600-0b62-11ef-b879-9d16dab40e58",
|
|
33
|
+
reference: "08025834659",
|
|
34
|
+
concept: "NFT Purchase",
|
|
35
|
+
description: "Golden Monkey NFT character",
|
|
36
|
+
amount: 500,
|
|
37
|
+
currency: "EUR",
|
|
38
|
+
singleUse: true,
|
|
39
|
+
favorite: false,
|
|
40
|
+
reasonId: 4,
|
|
41
|
+
reasonDes: null,
|
|
42
|
+
expirationDays: 0,
|
|
43
|
+
userId: "a9546500-65c0-11ed-97e6-2ffd2c53074d",
|
|
44
|
+
lang: "es",
|
|
45
|
+
imageBase: null,
|
|
46
|
+
state: 1,
|
|
47
|
+
urlSuccess: null,
|
|
48
|
+
urlFailed: null,
|
|
49
|
+
urlNotification: null,
|
|
50
|
+
expirationDate: null,
|
|
51
|
+
serviceDate: null,
|
|
52
|
+
hasClient: false,
|
|
53
|
+
credentialId: 129710,
|
|
54
|
+
force3ds: false,
|
|
55
|
+
saveToken: true,
|
|
56
|
+
origin: 2,
|
|
57
|
+
paymentcardType: 1,
|
|
58
|
+
updatedAt: "2024-05-06T04:35:54.352Z",
|
|
59
|
+
createdAt: "2024-05-06T04:35:53.568Z",
|
|
60
|
+
qrImage:
|
|
61
|
+
"data:image/png;base64,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",
|
|
62
|
+
shortUrl: "https://tppay.me/lvugy80i",
|
|
63
|
+
paymentUrl: null,
|
|
64
|
+
rawUrlPayment: null,
|
|
65
|
+
giftcard: null,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const tropipayInstance = new Tropipay({
|
|
69
|
+
clientId: "clientId",
|
|
70
|
+
clientSecret: "clientSecret",
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const result = await tropipayInstance.paymentCards.create(payload);
|
|
74
|
+
|
|
75
|
+
// expect(result).toEqual(expectedPaymentLink);
|
|
76
|
+
expect(true).toBeDefined();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -1,126 +1,126 @@
|
|
|
1
|
-
import * as crypto from "crypto";
|
|
2
|
-
import { Tropipay } from "../api/TropipayAPI";
|
|
3
|
-
|
|
4
|
-
import axios from "axios";
|
|
5
|
-
import fs from "fs/promises";
|
|
6
|
-
import { MAX_IMAGE_SIZE_MB } from "../constants/TropipayConstants";
|
|
7
|
-
export class ServerSideUtils {
|
|
8
|
-
private tropipay: Tropipay;
|
|
9
|
-
constructor(tropipayInstance: Tropipay) {
|
|
10
|
-
this.tropipay = tropipayInstance;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Verify Topipay's signature on webhooks.
|
|
15
|
-
* @param credentials Credential object or Tropipay instance
|
|
16
|
-
* @param {String} originalCurrencyAmount
|
|
17
|
-
* @param bankOrderCode
|
|
18
|
-
* @param signature
|
|
19
|
-
* @returns {Boolean}
|
|
20
|
-
*/
|
|
21
|
-
public static verifySignature(
|
|
22
|
-
credentials:
|
|
23
|
-
| {
|
|
24
|
-
clientId: string;
|
|
25
|
-
clientSecret: string;
|
|
26
|
-
}
|
|
27
|
-
| Tropipay,
|
|
28
|
-
originalCurrencyAmount: string,
|
|
29
|
-
bankOrderCode: string,
|
|
30
|
-
signature: string
|
|
31
|
-
): boolean {
|
|
32
|
-
const localSignature = crypto
|
|
33
|
-
.createHash("sha256")
|
|
34
|
-
.update(
|
|
35
|
-
bankOrderCode +
|
|
36
|
-
credentials.clientId +
|
|
37
|
-
credentials.clientSecret +
|
|
38
|
-
originalCurrencyAmount
|
|
39
|
-
)
|
|
40
|
-
.digest("hex");
|
|
41
|
-
return localSignature === signature;
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Checks if the provided base64 string represents a square image.
|
|
45
|
-
*
|
|
46
|
-
* @param {string} base64String - The base64 string of the image
|
|
47
|
-
* @return {Promise<boolean>} A Promise that resolves to a boolean indicating whether the image is square
|
|
48
|
-
*/
|
|
49
|
-
public static async isBase64ImageSquare(
|
|
50
|
-
base64String: string
|
|
51
|
-
): Promise<boolean> {
|
|
52
|
-
return new Promise<boolean>((resolve, reject) => {
|
|
53
|
-
const img = new Image();
|
|
54
|
-
|
|
55
|
-
img.onload = () => {
|
|
56
|
-
// Check if the image has a 1:1 aspect ratio
|
|
57
|
-
const isSquare = img.width === img.height;
|
|
58
|
-
resolve(isSquare);
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
img.onerror = (error) => {
|
|
62
|
-
reject(error);
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
img.src = base64String;
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Takes a local file path and returns a base64 representation of the file content.
|
|
71
|
-
*
|
|
72
|
-
* @param {string} filepath - the path of the file to be converted to base64
|
|
73
|
-
* @return {Promise<string>} a Promise that resolves to the base64 representation of the file content
|
|
74
|
-
*/
|
|
75
|
-
public static async fileToBase64(filepath: string): Promise<string> {
|
|
76
|
-
const contents = await fs.readFile(filepath);
|
|
77
|
-
let base64content = contents.toString("base64");
|
|
78
|
-
const ext = filepath.split(".").pop();
|
|
79
|
-
return `data:image/${ext};base64,` + base64content;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Get the base64 representation of a remote file from the given URL.
|
|
84
|
-
*
|
|
85
|
-
* @param {string} url - the URL of the file
|
|
86
|
-
* @return {Promise<string>} the base64 representation of the file
|
|
87
|
-
*/
|
|
88
|
-
public static async getBase64FromFileUrl(url: string): Promise<string> {
|
|
89
|
-
const response = await axios.get(url, {
|
|
90
|
-
responseType: "arraybuffer",
|
|
91
|
-
maxRedirects: 5,
|
|
92
|
-
});
|
|
93
|
-
return Buffer.from(response.data, "binary").toString("base64");
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Check if the base64 string represents a valid image and has a valid size
|
|
98
|
-
*
|
|
99
|
-
* @param {string} base64Image - the base64 image to be validated
|
|
100
|
-
* @return {Promise<string>} the valid base64 image
|
|
101
|
-
*/
|
|
102
|
-
public static isValidImage(base64Image: string): boolean {
|
|
103
|
-
// Check if the base64 string represents a valid image and has a valid size
|
|
104
|
-
try {
|
|
105
|
-
const isBase64Image = base64Image.startsWith("data:image/");
|
|
106
|
-
if (!isBase64Image) {
|
|
107
|
-
console.error("Not a valid base64 image");
|
|
108
|
-
throw new Error("Not a valid base64 image");
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// TODO: use constants intead of magic numbers here
|
|
112
|
-
const isSizeLessThan2MB =
|
|
113
|
-
base64Image.length * 0.75 <= MAX_IMAGE_SIZE_MB * 1024 * 1024;
|
|
114
|
-
|
|
115
|
-
if (!isSizeLessThan2MB) {
|
|
116
|
-
console.error(`The image should be less than 2mb`);
|
|
117
|
-
throw new Error(`The image should be less than 2mb`);
|
|
118
|
-
}
|
|
119
|
-
// all is fine
|
|
120
|
-
return true;
|
|
121
|
-
} catch (error) {
|
|
122
|
-
console.error("Error checking base64 image:", error);
|
|
123
|
-
throw new Error(`Error checking base64 image`);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
1
|
+
import * as crypto from "crypto";
|
|
2
|
+
import { Tropipay } from "../api/TropipayAPI";
|
|
3
|
+
|
|
4
|
+
import axios from "axios";
|
|
5
|
+
import fs from "fs/promises";
|
|
6
|
+
import { MAX_IMAGE_SIZE_MB } from "../constants/TropipayConstants";
|
|
7
|
+
export class ServerSideUtils {
|
|
8
|
+
private tropipay: Tropipay;
|
|
9
|
+
constructor(tropipayInstance: Tropipay) {
|
|
10
|
+
this.tropipay = tropipayInstance;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Verify Topipay's signature on webhooks.
|
|
15
|
+
* @param credentials Credential object or Tropipay instance
|
|
16
|
+
* @param {String} originalCurrencyAmount
|
|
17
|
+
* @param bankOrderCode
|
|
18
|
+
* @param signature
|
|
19
|
+
* @returns {Boolean}
|
|
20
|
+
*/
|
|
21
|
+
public static verifySignature(
|
|
22
|
+
credentials:
|
|
23
|
+
| {
|
|
24
|
+
clientId: string;
|
|
25
|
+
clientSecret: string;
|
|
26
|
+
}
|
|
27
|
+
| Tropipay,
|
|
28
|
+
originalCurrencyAmount: string,
|
|
29
|
+
bankOrderCode: string,
|
|
30
|
+
signature: string
|
|
31
|
+
): boolean {
|
|
32
|
+
const localSignature = crypto
|
|
33
|
+
.createHash("sha256")
|
|
34
|
+
.update(
|
|
35
|
+
bankOrderCode +
|
|
36
|
+
credentials.clientId +
|
|
37
|
+
credentials.clientSecret +
|
|
38
|
+
originalCurrencyAmount
|
|
39
|
+
)
|
|
40
|
+
.digest("hex");
|
|
41
|
+
return localSignature === signature;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Checks if the provided base64 string represents a square image.
|
|
45
|
+
*
|
|
46
|
+
* @param {string} base64String - The base64 string of the image
|
|
47
|
+
* @return {Promise<boolean>} A Promise that resolves to a boolean indicating whether the image is square
|
|
48
|
+
*/
|
|
49
|
+
public static async isBase64ImageSquare(
|
|
50
|
+
base64String: string
|
|
51
|
+
): Promise<boolean> {
|
|
52
|
+
return new Promise<boolean>((resolve, reject) => {
|
|
53
|
+
const img = new Image();
|
|
54
|
+
|
|
55
|
+
img.onload = () => {
|
|
56
|
+
// Check if the image has a 1:1 aspect ratio
|
|
57
|
+
const isSquare = img.width === img.height;
|
|
58
|
+
resolve(isSquare);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
img.onerror = (error) => {
|
|
62
|
+
reject(error);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
img.src = base64String;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Takes a local file path and returns a base64 representation of the file content.
|
|
71
|
+
*
|
|
72
|
+
* @param {string} filepath - the path of the file to be converted to base64
|
|
73
|
+
* @return {Promise<string>} a Promise that resolves to the base64 representation of the file content
|
|
74
|
+
*/
|
|
75
|
+
public static async fileToBase64(filepath: string): Promise<string> {
|
|
76
|
+
const contents = await fs.readFile(filepath);
|
|
77
|
+
let base64content = contents.toString("base64");
|
|
78
|
+
const ext = filepath.split(".").pop();
|
|
79
|
+
return `data:image/${ext};base64,` + base64content;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get the base64 representation of a remote file from the given URL.
|
|
84
|
+
*
|
|
85
|
+
* @param {string} url - the URL of the file
|
|
86
|
+
* @return {Promise<string>} the base64 representation of the file
|
|
87
|
+
*/
|
|
88
|
+
public static async getBase64FromFileUrl(url: string): Promise<string> {
|
|
89
|
+
const response = await axios.get(url, {
|
|
90
|
+
responseType: "arraybuffer",
|
|
91
|
+
maxRedirects: 5,
|
|
92
|
+
});
|
|
93
|
+
return Buffer.from(response.data, "binary").toString("base64");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Check if the base64 string represents a valid image and has a valid size
|
|
98
|
+
*
|
|
99
|
+
* @param {string} base64Image - the base64 image to be validated
|
|
100
|
+
* @return {Promise<string>} the valid base64 image
|
|
101
|
+
*/
|
|
102
|
+
public static isValidImage(base64Image: string): boolean {
|
|
103
|
+
// Check if the base64 string represents a valid image and has a valid size
|
|
104
|
+
try {
|
|
105
|
+
const isBase64Image = base64Image.startsWith("data:image/");
|
|
106
|
+
if (!isBase64Image) {
|
|
107
|
+
console.error("Not a valid base64 image");
|
|
108
|
+
throw new Error("Not a valid base64 image");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// TODO: use constants intead of magic numbers here
|
|
112
|
+
const isSizeLessThan2MB =
|
|
113
|
+
base64Image.length * 0.75 <= MAX_IMAGE_SIZE_MB * 1024 * 1024;
|
|
114
|
+
|
|
115
|
+
if (!isSizeLessThan2MB) {
|
|
116
|
+
console.error(`The image should be less than 2mb`);
|
|
117
|
+
throw new Error(`The image should be less than 2mb`);
|
|
118
|
+
}
|
|
119
|
+
// all is fine
|
|
120
|
+
return true;
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.error("Error checking base64 image:", error);
|
|
123
|
+
throw new Error(`Error checking base64 image`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
package/src/utils/errors.ts
CHANGED
|
@@ -1,92 +1,92 @@
|
|
|
1
|
-
import { AxiosError, AxiosResponse } from "axios";
|
|
2
|
-
|
|
3
|
-
const ErrorType = {
|
|
4
|
-
VALIDATION_ERROR: "VALIDATION_ERROR", // 400
|
|
5
|
-
UNAUTHORIZED_ERROR: "UNAUTHORIZED_ERROR", // 401 (Not valid credentials)
|
|
6
|
-
FORBIDDEN_ERROR: "FORBIDDEN_ERROR", // 403 (Valid credentials but insufficient to grant access)
|
|
7
|
-
NOT_FOUND: "NOT_FOUND", // 404
|
|
8
|
-
RATE_LIMIT_ERROR: "RATE_LIMIT_ERROR", // 429 (Used only in cloudflare for now)
|
|
9
|
-
OPERATION_ERROR: "OPERATION_ERROR", // 500
|
|
10
|
-
} as const;
|
|
11
|
-
|
|
12
|
-
const ErrorCode = {
|
|
13
|
-
VALIDATION_ERROR: "VALIDATION_ERROR",
|
|
14
|
-
UNAUTHORIZED_ERROR: "UNAUTHORIZED_ERROR",
|
|
15
|
-
FORBIDDEN_ERROR: "FORBIDDEN_ERROR",
|
|
16
|
-
NOT_FOUND: "NOT_FOUND",
|
|
17
|
-
RATE_LIMIT_ERROR: "RATE_LIMIT_ERROR",
|
|
18
|
-
OPERATION_ERROR: "OPERATION_ERROR",
|
|
19
|
-
SYNTAX_ERROR: "SYNTAX_ERROR",
|
|
20
|
-
INVALID_PARAM: "INVALID_PARAM",
|
|
21
|
-
UNHANDLED_ERROR: "SERVER_ERROR",
|
|
22
|
-
} as const;
|
|
23
|
-
|
|
24
|
-
export interface ErrorDetail {
|
|
25
|
-
param: string;
|
|
26
|
-
value: string | number | null;
|
|
27
|
-
msg: string;
|
|
28
|
-
code: string;
|
|
29
|
-
location: string | null;
|
|
30
|
-
meta: unknown;
|
|
31
|
-
}
|
|
32
|
-
export function handleExceptions(
|
|
33
|
-
error: AxiosError | Error
|
|
34
|
-
): TropipayJSException {
|
|
35
|
-
if (error instanceof AxiosError) {
|
|
36
|
-
if (error.response) {
|
|
37
|
-
const axiosResponse: AxiosResponse = error.response;
|
|
38
|
-
const errorMessage =
|
|
39
|
-
axiosResponse.data?.error?.message || "An error occurred";
|
|
40
|
-
|
|
41
|
-
switch (axiosResponse.status) {
|
|
42
|
-
case 400:
|
|
43
|
-
case 401:
|
|
44
|
-
case 403:
|
|
45
|
-
case 404:
|
|
46
|
-
return new TropipayJSException(
|
|
47
|
-
errorMessage,
|
|
48
|
-
axiosResponse.status,
|
|
49
|
-
axiosResponse.data.error
|
|
50
|
-
);
|
|
51
|
-
case 429:
|
|
52
|
-
return new TropipayJSException(
|
|
53
|
-
`${errorMessage} Too many request or rate limited`,
|
|
54
|
-
axiosResponse.status,
|
|
55
|
-
axiosResponse.data.error
|
|
56
|
-
);
|
|
57
|
-
default:
|
|
58
|
-
return new TropipayJSException(
|
|
59
|
-
errorMessage,
|
|
60
|
-
axiosResponse.status,
|
|
61
|
-
axiosResponse.data.error
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
} else if (error.request) {
|
|
65
|
-
// Axios request was made but no response received (e.g., network error)
|
|
66
|
-
return new TropipayJSException(
|
|
67
|
-
"Request failed: No response received",
|
|
68
|
-
500,
|
|
69
|
-
null
|
|
70
|
-
);
|
|
71
|
-
} else {
|
|
72
|
-
// Something else went wrong
|
|
73
|
-
return new TropipayJSException("An error occurred", 500, null);
|
|
74
|
-
}
|
|
75
|
-
} else {
|
|
76
|
-
return new TropipayJSException("An Unknown error occurred", 500, null);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export class TropipayJSException extends Error {
|
|
81
|
-
code: number; // Status code
|
|
82
|
-
error: unknown;
|
|
83
|
-
message: string;
|
|
84
|
-
constructor(message: string, code: number, data: unknown) {
|
|
85
|
-
super(message);
|
|
86
|
-
// Set the prototype explicitly.
|
|
87
|
-
Object.setPrototypeOf(this, TropipayJSException.prototype);
|
|
88
|
-
this.code = code;
|
|
89
|
-
this.error = data;
|
|
90
|
-
this.message = message;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
1
|
+
import { AxiosError, AxiosResponse } from "axios";
|
|
2
|
+
|
|
3
|
+
const ErrorType = {
|
|
4
|
+
VALIDATION_ERROR: "VALIDATION_ERROR", // 400
|
|
5
|
+
UNAUTHORIZED_ERROR: "UNAUTHORIZED_ERROR", // 401 (Not valid credentials)
|
|
6
|
+
FORBIDDEN_ERROR: "FORBIDDEN_ERROR", // 403 (Valid credentials but insufficient to grant access)
|
|
7
|
+
NOT_FOUND: "NOT_FOUND", // 404
|
|
8
|
+
RATE_LIMIT_ERROR: "RATE_LIMIT_ERROR", // 429 (Used only in cloudflare for now)
|
|
9
|
+
OPERATION_ERROR: "OPERATION_ERROR", // 500
|
|
10
|
+
} as const;
|
|
11
|
+
|
|
12
|
+
const ErrorCode = {
|
|
13
|
+
VALIDATION_ERROR: "VALIDATION_ERROR",
|
|
14
|
+
UNAUTHORIZED_ERROR: "UNAUTHORIZED_ERROR",
|
|
15
|
+
FORBIDDEN_ERROR: "FORBIDDEN_ERROR",
|
|
16
|
+
NOT_FOUND: "NOT_FOUND",
|
|
17
|
+
RATE_LIMIT_ERROR: "RATE_LIMIT_ERROR",
|
|
18
|
+
OPERATION_ERROR: "OPERATION_ERROR",
|
|
19
|
+
SYNTAX_ERROR: "SYNTAX_ERROR",
|
|
20
|
+
INVALID_PARAM: "INVALID_PARAM",
|
|
21
|
+
UNHANDLED_ERROR: "SERVER_ERROR",
|
|
22
|
+
} as const;
|
|
23
|
+
|
|
24
|
+
export interface ErrorDetail {
|
|
25
|
+
param: string;
|
|
26
|
+
value: string | number | null;
|
|
27
|
+
msg: string;
|
|
28
|
+
code: string;
|
|
29
|
+
location: string | null;
|
|
30
|
+
meta: unknown;
|
|
31
|
+
}
|
|
32
|
+
export function handleExceptions(
|
|
33
|
+
error: AxiosError | Error
|
|
34
|
+
): TropipayJSException {
|
|
35
|
+
if (error instanceof AxiosError) {
|
|
36
|
+
if (error.response) {
|
|
37
|
+
const axiosResponse: AxiosResponse = error.response;
|
|
38
|
+
const errorMessage =
|
|
39
|
+
axiosResponse.data?.error?.message || "An error occurred";
|
|
40
|
+
|
|
41
|
+
switch (axiosResponse.status) {
|
|
42
|
+
case 400:
|
|
43
|
+
case 401:
|
|
44
|
+
case 403:
|
|
45
|
+
case 404:
|
|
46
|
+
return new TropipayJSException(
|
|
47
|
+
errorMessage,
|
|
48
|
+
axiosResponse.status,
|
|
49
|
+
axiosResponse.data.error
|
|
50
|
+
);
|
|
51
|
+
case 429:
|
|
52
|
+
return new TropipayJSException(
|
|
53
|
+
`${errorMessage} Too many request or rate limited`,
|
|
54
|
+
axiosResponse.status,
|
|
55
|
+
axiosResponse.data.error
|
|
56
|
+
);
|
|
57
|
+
default:
|
|
58
|
+
return new TropipayJSException(
|
|
59
|
+
errorMessage,
|
|
60
|
+
axiosResponse.status,
|
|
61
|
+
axiosResponse.data.error
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
} else if (error.request) {
|
|
65
|
+
// Axios request was made but no response received (e.g., network error)
|
|
66
|
+
return new TropipayJSException(
|
|
67
|
+
"Request failed: No response received",
|
|
68
|
+
500,
|
|
69
|
+
null
|
|
70
|
+
);
|
|
71
|
+
} else {
|
|
72
|
+
// Something else went wrong
|
|
73
|
+
return new TropipayJSException("An error occurred", 500, null);
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
return new TropipayJSException("An Unknown error occurred", 500, null);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export class TropipayJSException extends Error {
|
|
81
|
+
code: number; // Status code
|
|
82
|
+
error: unknown;
|
|
83
|
+
message: string;
|
|
84
|
+
constructor(message: string, code: number, data: unknown) {
|
|
85
|
+
super(message);
|
|
86
|
+
// Set the prototype explicitly.
|
|
87
|
+
Object.setPrototypeOf(this, TropipayJSException.prototype);
|
|
88
|
+
this.code = code;
|
|
89
|
+
this.error = data;
|
|
90
|
+
this.message = message;
|
|
91
|
+
}
|
|
92
|
+
}
|