Skip to content

Latest commit

 

History

History
735 lines (566 loc) · 23.6 KB

SDK.markdown

File metadata and controls

735 lines (566 loc) · 23.6 KB
layout title permalink sidebar
page
SDK
/sdk/
true

We provide you with a NPM package for Node.js and Browser environments. Please see our GitHub page for details.

Getting started

This is a very short step by step guide to help you getting started and fetch a few transactions with our API and SDK in Node.js. You can do much more with it, please refer to the documentation or get in touch with us.

Requirements

Please have Node.js + NPM installed. This example is using plain JavaScript for simplicity, but you can use TypeScript as well.

Setup a new project

Create a new folder kontist-connect-example and open it in your terminal.

Init the project and add kontist as an dependency.

npm init -y
npm install kontist

Create a new file index.js

Create a new client

Add the following code to your index.js:

const { Client } = require("kontist");

const client = new Client({
    clientId: "<client id>",
    clientSecret: "<client secret>",
    scopes: ["transactions"]
});

You need to enter a <client id> and <client secret> here. Create your own client via the Client Management section. Click "Create Client" and enter a name and a secret. You will need to check the "Transactions" scope for this example. Enter some url like "https://example.com/" into the "Redirect URI" as we do not use it in this example.

Client Management{:class="img-responsive"}

After you did create the client click on it in the overview in order to reveal its client id.

Login and fetch transactions

Note: In this example you only want to access our own account. You can do this by using your own client and your username and password with the fetchTokenFromCredentials method. If you want to create an application that other users can use as well, then please follow the authentication section in the documentation. But for our own user this can be simplified by just using username and password like this:

const username = "<your username>";
const password = "<your password>";

client.auth.tokenManager.fetchTokenFromCredentials({ username, password })
    .then((tokenData) => {
        console.log("Your unconfirmed access token is:", tokenData.accessToken);
        console.log("Starting MFA...");
        return client.auth.push.getConfirmedToken();
    })
    .then((confirmed) => {
        console.log("Your confirmed access token is:", confirmed.accessToken);
        console.log("Fetching latest transactions...");
        return client.models.transaction.fetch();
    })
    .then((transactionData) => {
        console.dir(transactionData.items);
    })
    .catch((err) => console.error(err));

Save the index.js and run the script via

node index.js

You should see this output:

Your unconfirmed access token is: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ5eXkiLCJzY29wZSI6InRyYW5zYWN0aW9ucyIsImNsaWVudF9pZCI6Inh4eCIsImlhdCI6MTU3OTg4MDMxNCwiZXhwIjoxNTc5ODgzOTE0fQ.FBZZkGYSF1QCWhLXq7Y4oUDQ85AOx6qxT7wbf9BQ29k
Starting MFA...

Now you need to take your smartphone with the Kontist app and confirm the login. After that the confirmed access token is shown and then a few transactions are fetched.

Your confirmed access token is: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ5eXkiLCJzY29wZSI6InRyYW5zYWN0aW9ucyIsImNsaWVudF9pZCI6Inh4eCIsImNuZiI6eyJraWQiOiJ6enoifSwiaWF0IjoxNTc5ODgwMzQ3LCJleHAiOjE1Nzk4ODM5NDd9.dOavYfsqGpCu-wIUox8lQw_lo2yP8OA5QylQAKbeZn0
Fetching latest transactions...
[
  {
    id: 'a0c01510-d425-4193-8976-f6a3f82121e7',
    amount: 100,
    name: 'Test sender',
    iban: 'DE40220500009007705534',
    type: 'SEPA_CREDIT_TRANSFER',
    bookingDate: '2019-05-28T23:00:00.000Z',
    valutaDate: '2019-05-28T23:00:00.000Z',
    // ...
  },
  // ...
]

This was just a basic example. Please have a look at this documentation for more examples and things you can do with this SDK.

Installation

Node.js

Please install the SDK via npm install kontist. Depending on your application type you can then import it with

import { Client } from "kontist";

TypeScript users should add at least "lib": ["es2018.asynciterable", "es6", "dom"] to their tsconfig.json.

Browser

For your web application you can install the SDK via npm install kontist and then just load the bundle via

