npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

tbc-rando-sdk

v0.1.0

Published

TypeScript SDK for the TBC Bank Checkout (TPAY) API — card payments, installments and subscriptions for Node.js and Next.js

Readme

tbc-rando-sdk

TypeScript SDK for the TBC Bank Checkout (TPAY) API — card payments, installments and subscriptions (recurring payments) for Node.js and Next.js.

  • Zero runtime dependencies (uses the built-in fetch, Node ≥ 18.17)
  • Automatic access-token caching and refresh (single token request even under concurrent load, transparent retry on 401)
  • Typed request/response models with client-side validation before anything hits the network
  • Handles TBC's wire-format quirks for you (returnurl lowercase, PascalCase installmentProducts)
  • Webhook/callback helper that plugs straight into a Next.js App Router route

Install

npm install tbc-rando-sdk

Setup

Credentials come from developers.tbcbank.ge (app apikey) and your TBC E-Commerce merchant dashboard (client_id / client_secret):

import { TbcCheckoutClient } from 'tbc-rando-sdk';

const tbc = new TbcCheckoutClient({
  apiKey: process.env.TBC_API_KEY!,
  clientId: process.env.TBC_CLIENT_ID!,
  clientSecret: process.env.TBC_CLIENT_SECRET!,
});

Create the client once (e.g. in a module) and reuse it — the ~24h access token is cached inside the instance. Server-side only: never expose these credentials to the browser. In Next.js use it in route handlers, server actions or server components.

1. Standard payment

import { getRedirectUrl } from 'tbc-rando-sdk';

const payment = await tbc.payments.create({
  amount: { currency: 'GEL', total: 49.9 },
  returnUrl: 'https://shop.example.ge/checkout/return',
  callbackUrl: 'https://shop.example.ge/api/tbc/callback',
  merchantPaymentId: 'ORDER-1042',
  description: 'Order #1042',
  language: 'EN',
});

// Send the customer to the hosted TBC checkout page:
const checkoutUrl = getRedirectUrl(payment);

Then check the outcome (from your callback handler or by polling):

import { isFinalPaymentStatus, PaymentStatus } from 'tbc-rando-sdk';

const details = await tbc.payments.retrieve(payment.payId);
if (details.status === PaymentStatus.Succeeded) {
  // fulfill the order
}
isFinalPaymentStatus(details.status); // stop polling when true

Refunds / cancellations and pre-authorizations:

await tbc.payments.cancel(payId);                    // full refund
await tbc.payments.cancel(payId, { amount: 10.5 });  // partial refund

// preAuth: true on create blocks the amount for up to 30 days…
await tbc.payments.completePreAuth(payId, 45.0);     // …then capture (≤ blocked amount)

2. Installments

Installments are checkout payments restricted to payment method 8. Your merchant account needs an installment campaignId/merchantKey configured in the TBC back office. The customer picks the terms on TBC's page.

const payment = await tbc.installments.create({
  returnUrl: 'https://shop.example.ge/checkout/return',
  callbackUrl: 'https://shop.example.ge/api/tbc/callback',
  merchantPaymentId: 'ORDER-1043',
  products: [
    { name: 'Laptop', price: 2199, quantity: 1 },
    { name: 'Mouse', price: 89.5, quantity: 2 },
  ],
  // total is computed as sum(price × quantity) = 2378.00; currency defaults to GEL
});

const checkoutUrl = getRedirectUrl(payment);

The SDK validates product lines, computes the total with proper money rounding, and serializes products to the PascalCase wire format TBC expects ({ Name, Price, Quantity }).

3. Subscriptions (recurring payments)

TBC subscriptions are built on saved cards (card saving must be enabled for your merchant by TBC):

Step 1 — the customer pays once and the card is saved:

const first = await tbc.subscriptions.start({
  amount: { currency: 'GEL', total: 9.99 },
  returnUrl: 'https://app.example.ge/subscribe/return',
  callbackUrl: 'https://app.example.ge/api/tbc/callback',
  description: 'Monthly plan',
  saveCardToDate: '1230', // optional MMYY limit
});
// redirect the customer to getRedirectUrl(first)

Step 2 — after the payment succeeds, persist the card token:

const details = await tbc.payments.retrieve(first.payId);
const recId = details.recurringCard?.recId; // store this against the subscriber

