Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
edb3160f35 | |||
0eb51cf3c5 | |||
80373424b4 | |||
d108baf672 | |||
031d140a44 | |||
c96ccf198c | |||
18835fa5ae | |||
c0e26cdc4b | |||
68a3fcb06f | |||
2433b0d7b2 |
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@mojoio/tink",
|
"name": "@mojoio/tink",
|
||||||
"version": "1.0.8",
|
"version": "1.0.13",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@mojoio/tink",
|
"name": "@mojoio/tink",
|
||||||
"version": "1.0.8",
|
"version": "1.0.13",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@pushrocks/smartrequest": "^1.1.56"
|
"@pushrocks/smartrequest": "^1.1.56"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mojoio/tink",
|
"name": "@mojoio/tink",
|
||||||
"version": "1.0.8",
|
"version": "1.0.13",
|
||||||
"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",
|
||||||
|
23
readme.md
23
readme.md
@ -27,6 +27,29 @@ Platform support | [;
|
||||||
|
const tinkUser = await tinkAccount.createTinkUser('<YourOwnUniqueUserId>');
|
||||||
|
const tinkLinkUrl = await tinkUser.getTinkLink('<marketCode like DE>');
|
||||||
|
|
||||||
|
// 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). :)
|
||||||
|
@ -19,11 +19,16 @@ 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.createTinkUser("user_1234_abc");
|
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 () => {
|
tap.test('should delete existing users', async () => {
|
||||||
|
process.exit(0);
|
||||||
expect(tinkTestAccount).toBeInstanceOf(tink.TinkAccount);
|
expect(tinkTestAccount).toBeInstanceOf(tink.TinkAccount);
|
||||||
const tinkUser = new tink.TinkUser(tinkTestAccount, null, 'user_1234_abc');
|
const tinkUser = new tink.TinkUser(tinkTestAccount, null, 'user_1234_abc');
|
||||||
await tinkUser.delete();
|
await tinkUser.delete();
|
||||||
|
@ -16,7 +16,9 @@ export class TinkAccount {
|
|||||||
public async getTinkHealthyBoolean(): Promise<boolean> {
|
public async getTinkHealthyBoolean(): Promise<boolean> {
|
||||||
const response = await plugins.smartrequest.request(
|
const response = await plugins.smartrequest.request(
|
||||||
'https://api.tink.com/api/v1/monitoring/healthy',
|
'https://api.tink.com/api/v1/monitoring/healthy',
|
||||||
{}
|
{
|
||||||
|
keepAlive: false,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
return response.body === 'ok';
|
return response.body === 'ok';
|
||||||
}
|
}
|
||||||
@ -25,7 +27,9 @@ export class TinkAccount {
|
|||||||
// lets get an accessToken for the request
|
// lets get an accessToken for the request
|
||||||
const response = await plugins.smartrequest.postFormDataUrlEncoded(
|
const response = await plugins.smartrequest.postFormDataUrlEncoded(
|
||||||
`${this.apiBaseUrl}/api/v1/oauth/token`,
|
`${this.apiBaseUrl}/api/v1/oauth/token`,
|
||||||
{},
|
{
|
||||||
|
keepAlive: false,
|
||||||
|
},
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
key: 'client_id',
|
key: 'client_id',
|
||||||
@ -46,6 +50,8 @@ export class TinkAccount {
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
if (response.statusCode !== 200) {
|
if (response.statusCode !== 200) {
|
||||||
|
console.log(response.statusCode);
|
||||||
|
console.log(response.body);
|
||||||
throw new Error('there was an error aquiring an access token.');
|
throw new Error('there was an error aquiring an access token.');
|
||||||
}
|
}
|
||||||
const clientAccessToken = response.body.access_token;
|
const clientAccessToken = response.body.access_token;
|
||||||
@ -61,6 +67,7 @@ export class TinkAccount {
|
|||||||
const response = await plugins.smartrequest.postFormDataUrlEncoded(
|
const response = await plugins.smartrequest.postFormDataUrlEncoded(
|
||||||
`${this.apiBaseUrl}/api/v1/oauth/authorization-grant/delegate`,
|
`${this.apiBaseUrl}/api/v1/oauth/authorization-grant/delegate`,
|
||||||
{
|
{
|
||||||
|
keepAlive: false,
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${accessToken}`,
|
Authorization: `Bearer ${accessToken}`,
|
||||||
},
|
},
|
||||||
@ -101,7 +108,9 @@ export class TinkAccount {
|
|||||||
const accessToken = await this.getClientAccessTokenForScope('authorization:grant');
|
const accessToken = await this.getClientAccessTokenForScope('authorization:grant');
|
||||||
const response = await plugins.smartrequest.postFormDataUrlEncoded(
|
const response = await plugins.smartrequest.postFormDataUrlEncoded(
|
||||||
`${this.apiBaseUrl}/api/v1/oauth/token`,
|
`${this.apiBaseUrl}/api/v1/oauth/token`,
|
||||||
{},
|
{
|
||||||
|
keepAlive: false,
|
||||||
|
},
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
key: 'code',
|
key: 'code',
|
||||||
@ -130,14 +139,6 @@ export class TinkAccount {
|
|||||||
return userAccessToken;
|
return userAccessToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getTinkLinkCode(externalUserIdArg: string) {
|
|
||||||
const authorizationCode = this.getUserAuthorizationCode(
|
|
||||||
externalUserIdArg,
|
|
||||||
'df05e4b379934cd09963197cc855bfe9',
|
|
||||||
'authorization:read,authorization:grant,credentials:refresh,credentials:read,credentials:write,providers:read,user:read'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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(optionsArg: {
|
public async request(optionsArg: {
|
||||||
@ -153,6 +154,7 @@ export class TinkAccount {
|
|||||||
console.log('tink is healthy, continuing...');
|
console.log('tink is healthy, continuing...');
|
||||||
}
|
}
|
||||||
const response = await plugins.smartrequest.request(`${this.apiBaseUrl}${optionsArg.urlArg}`, {
|
const response = await plugins.smartrequest.request(`${this.apiBaseUrl}${optionsArg.urlArg}`, {
|
||||||
|
keepAlive: false,
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${optionsArg.accessToken}`,
|
Authorization: `Bearer ${optionsArg.accessToken}`,
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@ -164,6 +166,11 @@ export class TinkAccount {
|
|||||||
return response.body;
|
return response.body;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getTinkUser(externalUserIdArg: string) {
|
||||||
|
const tinkuser = await TinkUser.getTinkUser(this, externalUserIdArg);
|
||||||
|
return tinkuser;
|
||||||
|
}
|
||||||
|
|
||||||
public async createTinkUser(externalUserIdArg: string) {
|
public async createTinkUser(externalUserIdArg: string) {
|
||||||
const tinkuser = await TinkUser.createNewTinkUser(this, externalUserIdArg);
|
const tinkuser = await TinkUser.createNewTinkUser(this, externalUserIdArg);
|
||||||
return tinkuser;
|
return tinkuser;
|
||||||
|
30
ts/tink.classes.tinkproviderconsent.ts
Normal file
30
ts/tink.classes.tinkproviderconsent.ts
Normal 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) {}
|
||||||
|
}
|
@ -1,23 +1,77 @@
|
|||||||
import * as plugins from './tink.plugins';
|
import * as plugins from './tink.plugins';
|
||||||
|
|
||||||
import { TinkAccount } from './tink.classes.tinkaccount';
|
import { TinkAccount } from './tink.classes.tinkaccount';
|
||||||
|
import { TinkProviderConsent } from './tink.classes.tinkproviderconsent';
|
||||||
|
|
||||||
export class TinkUser {
|
export class TinkUser {
|
||||||
// STATIC
|
// STATIC
|
||||||
public static async createNewTinkUser (tinkaccountArg: TinkAccount, externalUserIdArg: string) {
|
public static async createNewTinkUser(tinkAccountArg: TinkAccount, externalUserIdArg: string) {
|
||||||
const accessToken = await tinkaccountArg.getClientAccessTokenForScope('user:create');
|
const accessToken = await tinkAccountArg.getClientAccessTokenForScope('user:create');
|
||||||
const response = await tinkaccountArg.request({
|
const responseData: {
|
||||||
|
external_user_id: string;
|
||||||
|
user_id: string;
|
||||||
|
} = await tinkAccountArg.request({
|
||||||
urlArg: '/api/v1/user/create',
|
urlArg: '/api/v1/user/create',
|
||||||
accessToken,
|
accessToken,
|
||||||
methodArg: 'POST',
|
methodArg: 'POST',
|
||||||
payloadArg: {
|
payloadArg: {
|
||||||
"external_user_id": externalUserIdArg,
|
external_user_id: externalUserIdArg,
|
||||||
"market": "GB",
|
market: 'DE',
|
||||||
"locale": "en_US"
|
locale: 'en_US',
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const newTinkUser = new TinkUser (tinkaccountArg, response.user_id, response.external_user_id);
|
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;
|
return newTinkUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,7 +80,7 @@ export class TinkUser {
|
|||||||
public tinkUserId: string;
|
public tinkUserId: string;
|
||||||
public externalUserIdArg: string;
|
public externalUserIdArg: string;
|
||||||
|
|
||||||
public authorizationToken: string
|
public authorizationToken: string;
|
||||||
|
|
||||||
constructor(tinkAccountrefArg: TinkAccount, tinkUserIdArg: string, externalUserIdArg: string) {
|
constructor(tinkAccountrefArg: TinkAccount, tinkUserIdArg: string, externalUserIdArg: string) {
|
||||||
this.tinkAccountRef = tinkAccountrefArg;
|
this.tinkAccountRef = tinkAccountrefArg;
|
||||||
@ -38,14 +92,38 @@ export class TinkUser {
|
|||||||
* deletes the user
|
* deletes the user
|
||||||
*/
|
*/
|
||||||
public async delete() {
|
public async delete() {
|
||||||
const authorizationCode = await this.tinkAccountRef.getUserAuthorizationCode(this.externalUserIdArg, this.tinkAccountRef.clientId, 'user:delete');
|
const authorizationCode = await this.tinkAccountRef.getUserAuthorizationCode(
|
||||||
|
this.externalUserIdArg,
|
||||||
|
this.tinkAccountRef.clientId,
|
||||||
|
'user:delete'
|
||||||
|
);
|
||||||
const accessToken = await this.tinkAccountRef.getUserAccessToken(authorizationCode);
|
const accessToken = await this.tinkAccountRef.getUserAccessToken(authorizationCode);
|
||||||
const response = await this.tinkAccountRef.request({
|
const response = await this.tinkAccountRef.request({
|
||||||
methodArg: 'POST',
|
methodArg: 'POST',
|
||||||
accessToken,
|
accessToken,
|
||||||
payloadArg: {},
|
payloadArg: {},
|
||||||
urlArg: '/api/v1/user/delete'
|
urlArg: '/api/v1/user/delete',
|
||||||
});
|
});
|
||||||
console.log(`successfully deleted user with externalId ${this.externalUserIdArg}`);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user