<script src="node_modules/kontist/dist/bundle.js"></script>

If you prefer you can skip the npm install and just use the latest version from our CDN with

<script src="https://cdn.kontist.com/sdk.min.js"></script>

Authentication

Node.js

If you are developing an application where you can store a clientSecret you can authenticate with regular OAuth2. In this example we will use express.

import express from "express";
import { Client } from "kontist";
const app = express();

We need to provide the values for our app from the Client Management and set the state to a random number. If you did not open an account with Kontist yet you should do so now.

const client = new Client({
  clientId: "<your client id>",
  clientSecret: "<your client secret>",
  redirectUri: "<your app url>/auth/callback",
  scopes: ["transactions", "transfers"],
  state: (Math.random() + "").substring(2)
});

If the user goes to "/auth" we will redirect him to the Kontist login.

app.get("/auth", async (req, res) => {
  const uri = await client.auth.tokenManager.getAuthUri();
  res.redirect(uri);
});

After the user authorized the app he will be redirect back to the redirectUri which we set to the /auth/callback endpoint.

app.get("/auth/callback", async (req, res) => {
  const callbackUrl = req.originalUrl;

  try {
    const token = await client.auth.tokenManager.fetchToken(callbackUrl);
    /* got access token, login successful */
    res.send("Successful, your token is " + token.accessToken);
  } catch (e) {
    /* handle error */
    res.send("Failed: " + JSON.stringify(e));
  }
});

The last thing left is to start listening on connections.

app.listen(3000, function() {
  console.log("Listening on port 3000!");
});

If you now visit http://localhost:3000/auth you will be directed to the Kontist login and then back to your application. The token will then be printed in the console and you can start using the client.

Browser

In some environments we cannot store a client secret without exposing it (e.g. a web application without a backend). To authorize the client we will use OAuth2 with the PKCE extension. During initialization of the client you just need to provide a verifier and persist it across page reloads. You also need to provide your clientId, a state and redirectUri. You can set all of this up in the Client Management. If did not open an account with Kontist yet you should do so now.

// persist two random values
sessionStorage.setItem(
  "state",
  sessionStorage.getItem("state") || (Math.random() + "").substring(2)
);
sessionStorage.setItem(
  "verifier",
  sessionStorage.getItem("verifier") || (Math.random() + "").substring(2)
);

// initialize Kontist client
const client = new Kontist.Client({
  clientId: "<your client id>",
  redirectUri: "<your url of the app>",
  scopes: ["transactions", "transfers"],
  state: sessionStorage.getItem("state"),
  verifier: sessionStorage.getItem("verifier")
});

If the user opens the page the first time we need to redirect him to the API so that he or she can authorize your application.

const code = new URL(document.location).searchParams.get("code");
if (!code) {
  // "code" query parameter missing, let's redirect the user to the login
  client.auth.tokenManager.getAuthUri().then(function(url) {
    window.location = url;
  });
} else {

After the authorization of the app the user is redirected back to the app's page (which must be available at redirectUri). There is a ?code=xxxxx query parameter attached to it. We can just put the whole url into the fetchToken method and it returns an access token.

  // we have a code, the client now can fetch a token
  client.auth.tokenManager.fetchToken(document.location.href).then(function(token) {
      console.log(token);
  });
}

After the successful call of fetchToken the client application is authorized and you can make requests to the API.

Using an existing token

