Compare commits

..

16 Commits

Author SHA1 Message Date
031d140a44 1.0.11 2022-02-19 13:28:29 +01:00
c96ccf198c fix(core): update 2022-02-19 13:28:29 +01:00
18835fa5ae 1.0.10 2022-02-19 13:16:00 +01:00
c0e26cdc4b fix(core): update 2022-02-19 13:15:59 +01:00
68a3fcb06f 1.0.9 2022-02-19 11:43:11 +01:00
2433b0d7b2 fix(core): update 2022-02-19 11:43:10 +01:00
570a1cd6b2 1.0.8 2022-02-19 01:37:48 +01:00
9425a85150 fix(core): update 2022-02-19 01:37:47 +01:00
2b902aa31b 1.0.7 2022-02-19 01:34:07 +01:00
7bb93a5edf fix(core): update 2022-02-19 01:34:07 +01:00
4c10678b17 1.0.6 2022-02-18 12:55:57 +01:00
eb61d1abbf fix(core): update 2022-02-18 12:55:56 +01:00
a0fa17fa1d 1.0.5 2022-02-17 11:54:44 +01:00
f5373c8c8d fix(core): update 2022-02-17 11:54:44 +01:00
ee60d6085c 1.0.4 2022-02-15 23:50:37 +01:00
0ab5f2039c fix(core): update 2022-02-15 23:50:37 +01:00
8 changed files with 403 additions and 93 deletions

96
package-lock.json generated
View File