Step 3 — bill on your own schedule (cron, queue, etc.), no customer interaction:

const charge = await tbc.subscriptions.charge({
  recId,
  amount: 9.99,
  currency: 'GEL',
  initiator: 'merchant', // merchant-initiated (unattended) billing
  merchantPaymentId: 'SUB-1042-2026-08',
});

if (charge.status === 'Succeeded') {
  // extend the subscription
}

Cancel the subscription (deletes the saved card):

await tbc.subscriptions.cancel(recId);

Callbacks (webhooks) in Next.js

TBC POSTs a PaymentId to your callbackUrl when a payment reaches a final status. The body carries no status — the handler fetches the authoritative payment details from the API before invoking your listener, so forged callbacks can't inject a fake status.

// app/api/tbc/callback/route.ts
import { tbc } from '@/lib/tbc'; // your shared TbcCheckoutClient instance

export const POST = tbc.webhooks.handler(async (payment) => {
  switch (payment.status) {
    case 'Succeeded':
      await fulfillOrder(payment.payId);
      break;
    case 'Failed':
    case 'Expired':
      await markOrderFailed(payment.payId);
      break;
  }
});

Lower-level pieces if you're not on the App Router:

import { parseCallbackBody, TBC_CALLBACK_IPS } from 'tbc-rando-sdk';

// pages/api/tbc-callback.ts
export default async function handler(req, res) {
  const { payment } = await tbc.webhooks.resolveCallback(req.body);
  // ...handle payment.status
  res.status(200).send('OK');
}

TBC_CALLBACK_IPS lists the four IPs TBC sends callbacks from, for firewall whitelisting; the route helper can also enforce them via { verifySourceIp: true } (only when X-Forwarded-For is set by your own proxy).

Error handling

import { TbcApiError, TbcAuthError, TbcValidationError } from 'tbc-rando-sdk';

try {
  await tbc.subscriptions.charge({ recId, amount: 9.99, currency: 'GEL', initiator: 'merchant' });
} catch (error) {
  if (error instanceof TbcApiError) {
    error.status;     // HTTP status
    error.resultCode; // e.g. 'decline_not_sufficient_funds', 'decline_expired_card'
    error.systemCode; // TBC problem code, e.g. 'tpay.400.012'
  }
}
  • TbcValidationError — thrown client-side before any request is sent
  • TbcAuthError — HTTP 401 (bad apikey or token; the SDK already retried once with a fresh token)
  • TbcApiError — any other non-2xx API response, with resultCode for business declines (reference)

Decline codes are available as constants:

import { ResultCode } from 'tbc-rando-sdk';

if (error instanceof TbcApiError && error.resultCode === ResultCode.DeclineNotSufficientFunds) {
  // ask the customer for another card
}

Constants

PaymentMethod, PaymentStatus, ResultCode, Currency and Language are exported as enum-style as const objects (each name is also the matching TypeScript type). Unlike real TS enums, plain strings stay assignable — status === 'Succeeded' and status === PaymentStatus.Succeeded both type-check, and the objects tree-shake cleanly.

Payment methods

Restrict the checkout page via methods:

import { PaymentMethod } from 'tbc-rando-sdk';

await tbc.payments.create({
  amount: { currency: 'GEL', total: 20 },
  returnUrl: '…',
  methods: [PaymentMethod.Card, PaymentMethod.ApplePay, PaymentMethod.GooglePay],
});

| Constant | ID | Notes | | --- | --- | --- | | PaymentMethod.WebQr | 4 | QR / BNPL, needs back-office activation | | PaymentMethod.Card | 5 | enabled by default | | PaymentMethod.InternetBank | 7 | needs activation | | PaymentMethod.Installment | 8 | needs campaign config; use tbc.installments | | PaymentMethod.ApplePay | 9 | needs activation | | PaymentMethod.GooglePay | 14 | needs activation |

Development

npm test           # vitest (56 tests, fully mocked — no network)
npm run typecheck  # tsc --noEmit
npm run build      # tsup → dist/ (ESM + CJS + d.ts)

Notes & limitations

  • TBC has no public sandbox; testing happens on production with small amounts (docs). baseUrl is configurable for mock servers.
  • The split-payment endpoint (/tpay/payments with split) isn't wrapped yet; use tbc.request() directly if you need it.