Skip to content

fintech-sdk/tokenio-client

Repository files navigation

tokenio-client

npm TypeScript License: MIT

Production-grade TypeScript client for the Token.io Open Banking platform.

16 APIs — full TypeScript types — automatic OAuth2 token caching — retry with jitter — HMAC webhook verification — ESM + CJS


Installation

npm install tokenio-client
# or
pnpm add tokenio-client
# or
yarn add tokenio-client

Node.js ≥ 18 required (uses native fetch, crypto.subtle, crypto.getRandomValues).


Quick Start

import { TokenioClient } from "tokenio-client";

const client = new TokenioClient({
  clientId: process.env.TOKENIO_CLIENT_ID!,
  clientSecret: process.env.TOKENIO_CLIENT_SECRET!,
  // environment: "production",  // defaults to "sandbox"
});

// Initiate a payment
const payment = await client.payments.initiate({
  initiation: {
    bankId: "ob-modelo",
    amount: { value: "10.50", currency: "GBP" },
    creditor: { accountNumber: "12345678", sortCode: "040004", name: "Acme Ltd" },
    remittanceInformationPrimary: "Invoice #42",
    callbackUrl: "https://yourapp.com/payment/return",
    returnRefundAccount: true,
  },
});

// Handle the auth flow
if (payment.requiresRedirect()) {
  console.log("Redirect PSU to:", payment.redirectUrl);
} else if (payment.requiresEmbeddedAuth()) {
  console.log("Collect fields:", payment.embeddedAuthFields);
}

// Poll to final (prefer webhooks in production)
const final = await client.payments.pollUntilFinal(payment.id, {
  intervalMs: 2_000,
  timeoutMs: 60_000,
});
console.log("Payment status:", final.status);

API Coverage

Namespace Methods
client.payments initiate, get, list, getWithTimeout, provideEmbeddedAuth, generateQRCode, pollUntilFinal
client.vrp createConsent, getConsent, listConsents, revokeConsent, listConsentPayments, createPayment, getPayment, listPayments, confirmFunds
client.ais listAccounts, getAccount, listBalances, getBalance, listTransactions, getTransaction, listStandingOrders, getStandingOrder
client.banks listV1, listV2, listCountries
client.refunds initiate, get, list
client.payouts initiate, get, list
client.settlement createAccount, listAccounts, getAccount, listTransactions, getTransaction, createRule, listRules, deleteRule
client.transfers redeem, get, list
client.tokens list, get, cancel
client.tokenRequests store, get, getResult, initiateBankAuth
client.accountOnFile create, get, delete
client.subTpps create, list, get, delete
client.authKeys submit, list, get, delete
client.reports listBankStatuses, getBankStatus
client.webhooks setConfig, getConfig, deleteConfig, parse, parseOrThrow, typed decoders
client.verification initiate

Variable Recurring Payments (VRP)

// 1. Create consent
const consent = await client.vrp.createConsent({
  bankId: "ob-modelo",
  currency: "GBP",
  creditor: { accountNumber: "12345678", sortCode: "040004", name: "Acme" },
  maximumIndividualAmount: "500.00",
  periodicLimits: [
    { maximumAmount: "1000.00", periodType: "MONTH", periodAlignment: "CALENDAR" },
  ],
  callbackUrl: "https://yourapp.com/vrp/return",
});

// 2. Redirect PSU to authorize
if (consent.requiresRedirect()) {
  redirect(consent.redirectUrl!);
}

// 3. Confirm funds (optional)
const hasFunds = await client.vrp.confirmFunds(consent.id, "49.99");

// 4. Initiate payment against authorized consent
const payment = await client.vrp.createPayment({
  consentId: consent.id,
  amount: { value: "49.99", currency: "GBP" },
  remittanceInformationPrimary: "Monthly subscription",
});

Webhook Verification

const client = new TokenioClient({
  staticToken: "...",
  webhookSecret: process.env.TOKENIO_WEBHOOK_SECRET,
});

// Express handler
app.post("/webhooks/tokenio", express.raw({ type: "application/json" }), async (req, res) => {
  const sig = req.headers["x-token-signature"] as string;
  const result = await client.webhooks.parse(req.body, sig);

  if (!result.ok) {
    res.status(401).json({ error: result.error });
    return;
  }

  const { event } = result;

  switch (event.type) {
    case "payment.completed": {
      const data = client.webhooks.decodePaymentData(event);
      await handlePaymentCompleted(data.paymentId, data.status);
      break;
    }
    case "vrp.completed": {
      const data = client.webhooks.decodeVRPData(event);
      await handleVRPCompleted(data.vrpId, data.consentId);
      break;
    }
  }

  res.status(200).json({ received: true });
});

Error Handling

All methods throw TokenioError on failure:

import { TokenioError } from "tokenio-client";

try {
  const payment = await client.payments.get(paymentId);
} catch (err) {
  if (err instanceof TokenioError) {
    switch (err.code) {
      case "not_found":
        return null;
      case "rate_limit_exceeded":
        await sleep((err.retryAfter ?? 5) * 1000);
        return retry();
      case "unauthorized":
        throw new Error("Check your API credentials");
      default:
        logger.error("Token.io error", { code: err.code, status: err.status, traceId: err.requestId });
        throw err;
    }
  }
  throw err;
}

Error properties

Property Type Description
code ErrorCode Machine-readable code
message string Human-readable description
status number HTTP status (0 for client-side)
requestId string? X-Request-ID trace header
retryAfter number? Retry-After header in seconds
isRetryable boolean True for 429/5xx
isNotFound boolean True for 404
isUnauthorized boolean True for 401
isRateLimited boolean True for 429

Configuration

new TokenioClient({
  clientId: "...",          // OAuth2 client ID
  clientSecret: "...",      // OAuth2 client secret
  staticToken: "...",       // Bypass OAuth2 (tests only)
  environment: "sandbox",   // "sandbox" | "production"
  baseUrl: "...",           // Override API URL
  timeoutMs: 30_000,        // Per-request timeout
  maxRetries: 3,            // Max retries on 5xx/429
  retryWaitMinMs: 500,      // Min retry backoff
  retryWaitMaxMs: 5_000,    // Max retry backoff
  webhookSecret: "...",     // HMAC webhook secret
  logger: myLogger,         // Custom logger
})

Telemetry

Attach a global handler to receive telemetry events:

(globalThis as any).__tokenioTelemetry = (event) => {
  metrics.histogram("tokenio.request.duration", event.duration, {
    method: event.method,
    path: event.path,
    status: String(event.status),
  });
};

Event fields: event, method, path, status, duration (ms), timestamp.


Testing

import { vi } from "vitest";
import { TokenioClient, clearTokenCache } from "tokenio-client";

beforeEach(() => {
  clearTokenCache();
  vi.stubGlobal("fetch", vi.fn(async () =>
    new Response(JSON.stringify({
      payment: { id: "pm:test", status: "INITIATION_COMPLETED" }
    }), { status: 200, headers: { "content-type": "application/json" } })
  ));
});

it("gets a payment", async () => {
  const client = new TokenioClient({ staticToken: "test-token" });
  const payment = await client.payments.get("pm:test");
  expect(payment.id).toBe("pm:test");
  expect(payment.isFinal()).toBe(true);
});

License

MIT — see LICENSE.

About

Production-grade TypeScript client for the Token.io Open Banking platform. Covers all 16 APIs: Payments v2, VRP, AIS, Banks, Refunds, Payouts, Settlement, Transfers, Tokens, Token Requests, Account on File, Sub-TPPs, Auth Keys, Reports, Webhooks, and Verification.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors