@yosle/tropipayjs 0.1.19 → 0.1.21
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/README.md +2 -2
- package/api/TropipayAPI.d.ts +9 -9
- package/constants/TropipayConstants.d.ts +1 -0
- package/hooks/TropipayHooks.d.ts +1 -1
- package/index.d.ts +41 -10
- package/index.js +129 -31
- package/interfaces/index.d.ts +2 -0
- package/package.json +6 -3
- package/paymentcard/paymentcard.spec.d.ts +1 -0
- package/rollup.config.js +1 -1
- package/src/api/TropipayAPI.ts +58 -28
- package/src/constants/TropipayConstants.ts +1 -0
- package/src/hooks/TropipayHooks.ts +1 -1
- package/src/index.ts +3 -7
- package/src/interfaces/index.ts +2 -0
- package/src/paymentcard/paymentcard.spec.ts +10 -0
- package/src/utils/TropipayUtils.ts +86 -1
- package/src/utils/errors.ts +8 -4
- package/utils/TropipayUtils.d.ts +28 -0
- package/utils/errors.d.ts +1 -2
package/.nvmrc
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
v20.10.0
|
package/README.md
CHANGED
|
@@ -47,8 +47,8 @@ or
|
|
|
47
47
|
|
|
48
48
|
## Setting up your app credentials
|
|
49
49
|
|
|
50
|
-

|
|
51
|
+