@ -1,22 +1,22 @@
{ {
"name": "@mojoio/tink", "name": "@mojoio/tink",
"version": "1.0.3", "version": "1.0.11",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@mojoio/tink", "name": "@mojoio/tink",
"version": "1.0.3", "version": "1.0.11",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@pushrocks/smartrequest": "^1.1.52" "@pushrocks/smartrequest": "^1.1.56"
}, },
"devDependencies": { "devDependencies": {
"@gitzone/tsbuild": "^2.1.25", "@gitzone/tsbuild": "^2.1.25",
"@gitzone/tsbundle": "^1.0.78", "@gitzone/tsbundle": "^1.0.78",
"@gitzone/tstest": "^1.0.64", "@gitzone/tstest": "^1.0.64",
"@pushrocks/qenv": "^4.0.10", "@pushrocks/qenv": "^4.0.10",
"@pushrocks/tapbundle": "^4.0.3", "@pushrocks/tapbundle": "^4.0.7",
"@types/node": "^17.0.18", "@types/node": "^17.0.18",
"tslint": "^6.1.3", "tslint": "^6.1.3",
"tslint-config-prettier": "^1.15.0" "tslint-config-prettier": "^1.15.0"
@ -2465,9 +2465,9 @@
} }
}, },
"node_modules/@pushrocks/smartexpect": { "node_modules/@pushrocks/smartexpect": {
"version": "1.0.11", "version": "1.0.12",
"resolved": "https://verdaccio.lossless.one/@pushrocks%2fsmartexpect/-/smartexpect-1.0.11.tgz", "resolved": "https://verdaccio.lossless.one/@pushrocks%2fsmartexpect/-/smartexpect-1.0.12.tgz",
"integrity": "sha512-LpNkDbOtEX+kFbFD8E7u+BV1XXEoicKv+7SeUSJcSXrakwgCe+MAUe8TffWax7YBZ4PGLYEpLpdRzCX67PjsdQ==", "integrity": "sha512-uZJ5OPr3ei14/Ovs2JRmKd7WixR0XGVVREkJ5xIsxKhqU/nm2spUbVLYjNBaKxDGCHjTAmdq8AV5Ola8F3Ia7w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -2778,14 +2778,14 @@
} }
}, },
"node_modules/@pushrocks/smartrequest": { "node_modules/@pushrocks/smartrequest": {
"version": "1.1.52", "version": "1.1.56",
"resolved": "https://verdaccio.lossless.one/@pushrocks%2fsmartrequest/-/smartrequest-1.1.52.tgz", "resolved": "https://verdaccio.lossless.one/@pushrocks%2fsmartrequest/-/smartrequest-1.1.56.tgz",
"integrity": "sha512-ctQvj/o3UQ3thK3TejflOh0wNSvPgmli4hiTPgXiUlHZyJEnkoRiRB+cmtJHDWngO/l83kwxWHQPrseNBYRN6Q==", "integrity": "sha512-iF6bApmTgd3ZvRK8OHa77UFg8nVZxS1Y6iL8VfHpWOXdSlQZcXo/WbvwxYtu0+wkERAfFtCTGrrLAPGsFm9lhw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@pushrocks/smartpromise": "^3.1.5", "@pushrocks/smartpromise": "^3.1.6",
"@pushrocks/smarturl": "^2.0.1", "@pushrocks/smarturl": "^2.0.1",
"agentkeepalive": "^4.1.4", "agentkeepalive": "^4.2.0",
"form-data": "^4.0.0" "form-data": "^4.0.0"
} }
}, },
@ -2908,16 +2908,16 @@
} }
}, },
"node_modules/@pushrocks/smarttime": { "node_modules/@pushrocks/smarttime": {
"version": "3.0.43", "version": "3.0.45",
"resolved": "https://verdaccio.lossless.one/@pushrocks%2fsmarttime/-/smarttime-3.0.43.tgz", "resolved": "https://verdaccio.lossless.one/@pushrocks%2fsmarttime/-/smarttime-3.0.45.tgz",
"integrity": "sha512-d+/2G9gkDNlG6bfBNISMTK1bQnOekt6xu4xiDLPG492aFwTaexASjn2+4OKB3oMa9hNONLcDVvhD+Nywwi74Rw==", "integrity": "sha512-3E/92Qmq7h2SpaA1TcVmWD02forTNQqEPE7xpk2dv/ussr/qb3WiM/c/D7Oe4lLuPxit0aJrcZlbtQs0H79uRg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@pushrocks/lik": "^5.0.0", "@pushrocks/lik": "^5.0.4",
"@pushrocks/smartdelay": "^2.0.13", "@pushrocks/smartdelay": "^2.0.13",
"@pushrocks/smartpromise": "^3.1.6", "@pushrocks/smartpromise": "^3.1.6",
"croner": "^4.0.69", "croner": "^4.0.86",
"dayjs": "^1.10.7", "dayjs": "^1.10.7",
"is-nan": "^1.3.2", "is-nan": "^1.3.2",
"pretty-ms": "^7.0.1" "pretty-ms": "^7.0.1"
@ -3025,18 +3025,18 @@
} }
}, },
"node_modules/@pushrocks/tapbundle": { "node_modules/@pushrocks/tapbundle": {
"version": "4.0.3", "version": "4.0.7",
"resolved": "https://verdaccio.lossless.one/@pushrocks%2ftapbundle/-/tapbundle-4.0.3.tgz", "resolved": "https://verdaccio.lossless.one/@pushrocks%2ftapbundle/-/tapbundle-4.0.7.tgz",
"integrity": "sha512-MC5iO8+FTKOeFLgOcnSgJsD466LKMNiVPgPCxpxRhONWX2XmRnqsci3Bc8lD3VphfebICPbKr2bMgvO62wtp4w==", "integrity": "sha512-ggm022doMy45+H66lYIOluEITAxm1VRqywd+4eK47FivvDaO06N+g/6eWcsav4KoB0n4QG71dAGxe4iS/8OpeQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@open-wc/testing-helpers": "^2.0.3", "@open-wc/testing-helpers": "^2.0.4",
"@pushrocks/smartdelay": "^2.0.13", "@pushrocks/smartdelay": "^2.0.13",
"@pushrocks/smartenv": "^4.0.16", "@pushrocks/smartenv": "^4.0.16",
"@pushrocks/smartexpect": "^1.0.11", "@pushrocks/smartexpect": "^1.0.12",
"@pushrocks/smartpromise": "^3.1.6", "@pushrocks/smartpromise": "^3.1.6",
"@pushrocks/smarttime": "^3.0.43" "@pushrocks/smarttime": "^3.0.45"
} }
}, },
"node_modules/@pushrocks/webrequest": { "node_modules/@pushrocks/webrequest": {
@ -5539,9 +5539,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/croner": { "node_modules/croner": {
"version": "4.0.83", "version": "4.1.95",
"resolved": "https://verdaccio.lossless.one/croner/-/croner-4.0.83.tgz", "resolved": "https://verdaccio.lossless.one/croner/-/croner-4.1.95.tgz",
"integrity": "sha512-uLsbJM6o1Q4g/+MYiyJlM+zkCWhLQBmu5v5nue58v7DVlfCWUVqj0JCmCipWtJxglBKDGMvsJj2DJMMWH/5E3A==", "integrity": "sha512-pFO5eKG2l+ku9F9zn1l/rRVKILiRNMjz51cMs12m36EOFKTDplP5wasxKpMkV4ar+M8Eulc7Ke+z1ecdKD5Bdw==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@ -17508,9 +17508,9 @@
} }
}, },
"@pushrocks/smartexpect": { "@pushrocks/smartexpect": {
"version": "1.0.11", "version": "1.0.12",
"resolved": "https://verdaccio.lossless.one/@pushrocks%2fsmartexpect/-/smartexpect-1.0.11.tgz", "resolved": "https://verdaccio.lossless.one/@pushrocks%2fsmartexpect/-/smartexpect-1.0.12.tgz",
"integrity": "sha512-LpNkDbOtEX+kFbFD8E7u+BV1XXEoicKv+7SeUSJcSXrakwgCe+MAUe8TffWax7YBZ4PGLYEpLpdRzCX67PjsdQ==", "integrity": "sha512-uZJ5OPr3ei14/Ovs2JRmKd7WixR0XGVVREkJ5xIsxKhqU/nm2spUbVLYjNBaKxDGCHjTAmdq8AV5Ola8F3Ia7w==",
"dev": true, "dev": true,
"requires": { "requires": {
"@pushrocks/smartdelay": "^2.0.13", "@pushrocks/smartdelay": "^2.0.13",
@ -17802,13 +17802,13 @@
} }
}, },
"@pushrocks/smartrequest": { "@pushrocks/smartrequest": {
"version": "1.1.52", "version": "1.1.56",
"resolved": "https://verdaccio.lossless.one/@pushrocks%2fsmartrequest/-/smartrequest-1.1.52.tgz", "resolved": "https://verdaccio.lossless.one/@pushrocks%2fsmartrequest/-/smartrequest-1.1.56.tgz",
"integrity": "sha512-ctQvj/o3UQ3thK3TejflOh0wNSvPgmli4hiTPgXiUlHZyJEnkoRiRB+cmtJHDWngO/l83kwxWHQPrseNBYRN6Q==", "integrity": "sha512-iF6bApmTgd3ZvRK8OHa77UFg8nVZxS1Y6iL8VfHpWOXdSlQZcXo/WbvwxYtu0+wkERAfFtCTGrrLAPGsFm9lhw==",
"requires": { "requires": {
"@pushrocks/smartpromise": "^3.1.5", "@pushrocks/smartpromise": "^3.1.6",
"@pushrocks/smarturl": "^2.0.1", "@pushrocks/smarturl": "^2.0.1",
"agentkeepalive": "^4.1.4", "agentkeepalive": "^4.2.0",
"form-data": "^4.0.0" "form-data": "^4.0.0"
} }
}, },
@ -17927,15 +17927,15 @@
} }
}, },
"@pushrocks/smarttime": { "@pushrocks/smarttime": {
"version": "3.0.43", "version": "3.0.45",
"resolved": "https://verdaccio.lossless.one/@pushrocks%2fsmarttime/-/smarttime-3.0.43.tgz", "resolved": "https://verdaccio.lossless.one/@pushrocks%2fsmarttime/-/smarttime-3.0.45.tgz",
"integrity": "sha512-d+/2G9gkDNlG6bfBNISMTK1bQnOekt6xu4xiDLPG492aFwTaexASjn2+4OKB3oMa9hNONLcDVvhD+Nywwi74Rw==", "integrity": "sha512-3E/92Qmq7h2SpaA1TcVmWD02forTNQqEPE7xpk2dv/ussr/qb3WiM/c/D7Oe4lLuPxit0aJrcZlbtQs0H79uRg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@pushrocks/lik": "^5.0.0", "@pushrocks/lik": "^5.0.4",
"@pushrocks/smartdelay": "^2.0.13", "@pushrocks/smartdelay": "^2.0.13",
"@pushrocks/smartpromise": "^3.1.6", "@pushrocks/smartpromise": "^3.1.6",
"croner": "^4.0.69", "croner": "^4.0.86",
"dayjs": "^1.10.7", "dayjs": "^1.10.7",
"is-nan": "^1.3.2", "is-nan": "^1.3.2",
"pretty-ms": "^7.0.1" "pretty-ms": "^7.0.1"
@ -18028,17 +18028,17 @@
} }
}, },
"@pushrocks/tapbundle": { "@pushrocks/tapbundle": {
"version": "4.0.3", "version": "4.0.7",
"resolved": "https://verdaccio.lossless.one/@pushrocks%2ftapbundle/-/tapbundle-4.0.3.tgz", "resolved": "https://verdaccio.lossless.one/@pushrocks%2ftapbundle/-/tapbundle-4.0.7.tgz",
"integrity": "sha512-MC5iO8+FTKOeFLgOcnSgJsD466LKMNiVPgPCxpxRhONWX2XmRnqsci3Bc8lD3VphfebICPbKr2bMgvO62wtp4w==", "integrity": "sha512-ggm022doMy45+H66lYIOluEITAxm1VRqywd+4eK47FivvDaO06N+g/6eWcsav4KoB0n4QG71dAGxe4iS/8OpeQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@open-wc/testing-helpers": "^2.0.3", "@open-wc/testing-helpers": "^2.0.4",
"@pushrocks/smartdelay": "^2.0.13", "@pushrocks/smartdelay": "^2.0.13",
"@pushrocks/smartenv": "^4.0.16", "@pushrocks/smartenv": "^4.0.16",
"@pushrocks/smartexpect": "^1.0.11", "@pushrocks/smartexpect": "^1.0.12",
"@pushrocks/smartpromise": "^3.1.6", "@pushrocks/smartpromise": "^3.1.6",
"@pushrocks/smarttime": "^3.0.43" "@pushrocks/smarttime": "^3.0.45"
} }
}, },
"@pushrocks/webrequest": { "@pushrocks/webrequest": {
@ -19976,9 +19976,9 @@
"dev": true "dev": true
}, },
"croner": { "croner": {
"version": "4.0.83", "version": "4.1.95",
"resolved": "https://verdaccio.lossless.one/croner/-/croner-4.0.83.tgz", "resolved": "https://verdaccio.lossless.one/croner/-/croner-4.1.95.tgz",
"integrity": "sha512-uLsbJM6o1Q4g/+MYiyJlM+zkCWhLQBmu5v5nue58v7DVlfCWUVqj0JCmCipWtJxglBKDGMvsJj2DJMMWH/5E3A==", "integrity": "sha512-pFO5eKG2l+ku9F9zn1l/rRVKILiRNMjz51cMs12m36EOFKTDplP5wasxKpMkV4ar+M8Eulc7Ke+z1ecdKD5Bdw==",
"dev": true "dev": true
}, },
"cross-spawn": { "cross-spawn": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@mojoio/tink", "name": "@mojoio/tink",
"version": "1.0.3", "version": "1.0.11",
"private": false, "private": false,
"description": "an unofficial api abstraction for tink.com", "description": "an unofficial api abstraction for tink.com",
"main": "dist_ts/index.js", "main": "dist_ts/index.js",
@ -16,13 +16,13 @@
"@gitzone/tsbundle": "^1.0.78", "@gitzone/tsbundle": "^1.0.78",
"@gitzone/tstest": "^1.0.64", "@gitzone/tstest": "^1.0.64",
"@pushrocks/qenv": "^4.0.10", "@pushrocks/qenv": "^4.0.10",
"@pushrocks/tapbundle": "^4.0.3", "@pushrocks/tapbundle": "^4.0.7",
"@types/node": "^17.0.18", "@types/node": "^17.0.18",
"tslint": "^6.1.3", "tslint": "^6.1.3",
"tslint-config-prettier": "^1.15.0" "tslint-config-prettier": "^1.15.0"
}, },
"dependencies": { "dependencies": {
"@pushrocks/smartrequest": "^1.1.52" "@pushrocks/smartrequest": "^1.1.56"
}, },
"browserslist": [ "browserslist": [
"last 1 chrome versions" "last 1 chrome versions"

View File

@ -27,6 +27,29 @@ Platform support | [![Supports Windows 10](https://badgen.net/badge/supports%20W
Use TypeScript for best inclass intellisense Use TypeScript for best inclass intellisense
```typescript
// this example assumes toplevel await
import * as tink from '@mojoio/tink';
const tinkAccount = new TinkAccount('clientId', 'clientSecret');
const tinkUser = await tinkAccount.createTinkUser('YouOwnUniqueUserId');
const tinkLinkUrl = await tinkUser.getTinkLink();
// present the link to your user to connect their bank accounts to the tink platform.
const tinkProviderConsents = await tinkUser.getProviderConsents();
for (const providerConsent of tinkProviderConsents) {
const bankAccounts = await providerConsent.getBankAccounts();
for (const bankAccount of bankAccounts) {
const transactions = bankAccount.getTransactions();
}
}
```
## Contribution ## Contribution
We are always happy for code contributions. If you are not the code contributing type that is ok. Still, maintaining Open Source repositories takes considerable time and thought. If you like the quality of what we do and our modules are useful to you we would appreciate a little monthly contribution: You can [contribute one time](https://lossless.link/contribute-onetime) or [contribute monthly](https://lossless.link/contribute). :) We are always happy for code contributions. If you are not the code contributing type that is ok. Still, maintaining Open Source repositories takes considerable time and thought. If you like the quality of what we do and our modules are useful to you we would appreciate a little monthly contribution: You can [contribute one time](https://lossless.link/contribute-onetime) or [contribute monthly](https://lossless.link/contribute). :)

View File

@ -19,8 +19,19 @@ tap.test('should report tink as healthy', async () => {
await expectAsync(tinkTestAccount.getTinkHealthyBoolean()).toBeTrue(); await expectAsync(tinkTestAccount.getTinkHealthyBoolean()).toBeTrue();
}); });
tap.test('should create a valid request', async () => { tap.test('should create a valid request', async (toolsArg) => {
await tinkTestAccount.request('', 'POST', '', {}); const tinkuser: tink.TinkUser = await tinkTestAccount.getTinkUser("user_1234_abc");
console.log(tinkuser);
console.log(await tinkuser.getTinkLinkForMarket()); // defaults to 'DE';
console.log(await tinkuser.getProviderConsents())
await toolsArg.delayFor(10000);
})
tap.test('should delete existing users', async () => {
process.exit(0);
expect(tinkTestAccount).toBeInstanceOf(tink.TinkAccount);
const tinkUser = new tink.TinkUser(tinkTestAccount, null, 'user_1234_abc');
await tinkUser.delete();
}) })
tap.start(); tap.start();

View File

@ -1 +1,2 @@
export * from './tink.classes.tinkaccount'; export * from './tink.classes.tinkaccount';
export * from './tink.classes.tinkuser';

View File

@ -1,61 +1,178 @@
import * as plugins from './tink.plugins'; import * as plugins from './tink.plugins';
import { TinkUser } from './tink.classes.tinkuser';
export class TinkAccount { export class TinkAccount {
private clientId: string; public clientId: string;
private clientSecret: string; private clientSecret: string;
private apiBaseUrl: string = 'https://api.tink.com';
constructor(clientIdArg: string, clientSecretArg: string) { constructor(clientIdArg: string, clientSecretArg: string) {
this.clientId = clientIdArg; this.clientId = clientIdArg;
this.clientSecret = clientSecretArg; this.clientSecret = clientSecretArg;
} }
public async getTinkHealthyBoolean (): Promise<boolean> { public async getTinkHealthyBoolean(): Promise<boolean> {
const response = await plugins.smartrequest.request('https://api.tink.com/api/v1/monitoring/healthy', { const response = await plugins.smartrequest.request(
'https://api.tink.com/api/v1/monitoring/healthy',
}); {
keepAlive: false,
}
);
return response.body === 'ok'; return response.body === 'ok';
} }
public async getClientAccessTokenForScope(scopeArg: string): Promise<string> {
// lets get an accessToken for the request
const response = await plugins.smartrequest.postFormDataUrlEncoded(
`${this.apiBaseUrl}/api/v1/oauth/token`,
{
keepAlive: false,
},
[
{
key: 'client_id',
content: this.clientId,
},
{
key: 'client_secret',
content: this.clientSecret,
},
{
key: 'grant_type',
content: 'client_credentials',
},
{
key: 'scope',
content: scopeArg,
},
]
);
if (response.statusCode !== 200) {
console.log(response.statusCode);
console.log(response.body);
throw new Error('there was an error aquiring an access token.');
}
const clientAccessToken = response.body.access_token;
return clientAccessToken;
}
public async getUserAuthorizationCode(
externalUserIdArg: string,
actorCLientIdArg: string,
scopeArg: string
) {
const accessToken = await this.getClientAccessTokenForScope('authorization:grant');
const response = await plugins.smartrequest.postFormDataUrlEncoded(
`${this.apiBaseUrl}/api/v1/oauth/authorization-grant/delegate`,
{
keepAlive: false,
headers: {
Authorization: `Bearer ${accessToken}`,
},
},
[
{
key: 'response_type',
content: 'code',
},
{
key: 'actor_client_id',
content: actorCLientIdArg,
},
{
key: 'external_user_id',
content: externalUserIdArg,
},
{
key: 'id_hint',
content: 'Hello there',
},
{
key: 'scope',
content: scopeArg,
},
]
);
if (response.statusCode !== 200) {
console.log(response.body);
throw new Error('there was an error aquiring an access token.');
}
const userAuthorizationCode = response.body.code;
return userAuthorizationCode;
}
public async getUserAccessToken(authorizationCode: string): Promise<string> {
const accessToken = await this.getClientAccessTokenForScope('authorization:grant');
const response = await plugins.smartrequest.postFormDataUrlEncoded(
`${this.apiBaseUrl}/api/v1/oauth/token`,
{
keepAlive: false,
},
[
{
key: 'code',
content: authorizationCode,
},
{
key: 'client_id',
content: this.clientId,
},
{
key: 'client_secret',
content: this.clientSecret,
},
{
key: 'grant_type',
content: 'authorization_code',
},
]
);
if (response.statusCode !== 200) {
console.log(response.body);
throw new Error('there was an error aquiring an access token.');
}
const userAccessToken = response.body.access_token;
return userAccessToken;
}
// the request method for tink respecting platform specific stuff // the request method for tink respecting platform specific stuff
// e.g. certain headers if needed // e.g. certain headers if needed
public async request(urlArg: string, methodArg: 'POST' | 'GET', scopeArg: string , payloadArg: any) { public async request(optionsArg: {
urlArg: string;
methodArg: 'POST' | 'GET';
accessToken: string;
payloadArg: any;
}) {
// check health // check health
if (!(await this.getTinkHealthyBoolean())) { if (!(await this.getTinkHealthyBoolean())) {
throw new Error('TINK is not healthy tight now. Please try again later.'); throw new Error('TINK is not healthy right now. Please try again later.');
} else { } else {
console.log('tink is healthy, continuing...'); console.log('tink is healthy, continuing...');
} }
// lets get an accessToken for the request const response = await plugins.smartrequest.request(`${this.apiBaseUrl}${optionsArg.urlArg}`, {
const response = await plugins.smartrequest.postFormData('https://api.tink.com/api/v1/oauth/token', { keepAlive: false,
headers: { headers: {
'content-type': 'multipart/form-data' Authorization: `Bearer ${optionsArg.accessToken}`,
} 'Content-Type': 'application/json',
}, [
{
name: 'client_id',
type: 'string',
payload: this.clientId,
contentType: 'text/plain'
}, },
{ method: optionsArg.methodArg,
name: 'client_secret', requestBody: JSON.stringify(optionsArg.payloadArg),
type: 'string', });
payload: this.clientSecret,
contentType: 'text/plain'
},
{
name: 'grant_type',
type: 'string',
payload: 'client_credentials',
contentType: 'text/plain'
},
{
name: 'scope',
type: 'string',
payload: 'user:create',
contentType: 'text/plain'
}
]);
console.log(response.statusCode); console.log(response.statusCode);
console.log(response.body); return response.body;
}; }
}
public async getTinkUser(externalUserIdArg: string) {
const tinkuser = await TinkUser.getTinkUser(this, externalUserIdArg);
return tinkuser;
}
public async createTinkUser(externalUserIdArg: string) {
const tinkuser = await TinkUser.createNewTinkUser(this, externalUserIdArg);
return tinkuser;
}
}

View File

@ -0,0 +1,30 @@
import * as plugins from './tink.plugins';
import { TinkUser } from './tink.classes.tinkuser';
/**
* a provider consent maps to tinks bank consents
*/
export class TinkProviderConsent {
// STATIC
public static async getProviderConsentsForUser(tinkUserRefArg: TinkUser) {
const returnProviderConsents: TinkProviderConsent[] = [];
const authorizationCode = await tinkUserRefArg.tinkAccountRef.getUserAuthorizationCode(
tinkUserRefArg.externalUserIdArg,
tinkUserRefArg.tinkAccountRef.clientId,
'accounts:read,balances:read,transactions:read,provider-consents:read'
);
const accessToken = await tinkUserRefArg.tinkAccountRef.getUserAccessToken(authorizationCode);
const responseData = await tinkUserRefArg.tinkAccountRef.request({
urlArg: '/api/v1/provider-consents',
accessToken,
methodArg: 'GET',
payloadArg: null
})
console.log(responseData);
return returnProviderConsents;
}
// INSTANCE
constructor(tinkUserRefArg: TinkUser) {}
}

View File

@ -1 +1,129 @@
import * as plugins from './tink.plugins'; import * as plugins from './tink.plugins';
import { TinkAccount } from './tink.classes.tinkaccount';
import { TinkProviderConsent } from './tink.classes.tinkproviderconsent';
export class TinkUser {
// STATIC
public static async createNewTinkUser(tinkAccountArg: TinkAccount, externalUserIdArg: string) {
const accessToken = await tinkAccountArg.getClientAccessTokenForScope('user:create');
const responseData: {
external_user_id: string;
user_id: string;
} = await tinkAccountArg.request({
urlArg: '/api/v1/user/create',
accessToken,
methodArg: 'POST',
payloadArg: {
external_user_id: externalUserIdArg,
market: 'DE',
locale: 'en_US',
},
});
const newTinkUser = await TinkUser.getTinkUser(tinkAccountArg, externalUserIdArg);
return newTinkUser;
}
public static async getTinkUser(tinkAccountArg: TinkAccount, externalUserIdArg: string) {
const authorizationCode = await tinkAccountArg.getUserAuthorizationCode(
externalUserIdArg,
tinkAccountArg.clientId,
'user:read'
);
const accessToken = await tinkAccountArg.getUserAccessToken(authorizationCode);
const responseData: {
appId: string;
created: string;
externalUserId: string;
flags: string[];
id: string;
nationalId: string;
profile: {
// cashbackEnabled: boolean; // deprecated
currency: string;
locale: string;
market: string;
notificationSettings: {
balance: boolean;
budget: boolean;
doubleCharge: boolean;
einvoices: boolean;
fraud: boolean;
income: boolean;
largeExpense: boolean;
leftToSpend: boolean;
loanUpdate: boolean;
summaryMonthly: boolean;
summaryWeekly: boolean;
transaction: boolean;
unusualAccount: boolean;
unusualCategory: boolean;
};
periodAdjustedDay: 25;
periodMode: 'MONTHLY_ADJUSTED' | 'MONTHLY';
timeZone: string;
};
// username: string; // not relevant
} = await tinkAccountArg.request({
urlArg: '/api/v1/user',
accessToken,
methodArg: 'GET',
payloadArg: null,
});
const newTinkUser = new TinkUser(tinkAccountArg, responseData.id, responseData.externalUserId);
return newTinkUser;
}
// INSTANCE
public tinkAccountRef: TinkAccount;
public tinkUserId: string;
public externalUserIdArg: string;
public authorizationToken: string;
constructor(tinkAccountrefArg: TinkAccount, tinkUserIdArg: string, externalUserIdArg: string) {
this.tinkAccountRef = tinkAccountrefArg;
this.tinkUserId = tinkUserIdArg;
this.externalUserIdArg = externalUserIdArg;
}
/**
* deletes the user
*/
public async delete() {
const authorizationCode = await this.tinkAccountRef.getUserAuthorizationCode(
this.externalUserIdArg,
this.tinkAccountRef.clientId,
'user:delete'
);
const accessToken = await this.tinkAccountRef.getUserAccessToken(authorizationCode);
const response = await this.tinkAccountRef.request({
methodArg: 'POST',
accessToken,
payloadArg: {},
urlArg: '/api/v1/user/delete',
});
console.log(`successfully deleted user with externalId ${this.externalUserIdArg}`);
}
/**
* gets a tink link that can be used by a user to connect accounts
* @returns
*/
public async getTinkLinkForMarket(countryIdArg: string = 'DE'): Promise<string> {
const authorizationCode = await this.tinkAccountRef.getUserAuthorizationCode(
this.externalUserIdArg,
'df05e4b379934cd09963197cc855bfe9', // this is a hardcoded app id for tink link, as recommended by tink.com
'authorization:read,authorization:grant,credentials:refresh,credentials:read,credentials:write,providers:read,user:read'
);
const tinkLinkUrl = `https://link.tink.com/1.0/business-transactions/connect-accounts?client_id=${'teststate'}&redirect_uri=https://console.tink.com/callback&authorization_code=${authorizationCode}&market=${countryIdArg}`;
return tinkLinkUrl;
}
public async getProviderConsents(): Promise<TinkProviderConsent[]> {
const providerConsents: TinkProviderConsent[] =
await TinkProviderConsent.getProviderConsentsForUser(this);
return providerConsents;
}
}