If you already obtained an access token previously (either with a previous SDK authentication or with any other method like using Kontist's REST API directly), you can configure your SDK client instance to use this token with this method:

const token = client.auth.tokenManager.setToken(accessToken);

All SDK requests will then be performed with this new access token.

Optionally, you can also provide a refresh token if you have one:

const token = client.auth.tokenManager.setToken(accessToken, refreshToken);

Renewing access tokens

After the initial authentication steps described above, renewing access tokens can be done using this simple method:

const token = await client.auth.tokenManager.refresh();

Optionally, this method accepts a number as an argument to specify after how many milliseconds the refresh request should timeout (default is 10000):

// abort after 20 seconds
const token = await client.auth.tokenManager.refresh(20000);

The method is the same for both Node.js and Browser environments (it uses refresh tokens or PKCE with response_mode=web_message respectively).

Multi-Factor Authentication

Accessing Kontist banking APIs require Multi-Factor Authentication (MFA).

MFA is available once you have installed the Kontist application and paired your device in it.

The following steps are necessary to complete the MFA procedure:

  1. initiate the procedure by creating a challenge (Kontist SDK exposes a method to do that)
  2. click the push notification you received on your phone, it will open the Kontist application
  3. login (if applicable) and confirm the MFA by clicking on the corresponding button

Kontist SDK exposes a method to initiate the push notification MFA flow after you successfully received the initial access token:

// fetch a regular access token
const token = await client.auth.tokenManager.fetchToken(callbackUrl);
try {
  // create a push notification challenge and wait for confirmation
  const confirmedToken = await client.auth.push.getConfirmedToken();
  // once it has been verified, your `client` instance will have a confirmed access token
  // the confirmed token is also returned in case you want to store it
} catch (err) {
  // if the challenge expires, a `ChallengeExpiredError` will be thrown
  // if the challenge is denied, a `ChallengeDeniedError` will be thrown
  console.log(err);
}

After obtaining a confirmed access token with this method, you will have access to all banking APIs.

If your OAuth2 client is using refresh tokens, you will also receive a confirmed refresh token when the push notification MFA flow completes successfully. When renewing your access token with such a refresh token, it will already be confirmed, allowing you to perform the MFA procedure only once for the lifetime of your refresh token:

// initiate the push notification MFA flow
const { refreshToken } = await client.auth.push.getConfirmedToken();

// the received refreshToken is confirmed, and your SDK instance will be configured to use it for upcoming access token renewal requests
console.log(refreshToken);

// renew your access token using the regular method
const { accessToken } = await client.auth.tokenManager.refresh();

// your access token will be confirmed without having to go through the MFA flow again
console.log(accessToken);

If you want to cancel a pending push notification confirmation, you can call the following method:

client.auth.push.cancelConfirmation();

The Promise returned by getConfirmedToken will then reject with a MFAConfirmationCanceledError.

Advanced Topics

Some clients might use device binding with certificates as MFA or make use of other OAuth2 grant types. This depends on the environment where this application will run. Please see our advanced topics on authentication.

Using the SDK

Fetch transactions

You can use the fetch method to fetch the last 50 transactions:

const result = await client.models.transaction.fetch();

result then has following structure:

{
  items: [
    {
      id: "08995f8f-1f87-42ab-80d2-6e73a3db40e8",
      amount: 4711,
      name: "Example",
      iban: null,
      type: "SEPA_CREDIT_TRANSFER",
      bookingDate: "1570399200000",
      valutaDate: "2019-10-06T22:00:00.000+00:00",
      originalAmount: null,
      foreignCurrency: null,
      e2eId: null,
      mandateNumber: null,
      paymentMethod: "bank_account",
      category: null,
      userSelectedBookingDate: null,
      purpose: "kauf+dir+was",
      bookingType: "SEPA_CREDIT_TRANSFER",
      documentNumber: null,
      documentPreviewUrl: null,
      documentDownloadUrl: null,
      documentType: null
    }
    // ...
  ];
}

If there are more than 50 transactions, result will also contain a nextPage method. When called, nextPage will return another result object.

Another way would be to just iterate over client.models.transaction.fetchAll(). It implements the AsyncIterator interface, so you can do this to fetch all transactions:

let transactions = [];
for await (const transaction of client.models.transaction.fetchAll()) {
  transactions = transactions.concat(transaction);
}

Transactions search

If you want to provide search functionality for your users' transactions, you can use:

const result = await client.models.transaction.search(userInput);

result will have the same structure as the fetch method, returning only the transactions that match the user's input (up to 50 maximum):

{
  items: [
    {
      id: "08995f8f-1f87-42ab-80d2-6e73a3db40e8",
      amount: 4711,
      ...
    }
    // ...
  ];
}

Filtering will be done by looking at each word in the user's input, and look for matches for name (sender/receiver), iban, purpose (reference) and amount (when applicable) fields, and return only matching transactions.

For example:

const result = await client.models.transaction.search("DE123456 Paypal 42");

Will return all transactions which have iban containing "DE123456", or name containing "Paypal", or purpose containing "Paypal", or amount equal to 42€, or amount equal to -42€.

Note: Under the hood, it just uses the filter argument of the fetch method with a predefined set of generic filters covering the most common use case.

Categorize a transaction

Transactions can be categorized using:

const result = await client.models.transaction.categorize({
	id: "08995f8f-1f87-42ab-80d2-6e73a3db40e8",
  category: TransactionCategory.TaxPayment,
  userSelectedBookingDate: "2019-10-06"
});

Note: Only transactions of types TaxPayment, VatPayment , TaxRefund, VatRefund can have a userSelectedBookingDate, if you provide an invalid combination, your categorization request will be rejected.

Subscribe to new transactions

A 'Subscribtion' is a GraphQL concept allowing clients to listen to new events published by a GraphQL server.

The SDK allows you to subscribe to new transactions using the subscribe method:

const onNext = transaction => {
  // do something with the transaction
};

const onError = error => {
  // do something with the error
};

client.models.transaction.subscribe(onNext, onError);

Whenever a new transaction is created, the onNext function will be called. Whenever an error occurs with the subscription, the onError function will be called.

The subscribe method returns a Subscription object with an unsubscribe method to be called when you want to unsubscribe to new transactions:

const { unsubscribe } = client.models.transaction.subscribe(onNext, onError);
// ...
unsubscribe();
// after this point, onNext / onError will no longer be called when new transactions / errors are received

Fetch transfers

You can use the fetch method to fetch the last 50 transfers of a given type:

const result = await client.models.transfer.fetch({
  type: "SEPA_TRANSFER"
});

Specifying the type of transfer is mandatory, existing types are: SEPA_TRANSFER, STANDING_ORDER and TIMED_ORDER.

If you are using typescript you should use the corresponding Schema.TransferType enum:

import { Schema } from "kontist";

const result = await client.models.transfer.fetch({
  type: Schema.TransferType.SepaTransfer
});

Optionally, you can also specify a filter using the where field. For example, if you only want to get "scheduled" timed orders:

const result = await client.models.transfer.fetch({
  type: "TIMED_ORDER",
  where: {
    status: "SCHEDULED"
  }
});

And using typescript:

import { Schema } from "kontist";

const result = await client.models.transfer.fetch({
  type: Schema.TransferType.TimedOrder,
  where: {
    status: Schema.TransferStatus.Scheduled
  }
});

result then has following structure:

{
  items: [
    {
      amount: 4231,
      e2eId: "E-0f4c88099b7e6b62e002f302ed490dbc",
      executeAt: null,
      iban: "DE18512308000000060339",
      id: "f4ff3af1d198e222bf5f2be5301827aa",
      lastExecutionDate: null,
      nextOccurrence: null,
      purpose: "Transfer purpose",
      recipient: "Example",
      reoccurrence: null,
      status: "CONFIRMED"
    }
    // ...
  ];
}

If there are more than 50 transfers, result will also contain a nextPage method. When called, nextPage will return another result object.

Another way would be to just iterate over client.models.transfer.fetchAll(). It implements the AsyncIterator interface, so you can do this to fetch all transfers of a given type:

let transfers = [];
for await (const transfer of client.models.transfer.fetchAll({
  type: Schema.TransferType.StandingOrder
})) {
  transfers = transfers.concat(transfer);
}

Create a new transfer

To create and confirm a transfer:

const confirmationId = await client.models.transfer.createOne({
  amount: 1234,
  recipient: "<recipent_name>",
  iban: "<recipent_iban>",
  purpose: "<optional_description>",
  e2eId: "<optional_e2eId>",
});

// wait for sms
const smsToken = "...";

const result = await client.models.transfer.confirmOne(
  confirmationId,
  smsToken
);

Create multiple transfers

To create and confirm multiple transfers (with only one confirmation):

const confirmationId = await client.models.transfer.createMany([{
  amount: 1234,
  recipient: "<recipent_name>",
  iban: "<recipent_iban>",
  purpose: "<optional_description>",
  e2eId: "<optional_e2eId>",
}, {
  amount: 4567,
  recipient: "<recipent_name>",
  iban: "<recipent_iban>",
  purpose: "<optional_description>",
  e2eId: "<optional_e2eId>",
}]);

// wait for sms
const smsToken = "...";

const result = await client.models.transfer.confirmMany(
  confirmationId,
  smsToken
);

Create a standing order

See "Create a new transfer", but please include these additional fields:

executeAt: "<execution_date>" // e.g. 2017-03-30T12:56:54+00:00
lastExecutionDate: "<optional_date>" // optional, e.g. 2019-05-28T12:56:54+00:00
reoccurrence: "<StandingOrderReoccurenceType>" // e.g. StandingOrderReoccurenceType.Monthly

For reoccurrence please see StandingOrderReoccurenceType enum: Monthly, Quarterly, EverySixMonths, Annually

Please note that you can only create one (not many) standing orders at the same time.

Create a timed order

See "Create a new transfer", but please include this additional field:

executeAt: "<execution_date>" // e.g. 2017-03-30T12:56:54+00:00

Please note that you can only create one (not many) timed orders at the same time.

Cancel transfer

To cancel and confirm transfer cancellation:

let canceledTransfer;
const cancelResult = await client.models.transfer.cancelTransfer(
  TransferType.STANDING_ORDER,
  "<standing_order_id>"
);

if (!cancelResult.confirmationId) {
  canceledTransfer = cancelResult;
} else {
  // wait for sms
  const smsToken = "...";

  canceledTransfer = await client.models.transfer.confirmCancelTransfer(
    TransferType.STANDING_ORDER,
    confirmationId,
    smsToken
  );
}

Updating a Standing Order

You can change several attributes of an existing standing order by using:

const suggestions = await client.models.transfer.update({
  amount: 4231,
  type: TransferType.STANDING_ORDER,
  e2eId: "E-0f4c88099b7e6b62e002f302ed490dbc",
  id: "f4ff3af1d198e222bf5f2be5301827aa",
  lastExecutionDate: null,
  purpose: "Updated transfer purpose",
  reoccurrence: StandingOrderReoccurenceType.Monthly,
});

Notes:

  • Currently, only standing order can be updated, so you must specify that type.
  • While updating a standing order, any property that you do not specify will be set to null if applicable. This is why the amount property is mandatory.

Getting Transfer Suggestions

It can be useful to present users with IBAN and name suggestions (based on previous transactions and transfer data) when they create a new transfers. For this, you can use:

const suggestions = await client.models.transfer.suggestions();

suggestions will be an array of IBAN / Name suggestions:

[
  {
    name: "Example Suggestion 1",
    iban: "DE18512428000000060367"
  },
  {
    name: "Example Suggestion 2",
    iban: "DE58112428000000060367"
  }
]

Plain GraphQL requests

You can use the rawQuery method to send plain requests to the GraphQL endpoint like this:

const query = `{
  viewer {
    mainAccount {
      iban
      balance
    }
  }
}`;

const result = await client.graphQL.rawQuery(query);

GraphQL error handling

If one of your requests raises an error, an instance of GraphQLError will be thrown with the following properties:

Property Type Description
name name The name of the error, i.e. GraphQLError
message string A message describing the error.
status number The HTTP status code of the error.
type string The type of the error as defined by the Kontist API.

You can simply catch the error and execute some logic based on any of the above properties:

try {
  const confirmationId = await client.models.transfer.createOne({
    amount: 1234,
    recipient: "<recipent_name>",
    iban: "<recipent_iban>",
    purpose: "<optional_description>",
    e2eId: "<optional_e2eId>",
  });
} catch (error) {
  // Do something with the error:
  // GraphQLError {
  //   name: "GraphQLError",
  //   message: "There were insufficient funds to complete this action",
  //   status: 400,
  //   type: "https://api.kontist.com/problems/transfer/insufficient-funds"
  // }
}

Note: As per the GraphQL specification, the actual HTTP status code of the response will be either 200 or 500. Do not rely on that and rather use the status property of the error you caught.