|
|
52
52
|
|
|
53
53
|
In order to use the Tropipay API you'll need a client ID and a client secret. Sign up with your [Tropipay](www.tropipay.com) account, go to the App Menu->Applications and credentials. You'll be asked to enter a confirmation code (use 123456 in the test enviroment). _Make sure you test everything in the [test environment](https://tropipay-dev.herokuapp.com) first before using your real account._ Create an app and configure the client id an client secret for the app.
|
|
54
54
|
|
package/api/TropipayAPI.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
*/
|
|
7
7
|
import { Axios } from "axios";
|
|
8
|
-
import { TropipayConfig, AccountBalance, Country, PaymentLink, mediationPaymentCardConfig, LoginResponse
|
|
8
|
+
import { TropipayConfig, AccountBalance, Country, PaymentLink, mediationPaymentCardConfig, LoginResponse } from "../interfaces";
|
|
9
9
|
type ServerMode = "Development" | "Production";
|
|
10
10
|
import { TropipayHooks } from "../hooks/TropipayHooks";
|
|
11
11
|
import { PaymentCard } from "../paymentcard/PaymentCard";
|
|
@@ -13,21 +13,21 @@ import { DepositAccounts } from "../depositAccount/depositAccounts";
|
|
|
13
13
|
export declare class Tropipay {
|
|
14
14
|
readonly clientId: string;
|
|
15
15
|
readonly clientSecret: string;
|
|
16
|
+
readonly scopes: String[];
|
|
16
17
|
request: Axios;
|
|
17
|
-
static accessToken: string |
|
|
18
|
-
static refreshToken: string |
|
|
18
|
+
static accessToken: string | null;
|
|
19
|
+
static refreshToken: string | null;
|
|
19
20
|
serverMode: ServerMode;
|
|
20
21
|
hooks: TropipayHooks;
|
|
21
22
|
paymentCards: PaymentCard;
|
|
22
23
|
depositAccounts: DepositAccounts;
|
|
23
|
-
constructor(config: TropipayConfig);
|
|
24
|
-
login(): Promise<LoginResponse>;
|
|
25
24
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* @
|
|
25
|
+
* Initializes a new instance of the Tropipay class.
|
|
26
|
+
*
|
|
27
|
+
* @param {TropipayConfig} config - The configuration object.
|
|
29
28
|
*/
|
|
30
|
-
|
|
29
|
+
constructor(config: TropipayConfig);
|
|
30
|
+
login(): Promise<LoginResponse>;
|
|
31
31
|
/**
|
|
32
32
|
* Get the list of all supported countries by Tropipay.
|
|
33
33
|
* @returns Array of Countries Data
|
package/hooks/TropipayHooks.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export declare class TropipayHooks {
|
|
|
25
25
|
value: string;
|
|
26
26
|
}): Promise<any>;
|
|
27
27
|
/**
|
|
28
|
-
* Get the
|
|
28
|
+
* Get the subscribed hook info by his event type.
|
|
29
29
|
* If no event type is passed, it will return
|
|
30
30
|
* all subscribed hooks or empty Array if none exist.
|
|
31
31
|
* @param eventType or no params for retrieving all hooks
|
package/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Axios } from 'axios';
|
|
2
2
|
|
|
3
3
|
type ServerMode$1 = "Development" | "Production";
|
|
4
|
+
declare const MAX_IMAGE_SIZE_MB = 1;
|
|
4
5
|
|
|
5
6
|
type AccountBalance = {
|
|
6
7
|
balance: number;
|
|
@@ -10,6 +11,7 @@ type AccountBalance = {
|
|
|
10
11
|
interface TropipayConfig {
|
|
11
12
|
clientId: string;
|
|
12
13
|
clientSecret: string;
|
|
14
|
+
scopes?: string[];
|
|
13
15
|
serverMode?: ServerMode$1;
|
|
14
16
|
}
|
|
15
17
|
type TropipayCredentials = {
|
|
@@ -62,6 +64,7 @@ interface PaymentLinkPayload {
|
|
|
62
64
|
};
|
|
63
65
|
directPayment: boolean;
|
|
64
66
|
paymentMethods?: string[];
|
|
67
|
+
imageBase?: string;
|
|
65
68
|
}
|
|
66
69
|
interface PaymentLink extends PaymentLinkPayload {
|
|
67
70
|
expirationDate: string;
|
|
@@ -186,7 +189,7 @@ declare class TropipayHooks {
|
|
|
186
189
|
value: string;
|
|
187
190
|
}): Promise<any>;
|
|
188
191
|
/**
|
|
189
|
-
* Get the
|
|
192
|
+
* Get the subscribed hook info by his event type.
|
|
190
193
|
* If no event type is passed, it will return
|
|
191
194
|
* all subscribed hooks or empty Array if none exist.
|
|
192
195
|
* @param eventType or no params for retrieving all hooks
|
|
@@ -292,21 +295,21 @@ type ServerMode = "Development" | "Production";
|
|
|
292
295
|
declare class Tropipay {
|
|
293
296
|
readonly clientId: string;
|
|
294
297
|
readonly clientSecret: string;
|
|
298
|
+
readonly scopes: String[];
|
|
295
299
|
request: Axios;
|
|
296
|
-
static accessToken: string |
|
|
297
|
-
static refreshToken: string |
|
|
300
|
+
static accessToken: string | null;
|
|
301
|
+
static refreshToken: string | null;
|
|
298
302
|
serverMode: ServerMode;
|
|
299
303
|
hooks: TropipayHooks;
|
|
300
304
|
paymentCards: PaymentCard;
|
|
301
305
|
depositAccounts: DepositAccounts;
|
|
302
|
-
constructor(config: TropipayConfig);
|
|
303
|
-
login(): Promise<LoginResponse>;
|
|
304
306
|
/**
|
|
305
|
-
*
|
|
306
|
-
*
|
|
307
|
-
* @
|
|
307
|
+
* Initializes a new instance of the Tropipay class.
|
|
308
|
+
*
|
|
309
|
+
* @param {TropipayConfig} config - The configuration object.
|
|
308
310
|
*/
|
|
309
|
-
|
|
311
|
+
constructor(config: TropipayConfig);
|
|
312
|
+
login(): Promise<LoginResponse>;
|
|
310
313
|
/**
|
|
311
314
|
* Get the list of all supported countries by Tropipay.
|
|
312
315
|
* @returns Array of Countries Data
|
|
@@ -382,8 +385,36 @@ declare class ServerSideUtils {
|
|
|
382
385
|
clientId: string;
|
|
383
386
|
clientSecret: string;
|
|
384
387
|
} | Tropipay, originalCurrencyAmount: string, bankOrderCode: string, signature: string): boolean;
|
|
388
|
+
/**
|
|
389
|
+
* Checks if the provided base64 string represents a square image.
|
|
390
|
+
*
|
|
391
|
+
* @param {string} base64String - The base64 string of the image
|
|
392
|
+
* @return {Promise<boolean>} A Promise that resolves to a boolean indicating whether the image is square
|
|
393
|
+
*/
|
|
394
|
+
static isBase64ImageSquare(base64String: string): Promise<boolean>;
|
|
395
|
+
/**
|
|
396
|
+
* Takes a local file path and returns a base64 representation of the file content.
|
|
397
|
+
*
|
|
398
|
+
* @param {string} filepath - the path of the file to be converted to base64
|
|
399
|
+
* @return {Promise<string>} a Promise that resolves to the base64 representation of the file content
|
|
400
|
+
*/
|
|
401
|
+
static fileToBase64(filepath: string): Promise<string>;
|
|
402
|
+
/**
|
|
403
|
+
* Get the base64 representation of a remote file from the given URL.
|
|
404
|
+
*
|
|
405
|
+
* @param {string} url - the URL of the file
|
|
406
|
+
* @return {Promise<string>} the base64 representation of the file
|
|
407
|
+
*/
|
|
408
|
+
static getBase64FromFileUrl(url: string): Promise<string>;
|
|
409
|
+
/**
|
|
410
|
+
* Check if the base64 string represents a valid image and has a valid size
|
|
411
|
+
*
|
|
412
|
+
* @param {string} base64Image - the base64 image to be validated
|
|
413
|
+
* @return {Promise<string>} the valid base64 image
|
|
414
|
+
*/
|
|
415
|
+
static isValidImage(base64Image: string): boolean;
|
|
385
416
|
}
|
|
386
417
|
|
|
387
418
|
declare const SERVER_MODE: ServerMode$1;
|
|
388
419
|
|
|
389
|
-
export { AccountBalance, AccountDeposits, ClientSideUtils, Country, Deposit, DepositAccounts, HookEventType, HookTargetType, LoginError, LoginResponse, PaymentCard, PaymentLink, PaymentLinkPayload, SERVER_MODE, ServerMode$1 as ServerMode, ServerSideUtils, Tropipay, TropipayConfig, TropipayCredentials, TropipayHooks, UserHook, UserHookSubscribed, mediationPaymentCardConfig };
|
|
420
|
+
export { AccountBalance, AccountDeposits, ClientSideUtils, Country, Deposit, DepositAccounts, HookEventType, HookTargetType, LoginError, LoginResponse, MAX_IMAGE_SIZE_MB, PaymentCard, PaymentLink, PaymentLinkPayload, SERVER_MODE, ServerMode$1 as ServerMode, ServerSideUtils, Tropipay, TropipayConfig, TropipayCredentials, TropipayHooks, UserHook, UserHookSubscribed, mediationPaymentCardConfig };
|
package/index.js
CHANGED
|
@@ -4,6 +4,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
4
4
|
|
|
5
5
|
var axios = require('axios');
|
|
6
6
|
var crypto = require('crypto');
|
|
7
|
+
var fs = require('fs/promises');
|
|
7
8
|
|
|
8
9
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
9
10
|
|
|
@@ -27,6 +28,7 @@ function _interopNamespace(e) {
|
|
|
27
28
|
|
|
28
29
|
var axios__default = /*#__PURE__*/_interopDefaultLegacy(axios);
|
|
29
30
|
var crypto__namespace = /*#__PURE__*/_interopNamespace(crypto);
|
|
31
|
+
var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
|
|
30
32
|
|
|
31
33
|
function handleExceptions(error) {
|
|
32
34
|
if (error instanceof axios.AxiosError) {
|
|
@@ -40,8 +42,8 @@ function handleExceptions(error) {
|
|
|
40
42
|
return new TropipayJSException(errorMessage, axiosResponse.status, axiosResponse.data.error);
|
|
41
43
|
case 404:
|
|
42
44
|
return new TropipayJSException(errorMessage, axiosResponse.status, axiosResponse.data.error);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
case 429:
|
|
46
|
+
return new TropipayJSException(`${errorMessage} Too many request or rate limited`, axiosResponse.status, axiosResponse.data.error);
|
|
45
47
|
default:
|
|
46
48
|
return new TropipayJSException(errorMessage, axiosResponse.status, axiosResponse.data.error);
|
|
47
49
|
}
|
|
@@ -56,7 +58,7 @@ function handleExceptions(error) {
|
|
|
56
58
|
}
|
|
57
59
|
}
|
|
58
60
|
else {
|
|
59
|
-
return new TropipayJSException(
|
|
61
|
+
return new TropipayJSException("An Unknown error occurred", 500, null);
|
|
60
62
|
}
|
|
61
63
|
}
|
|
62
64
|
class TropipayJSException extends Error {
|
|
@@ -118,7 +120,7 @@ class TropipayHooks {
|
|
|
118
120
|
}
|
|
119
121
|
}
|
|
120
122
|
/**
|
|
121
|
-
* Get the
|
|
123
|
+
* Get the subscribed hook info by his event type.
|
|
122
124
|
* If no event type is passed, it will return
|
|
123
125
|
* all subscribed hooks or empty Array if none exist.
|
|
124
126
|
* @param eventType or no params for retrieving all hooks
|
|
@@ -406,6 +408,7 @@ class DepositAccounts {
|
|
|
406
408
|
class Tropipay {
|
|
407
409
|
clientId;
|
|
408
410
|
clientSecret;
|
|
411
|
+
scopes;
|
|
409
412
|
request;
|
|
410
413
|
static accessToken;
|
|
411
414
|
static refreshToken;
|
|
@@ -413,7 +416,32 @@ class Tropipay {
|
|
|
413
416
|
hooks;
|
|
414
417
|
paymentCards;
|
|
415
418
|
depositAccounts;
|
|
419
|
+
/**
|
|
420
|
+
* Initializes a new instance of the Tropipay class.
|
|
421
|
+
*
|
|
422
|
+
* @param {TropipayConfig} config - The configuration object.
|
|
423
|
+
*/
|
|
416
424
|
constructor(config) {
|
|
425
|
+
// use all scopes if none is passed
|
|
426
|
+
if (!config?.scopes) {
|
|
427
|
+
this.scopes = [
|
|
428
|
+
"ALLOW_GET_PROFILE_DATA",
|
|
429
|
+
"ALLOW_PAYMENT_IN",
|
|
430
|
+
"ALLOW_EXTERNAL_CHARGE",
|
|
431
|
+
"KYC3_FULL_ALLOW",
|
|
432
|
+
"ALLOW_PAYMENT_OUT",
|
|
433
|
+
"ALLOW_MARKET_PURCHASES",
|
|
434
|
+
"ALLOW_GET_BALANCE",
|
|
435
|
+
"ALLOW_GET_MOVEMENT_LIST",
|
|
436
|
+
"ALLOW_GET_CREDENTIAL",
|
|
437
|
+
];
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
this.scopes = config.scopes;
|
|
441
|
+
}
|
|
442
|
+
if (!config.clientId || !config.clientSecret) {
|
|
443
|
+
throw new TropipayJSException(`You must pass clientId and clientSecret in Tropipay constructor`, 400, null);
|
|
444
|
+
}
|
|
417
445
|
this.clientId = config.clientId;
|
|
418
446
|
this.clientSecret = config.clientSecret;
|
|
419
447
|
this.serverMode = config.serverMode || "Development";
|
|
@@ -433,11 +461,26 @@ class Tropipay {
|
|
|
433
461
|
}
|
|
434
462
|
async login() {
|
|
435
463
|
try {
|
|
464
|
+
if (Tropipay.refreshToken) {
|
|
465
|
+
const { data } = await this.request.post("/api/v2/access/token", {
|
|
466
|
+
client_id: this.clientId,
|
|
467
|
+
client_secret: this.clientSecret,
|
|
468
|
+
grant_type: "refresh_token",
|
|
469
|
+
refresh_token: Tropipay.refreshToken,
|
|
470
|
+
}, {
|
|
471
|
+
headers: {
|
|
472
|
+
"Content-Type": "application/json",
|
|
473
|
+
Accept: "application/json",
|
|
474
|
+
},
|
|
475
|
+
});
|
|
476
|
+
return data;
|
|
477
|
+
}
|
|
478
|
+
// normal credetials login
|
|
436
479
|
const { data } = await this.request.post("/api/v2/access/token", {
|
|
437
480
|
client_id: this.clientId,
|
|
438
481
|
client_secret: this.clientSecret,
|
|
439
482
|
grant_type: "client_credentials",
|
|
440
|
-
scope: "ALLOW_GET_PROFILE_DATA ALLOW_PAYMENT_IN ALLOW_EXTERNAL_CHARGE KYC3_FULL_ALLOW ALLOW_PAYMENT_OUT ALLOW_MARKET_PURCHASES ALLOW_GET_BALANCE ALLOW_GET_MOVEMENT_LIST ALLOW_GET_CREDENTIAL",
|
|
483
|
+
scope: this.scopes.join(" "), // "ALLOW_GET_PROFILE_DATA ALLOW_PAYMENT_IN ALLOW_EXTERNAL_CHARGE KYC3_FULL_ALLOW ALLOW_PAYMENT_OUT ALLOW_MARKET_PURCHASES ALLOW_GET_BALANCE ALLOW_GET_MOVEMENT_LIST ALLOW_GET_CREDENTIAL",
|
|
441
484
|
}, {
|
|
442
485
|
headers: {
|
|
443
486
|
"Content-Type": "application/json",
|
|
@@ -449,29 +492,8 @@ class Tropipay {
|
|
|
449
492
|
return data;
|
|
450
493
|
}
|
|
451
494
|
catch (error) {
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
}
|
|
455
|
-
/**
|
|
456
|
-
* Get all deposits in this account.
|
|
457
|
-
* @returns A Promise of an Array of AccountDeposits or throws an Exception
|
|
458
|
-
* @see https://tpp.stoplight.io/docs/tropipay-api-doc/b3A6OTgyOTQ1Mg-get-deposit-accounts-list
|
|
459
|
-
*/
|
|
460
|
-
async getDepositAccounts() {
|
|
461
|
-
if (!Tropipay.accessToken) {
|
|
462
|
-
await this.login();
|
|
463
|
-
}
|
|
464
|
-
try {
|
|
465
|
-
const deposits = await this.request.get("/api/v2/deposit_accounts", {
|
|
466
|
-
headers: {
|
|
467
|
-
"Content-Type": "application/json",
|
|
468
|
-
Authorization: `Bearer ${Tropipay.accessToken}`,
|
|
469
|
-
Accept: "application/json",
|
|
470
|
-
},
|
|
471
|
-
});
|
|
472
|
-
return deposits.data;
|
|
473
|
-
}
|
|
474
|
-
catch (error) {
|
|
495
|
+
Tropipay.accessToken = null;
|
|
496
|
+
Tropipay.refreshToken = null;
|
|
475
497
|
throw handleExceptions(error);
|
|
476
498
|
}
|
|
477
499
|
}
|
|
@@ -650,6 +672,8 @@ class ClientSideUtils {
|
|
|
650
672
|
}
|
|
651
673
|
}
|
|
652
674
|
|
|
675
|
+
const MAX_IMAGE_SIZE_MB = 1;
|
|
676
|
+
|
|
653
677
|
class ServerSideUtils {
|
|
654
678
|
tropipay;
|
|
655
679
|
constructor(tropipayInstance) {
|
|
@@ -673,6 +697,79 @@ class ServerSideUtils {
|
|
|
673
697
|
.digest("hex");
|
|
674
698
|
return localSignature === signature;
|
|
675
699
|
}
|
|
700
|
+
/**
|
|
701
|
+
* Checks if the provided base64 string represents a square image.
|
|
702
|
+
*
|
|
703
|
+
* @param {string} base64String - The base64 string of the image
|
|
704
|
+
* @return {Promise<boolean>} A Promise that resolves to a boolean indicating whether the image is square
|
|
705
|
+
*/
|
|
706
|
+
static async isBase64ImageSquare(base64String) {
|
|
707
|
+
return new Promise((resolve, reject) => {
|
|
708
|
+
const img = new Image();
|
|
709
|
+
img.onload = () => {
|
|
710
|
+
// Check if the image has a 1:1 aspect ratio
|
|
711
|
+
const isSquare = img.width === img.height;
|
|
712
|
+
resolve(isSquare);
|
|
713
|
+
};
|
|
714
|
+
img.onerror = (error) => {
|
|
715
|
+
reject(error);
|
|
716
|
+
};
|
|
717
|
+
img.src = base64String;
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Takes a local file path and returns a base64 representation of the file content.
|
|
722
|
+
*
|
|
723
|
+
* @param {string} filepath - the path of the file to be converted to base64
|
|
724
|
+
* @return {Promise<string>} a Promise that resolves to the base64 representation of the file content
|
|
725
|
+
*/
|
|
726
|
+
static async fileToBase64(filepath) {
|
|
727
|
+
const contents = await fs__default["default"].readFile(filepath);
|
|
728
|
+
let base64content = contents.toString("base64");
|
|
729
|
+
const ext = filepath.split(".").pop();
|
|
730
|
+
return `data:image/${ext};base64,` + base64content;
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* Get the base64 representation of a remote file from the given URL.
|
|
734
|
+
*
|
|
735
|
+
* @param {string} url - the URL of the file
|
|
736
|
+
* @return {Promise<string>} the base64 representation of the file
|
|
737
|
+
*/
|
|
738
|
+
static async getBase64FromFileUrl(url) {
|
|
739
|
+
const response = await axios__default["default"].get(url, {
|
|
740
|
+
responseType: "arraybuffer",
|
|
741
|
+
maxRedirects: 5,
|
|
742
|
+
});
|
|
743
|
+
return Buffer.from(response.data, "binary").toString("base64");
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Check if the base64 string represents a valid image and has a valid size
|
|
747
|
+
*
|
|
748
|
+
* @param {string} base64Image - the base64 image to be validated
|
|
749
|
+
* @return {Promise<string>} the valid base64 image
|
|
750
|
+
*/
|
|
751
|
+
static isValidImage(base64Image) {
|
|
752
|
+
// Check if the base64 string represents a valid image and has a valid size
|
|
753
|
+
try {
|
|
754
|
+
const isBase64Image = base64Image.startsWith("data:image/");
|
|
755
|
+
if (!isBase64Image) {
|
|
756
|
+
console.error("Not a valid base64 image");
|
|
757
|
+
throw new Error("Not a valid base64 image");
|
|
758
|
+
}
|
|
759
|
+
// TODO: use constants intead of magic numbers here
|
|
760
|
+
const isSizeLessThan2MB = base64Image.length * 0.75 <= MAX_IMAGE_SIZE_MB * 1024 * 1024;
|
|
761
|
+
if (!isSizeLessThan2MB) {
|
|
762
|
+
console.error(`The image should be less than 2mb`);
|
|
763
|
+
throw new Error(`The image should be less than 2mb`);
|
|
764
|
+
}
|
|
765
|
+
// all is fine
|
|
766
|
+
return true;
|
|
767
|
+
}
|
|
768
|
+
catch (error) {
|
|
769
|
+
console.error("Error checking base64 image:", error);
|
|
770
|
+
throw new Error(`Error checking base64 image`);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
676
773
|
}
|
|
677
774
|
|
|
678
775
|
const SERVER_MODE = "Development"; // Move the constant here
|
|
@@ -689,13 +786,14 @@ const SERVER_MODE = "Development"; // Move the constant here
|
|
|
689
786
|
* @license MIT
|
|
690
787
|
*/
|
|
691
788
|
if (typeof window !== "undefined") {
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
789
|
+
const err_msg = `⚠️ **Warning:** The Tropipay SDK should only be instantiated server-side for security reasons. Using it client-side may lead to unexpected behavior and security vulnerabilities. YOUR CREDENTIALS COULD BE EXPOSED. Check https://yosle.github.io/tropipayjs-docs/guides/installation/ for more details.`;
|
|
790
|
+
console.error(err_msg);
|
|
791
|
+
throw new Error(err_msg);
|
|
695
792
|
}
|
|
696
793
|
|
|
697
794
|
exports.ClientSideUtils = ClientSideUtils;
|
|
698
795
|
exports.DepositAccounts = DepositAccounts;
|
|
796
|
+
exports.MAX_IMAGE_SIZE_MB = MAX_IMAGE_SIZE_MB;
|
|
699
797
|
exports.PaymentCard = PaymentCard;
|
|
700
798
|
exports.SERVER_MODE = SERVER_MODE;
|
|
701
799
|
exports.ServerSideUtils = ServerSideUtils;
|
package/interfaces/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export type AccountBalance = {
|
|
|
7
7
|
export interface TropipayConfig {
|
|
8
8
|
clientId: string;
|
|
9
9
|
clientSecret: string;
|
|
10
|
+
scopes?: string[];
|
|
10
11
|
serverMode?: ServerMode;
|
|
11
12
|
}
|
|
12
13
|
export type TropipayCredentials = {
|
|
@@ -59,6 +60,7 @@ export interface PaymentLinkPayload {
|
|
|
59
60
|
};
|
|
60
61
|
directPayment: boolean;
|
|
61
62
|
paymentMethods?: string[];
|
|
63
|
+
imageBase?: string;
|
|
62
64
|
}
|
|
63
65
|
export interface PaymentLink extends PaymentLinkPayload {
|
|
64
66
|
expirationDate: string;
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yosle/tropipayjs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.21",
|
|
4
4
|
"description": "Javascript / Typescript SDK for the Tropipay API",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"test": "
|
|
7
|
+
"test": "vitest run",
|
|
8
8
|
"_clear": "rimraf src/dist/*",
|
|
9
9
|
"_tsc": "tsc",
|
|
10
10
|
"_make-bundle": "rollup --config",
|
|
@@ -30,11 +30,14 @@
|
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@rollup/plugin-typescript": "^8.5.0",
|
|
33
|
+
"@types/tough-cookie": "^4.0.5",
|
|
33
34
|
"rimraf": "^3.0.2",
|
|
34
35
|
"rollup": "^2.79.1",
|
|
35
36
|
"rollup-plugin-dts": "^4.2.2",
|
|
36
37
|
"run-s": "^0.0.0",
|
|
37
|
-
"typescript": "^5.0.4"
|
|
38
|
+
"typescript": "^5.0.4",
|
|
39
|
+
"vitest": "^1.2.2",
|
|
40
|
+
"@types/node": "^20.12.7"
|
|
38
41
|
},
|
|
39
42
|
"funding": {
|
|
40
43
|
"type": "individual",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/rollup.config.js
CHANGED
package/src/api/TropipayAPI.ts
CHANGED
|
@@ -20,19 +20,51 @@ type ServerMode = "Development" | "Production";
|
|
|
20
20
|
import { TropipayHooks } from "../hooks/TropipayHooks";
|
|
21
21
|
import { PaymentCard } from "../paymentcard/PaymentCard";
|
|
22
22
|
import { DepositAccounts } from "../depositAccount/depositAccounts";
|
|
23
|
-
import { handleExceptions } from "../utils/errors";
|
|
23
|
+
import { TropipayJSException, handleExceptions } from "../utils/errors";
|
|
24
24
|
export class Tropipay {
|
|
25
25
|
readonly clientId: string;
|
|
26
26
|
readonly clientSecret: string;
|
|
27
|
+
readonly scopes: String[];
|
|
27
28
|
public request: Axios;
|
|
28
|
-
public static accessToken: string |
|
|
29
|
-
public static refreshToken: string |
|
|
29
|
+
public static accessToken: string | null;
|
|
30
|
+
public static refreshToken: string | null;
|
|
30
31
|
public serverMode: ServerMode;
|
|
31
32
|
public hooks: TropipayHooks;
|
|
32
33
|
public paymentCards: PaymentCard;
|
|
33
34
|
public depositAccounts: DepositAccounts;
|
|
34
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Initializes a new instance of the Tropipay class.
|
|
38
|
+
*
|
|
39
|
+
* @param {TropipayConfig} config - The configuration object.
|
|
40
|
+
*/
|
|
41
|
+
|
|
35
42
|
constructor(config: TropipayConfig) {
|
|
43
|
+
// use all scopes if none is passed
|
|
44
|
+
if (!config?.scopes) {
|
|
45
|
+
this.scopes = [
|
|
46
|
+
"ALLOW_GET_PROFILE_DATA",
|
|
47
|
+
"ALLOW_PAYMENT_IN",
|
|
48
|
+
"ALLOW_EXTERNAL_CHARGE",
|
|
49
|
+
"KYC3_FULL_ALLOW",
|
|
50
|
+
"ALLOW_PAYMENT_OUT",
|
|
51
|
+
"ALLOW_MARKET_PURCHASES",
|
|
52
|
+
"ALLOW_GET_BALANCE",
|
|
53
|
+
"ALLOW_GET_MOVEMENT_LIST",
|
|
54
|
+
"ALLOW_GET_CREDENTIAL",
|
|
55
|
+
];
|
|
56
|
+
} else {
|
|
57
|
+
this.scopes = config.scopes;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!config.clientId || !config.clientSecret) {
|
|
61
|
+
throw new TropipayJSException(
|
|
62
|
+
`You must pass clientId and clientSecret in Tropipay constructor`,
|
|
63
|
+
400,
|
|
64
|
+
null
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
36
68
|
this.clientId = config.clientId;
|
|
37
69
|
this.clientSecret = config.clientSecret;
|
|
38
70
|
this.serverMode = config.serverMode || "Development";
|
|
@@ -54,14 +86,33 @@ export class Tropipay {
|
|
|
54
86
|
|
|
55
87
|
public async login() {
|
|
56
88
|
try {
|
|
89
|
+
if (Tropipay.refreshToken) {
|
|
90
|
+
const { data } = await this.request.post<LoginResponse>(
|
|
91
|
+
"/api/v2/access/token",
|
|
92
|
+
{
|
|
93
|
+
client_id: this.clientId,
|
|
94
|
+
client_secret: this.clientSecret,
|
|
95
|
+
grant_type: "refresh_token",
|
|
96
|
+
refresh_token: Tropipay.refreshToken,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
headers: {
|
|
100
|
+
"Content-Type": "application/json",
|
|
101
|
+
Accept: "application/json",
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
);
|
|
105
|
+
return data;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// normal credetials login
|
|
57
109
|
const { data } = await this.request.post<LoginResponse>(
|
|
58
110
|
"/api/v2/access/token",
|
|
59
111
|
{
|
|
60
112
|
client_id: this.clientId,
|
|
61
113
|
client_secret: this.clientSecret,
|
|
62
114
|
grant_type: "client_credentials",
|
|
63
|
-
scope:
|
|
64
|
-
"ALLOW_GET_PROFILE_DATA ALLOW_PAYMENT_IN ALLOW_EXTERNAL_CHARGE KYC3_FULL_ALLOW ALLOW_PAYMENT_OUT ALLOW_MARKET_PURCHASES ALLOW_GET_BALANCE ALLOW_GET_MOVEMENT_LIST ALLOW_GET_CREDENTIAL",
|
|
115
|
+
scope: this.scopes.join(" "), // "ALLOW_GET_PROFILE_DATA ALLOW_PAYMENT_IN ALLOW_EXTERNAL_CHARGE KYC3_FULL_ALLOW ALLOW_PAYMENT_OUT ALLOW_MARKET_PURCHASES ALLOW_GET_BALANCE ALLOW_GET_MOVEMENT_LIST ALLOW_GET_CREDENTIAL",
|
|
65
116
|
},
|
|
66
117
|
{
|
|
67
118
|
headers: {
|
|
@@ -75,29 +126,8 @@ export class Tropipay {
|
|
|
75
126
|
Tropipay.refreshToken = data.refresh_token;
|
|
76
127
|
return data;
|
|
77
128
|
} catch (error) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Get all deposits in this account.
|
|
84
|
-
* @returns A Promise of an Array of AccountDeposits or throws an Exception
|
|
85
|
-
* @see https://tpp.stoplight.io/docs/tropipay-api-doc/b3A6OTgyOTQ1Mg-get-deposit-accounts-list
|
|
86
|
-
*/
|
|
87
|
-
async getDepositAccounts(): Promise<AccountDeposits[] | Error> {
|
|
88
|
-
if (!Tropipay.accessToken) {
|
|
89
|
-
await this.login();
|
|
90
|
-
}
|
|
91
|
-
try {
|
|
92
|
-
const deposits = await this.request.get("/api/v2/deposit_accounts", {
|
|
93
|
-
headers: {
|
|
94
|
-
"Content-Type": "application/json",
|
|
95
|
-
Authorization: `Bearer ${Tropipay.accessToken}`,
|
|
96
|
-
Accept: "application/json",
|
|
97
|
-
},
|
|
98
|
-
});
|
|
99
|
-
return deposits.data;
|
|
100
|
-
} catch (error) {
|
|
129
|
+
Tropipay.accessToken = null;
|
|
130
|
+
Tropipay.refreshToken = null;
|
|
101
131
|
throw handleExceptions(error as any);
|
|
102
132
|
}
|
|
103
133
|
}
|
|
@@ -57,7 +57,7 @@ export class TropipayHooks {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
/**
|
|
60
|
-
* Get the
|
|
60
|
+
* Get the subscribed hook info by his event type.
|
|
61
61
|
* If no event type is passed, it will return
|
|
62
62
|
* all subscribed hooks or empty Array if none exist.
|
|
63
63
|
* @param eventType or no params for retrieving all hooks
|
package/src/index.ts
CHANGED
|
@@ -10,14 +10,10 @@
|
|
|
10
10
|
* @license MIT
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { DepositAccounts } from "./depositAccount/depositAccounts";
|
|
14
|
-
|
|
15
13
|
if (typeof window !== "undefined") {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
https://yosle.github.io/tropipayjs-docs/guides/installation/ for more details.`
|
|
20
|
-
);
|
|
14
|
+
const err_msg = `⚠️ **Warning:** The Tropipay SDK should only be instantiated server-side for security reasons. Using it client-side may lead to unexpected behavior and security vulnerabilities. YOUR CREDENTIALS COULD BE EXPOSED. Check https://yosle.github.io/tropipayjs-docs/guides/installation/ for more details.`;
|
|
15
|
+
console.error(err_msg);
|
|
16
|
+
throw new Error(err_msg);
|
|
21
17
|
}
|
|
22
18
|
|
|
23
19
|
export { Tropipay } from "./api/TropipayAPI";
|
package/src/interfaces/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ export type AccountBalance = {
|
|
|
9
9
|
export interface TropipayConfig {
|
|
10
10
|
clientId: string;
|
|
11
11
|
clientSecret: string;
|
|
12
|
+
scopes?: string[];
|
|
12
13
|
serverMode?: ServerMode;
|
|
13
14
|
}
|
|
14
15
|
|
|
@@ -86,6 +87,7 @@ export interface PaymentLinkPayload {
|
|
|
86
87
|
};
|
|
87
88
|
directPayment: boolean;
|
|
88
89
|
paymentMethods?: string[];
|
|
90
|
+
imageBase?: string;
|
|
89
91
|
}
|
|
90
92
|
|
|
91
93
|
export interface PaymentLink extends PaymentLinkPayload {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import * as crypto from "crypto";
|
|
2
2
|
import { Tropipay } from "../api/TropipayAPI";
|
|
3
|
-
import { AxiosError, AxiosResponse } from "axios";
|
|
4
3
|
|
|
4
|
+
import axios from "axios";
|
|
5
|
+
import fs from "fs/promises";
|
|
6
|
+
import { MAX_IMAGE_SIZE_MB } from "../constants/TropipayConstants";
|
|
5
7
|
export class ServerSideUtils {
|
|
6
8
|
private tropipay: Tropipay;
|
|
7
9
|
constructor(tropipayInstance: Tropipay) {
|
|
@@ -38,4 +40,87 @@ export class ServerSideUtils {
|
|
|
38
40
|
.digest("hex");
|
|
39
41
|
return localSignature === signature;
|
|
40
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
|
+
}
|
|
41
126
|
}
|
package/src/utils/errors.ts
CHANGED
|
@@ -57,8 +57,12 @@ export function handleExceptions(
|
|
|
57
57
|
axiosResponse.status,
|
|
58
58
|
axiosResponse.data.error
|
|
59
59
|
);
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
case 429:
|
|
61
|
+
return new TropipayJSException(
|
|
62
|
+
`${errorMessage} Too many request or rate limited`,
|
|
63
|
+
axiosResponse.status,
|
|
64
|
+
axiosResponse.data.error
|
|
65
|
+
);
|
|
62
66
|
default:
|
|
63
67
|
return new TropipayJSException(
|
|
64
68
|
errorMessage,
|
|
@@ -78,11 +82,11 @@ export function handleExceptions(
|
|
|
78
82
|
return new TropipayJSException("An error occurred", 500, null);
|
|
79
83
|
}
|
|
80
84
|
} else {
|
|
81
|
-
return new TropipayJSException(
|
|
85
|
+
return new TropipayJSException("An Unknown error occurred", 500, null);
|
|
82
86
|
}
|
|
83
87
|
}
|
|
84
88
|
|
|
85
|
-
class TropipayJSException extends Error {
|
|
89
|
+
export class TropipayJSException extends Error {
|
|
86
90
|
code: number; // Status code
|
|
87
91
|
error: unknown;
|
|
88
92
|
message: string;
|
package/utils/TropipayUtils.d.ts
CHANGED
|
@@ -14,4 +14,32 @@ export declare class ServerSideUtils {
|
|
|
14
14
|
clientId: string;
|
|
15
15
|
clientSecret: string;
|
|
16
16
|
} | Tropipay, originalCurrencyAmount: string, bankOrderCode: string, signature: string): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Checks if the provided base64 string represents a square image.
|
|
19
|
+
*
|
|
20
|
+
* @param {string} base64String - The base64 string of the image
|
|
21
|
+
* @return {Promise<boolean>} A Promise that resolves to a boolean indicating whether the image is square
|
|
22
|
+
*/
|
|
23
|
+
static isBase64ImageSquare(base64String: string): Promise<boolean>;
|
|
24
|
+
/**
|
|
25
|
+
* Takes a local file path and returns a base64 representation of the file content.
|
|
26
|
+
*
|
|
27
|
+
* @param {string} filepath - the path of the file to be converted to base64
|
|
28
|
+
* @return {Promise<string>} a Promise that resolves to the base64 representation of the file content
|
|
29
|
+
*/
|
|
30
|
+
static fileToBase64(filepath: string): Promise<string>;
|
|
31
|
+
/**
|
|
32
|
+
* Get the base64 representation of a remote file from the given URL.
|
|
33
|
+
*
|
|
34
|
+
* @param {string} url - the URL of the file
|
|
35
|
+
* @return {Promise<string>} the base64 representation of the file
|
|
36
|
+
*/
|
|
37
|
+
static getBase64FromFileUrl(url: string): Promise<string>;
|
|
38
|
+
/**
|
|
39
|
+
* Check if the base64 string represents a valid image and has a valid size
|
|
40
|
+
*
|
|
41
|
+
* @param {string} base64Image - the base64 image to be validated
|
|
42
|
+
* @return {Promise<string>} the valid base64 image
|
|
43
|
+
*/
|
|
44
|
+
static isValidImage(base64Image: string): boolean;
|
|
17
45
|
}
|
package/utils/errors.d.ts
CHANGED
|
@@ -8,10 +8,9 @@ export interface ErrorDetail {
|
|
|
8
8
|
meta: unknown;
|
|
9
9
|
}
|
|
10
10
|
export declare function handleExceptions(error: AxiosError | Error): TropipayJSException;
|
|
11
|
-
declare class TropipayJSException extends Error {
|
|
11
|
+
export declare class TropipayJSException extends Error {
|
|
12
12
|
code: number;
|
|
13
13
|
error: unknown;
|
|
14
14
|
message: string;
|
|
15
15
|
constructor(message: string, code: number, data: unknown);
|
|
16
16
|
}
|
|
17
|
-
export {};
|