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

@tap-payments/node

v0.1.4

Published

Official Node.js & TypeScript SDK for the Tap Payments API.

Readme

Tap Payments Node.js SDK

npm

The official Node.js & TypeScript SDK for the Tap Payments API. Convenient typed access to the API from server-side JavaScript/TypeScript — auth, retries, timeouts, and error handling handled for you.

⚠️ This SDK is for server-side use only. It uses your secret API key, which must never be exposed in a browser, mobile app, or any client-side code. See Security.


Contents


Requirements

  • Node.js 18+ (uses the built-in global fetch). For older runtimes, supply a custom HTTP client or a fetch polyfill.
  • Works with TypeScript (types bundled), ESM, and CommonJS.

Installation

npm install @tap-payments/node
# or
yarn add @tap-payments/node
# or
pnpm add @tap-payments/node

Quick start

ESM / TypeScript

import Tap from '@tap-payments/node';

const tap = new Tap(process.env.TAP_SECRET_KEY!);

const charge = await tap.charges.create({
  amount: 10,
  currency: 'KWD',
  customer: {
    first_name: 'Ahmed',
    last_name: 'Elsharkawy',
    email: '[email protected]',
    phone: { country_code: '965', number: '51234567' },
  },
  source: { id: 'src_all' },
  redirect: { url: 'https://example.com/checkout/return' },
});

console.log(charge.id, charge.status);

CommonJS

const { Tap } = require('@tap-payments/node');

const tap = new Tap(process.env.TAP_SECRET_KEY);

The API key is read, in order, from the constructor string, the apiKey config field, or the TAP_SECRET_KEY environment variable.

Configuration

Pass a key string, a config object, or both:

const tap = new Tap({
  apiKey: 'sk_test_XXXX',
  timeout: 30_000, // ms; default 80_000
  maxRetries: 3, // default 2 (set 0 to disable)
  telemetry: true, // default true; anonymous SDK metrics, never PII/payloads
  appInfo: {
    name: 'acme-storefront',
    version: '1.4.2',
    url: 'https://acme.example',
  },
  defaultHeaders: { 'X-My-Header': 'value' },
});

| Option | Type | Default | Description | | ---------------- | ----------------------- | ---------------------------- | -------------------------------------------- | | apiKey | string | process.env.TAP_SECRET_KEY | Your secret key (sk_test_… / sk_live_…). | | baseUrl | string | https://api.tap.company | API origin. | | apiVersion | string | v2 | API version segment. | | timeout | number | 80000 | Per-request timeout in milliseconds. | | maxRetries | number | 2 | Automatic retries for transient failures. | | httpClient | HTTPClient | FetchHTTPClient | Custom transport (see below). | | telemetry | boolean | true | Send anonymous SDK telemetry headers. | | appInfo | AppInfo | — | Your app info, appended to the User-Agent. | | defaultHeaders | Record<string,string> | {} | Headers merged into every request. |

Inspect the resolved config (with the key masked) via tap.getConfig().

The redirect flow (3DS / KNET / mada)

Most Tap payments require the customer to complete an off-session step. When they do, the created charge is returned with status INITIATED and a transaction.url:

const charge = await tap.charges.create({
  amount: 25.5,
  currency: 'SAR',
  customer: { first_name: 'Ahmed', email: '[email protected]' },
  source: { id: 'src_all' },
  redirect: { url: 'https://example.com/checkout/return' }, // required for these flows
  post: { url: 'https://example.com/webhooks/tap' }, // optional webhook
});

if (charge.transaction?.url) {
  // 1. Redirect the customer's browser to this URL.
  // 2. Tap sends them back to your `redirect.url` when done.
  // 3. Confirm the final status from your `post.url` webhook or by retrieving the charge.
  return res.redirect(charge.transaction.url);
}

Per-request options

Every resource method accepts an optional final argument to override client settings for that call:

const controller = new AbortController();

await tap.charges.create(params, {
  idempotencyKey: 'order-1024', // stable de-duplication key
  timeout: 10_000, // override client timeout
  maxRetries: 0, // disable retries for this call
  apiKey: 'sk_test_other', // use a different key
  headers: { 'X-Trace-Id': 'abc' }, // extra headers
  signal: controller.signal, // cancel the request
});

Automatic retries & idempotency

The SDK automatically retries transient failures — network errors, timeouts, HTTP 429, and 5xx — up to maxRetries, using exponential backoff with jitter and honoring the Retry-After header.

To make those retries safe, every mutating request (e.g. charges.create) automatically includes an idempotency key, so a charge is never created twice. Supply your own idempotencyKey (e.g. your order id) to also de-duplicate retries you trigger yourself.

Error handling

Every failure throws a typed subclass of TapError. Catch the base class, or narrow to a specific type:

import Tap, {
  TapError,
  TapAuthenticationError,
  TapInvalidRequestError,
  TapRateLimitError,
} from '@tap-payments/node';

try {
  await tap.charges.create(params);
} catch (err) {
  if (err instanceof TapInvalidRequestError) {
    console.error('Validation failed:', err.code, err.message);
  } else if (err instanceof TapAuthenticationError) {
    console.error('Bad API key.');
  } else if (err instanceof TapRateLimitError) {
    console.error('Rate limited — retry later.');
  } else if (err instanceof TapError) {
    console.error(`[${err.type}] ${err.message} (request ${err.requestId})`);
  } else {
    throw err;
  }
}

| Error class | When | HTTP | | ------------------------ | ------------------------------------------------ | ----------- | | TapAuthenticationError | Missing/invalid/revoked API key | 401 | | TapPermissionError | Key lacks permission / feature not enabled | 403 | | TapInvalidRequestError | Malformed request or failed validation | 400/422 | | TapNotFoundError | Resource not found | 404 | | TapRateLimitError | Rate limit exceeded | 429 | | TapAPIError | Tap server error | 5xx | | TapConnectionError | Network failure, timeout, or abort (no response) | — | | TapConfigurationError | SDK misconfiguration (e.g. missing key) | — |

Each error exposes type, code, statusCode, requestId, headers, and raw. Its toJSON() redacts the Authorization header, so logging an error never leaks your key.

Tap's business code (e.g. "1100", "1117") can be resolved to a message with the bundled reference — handy when a server description isn't present:

import {
  describeErrorCode,
  describeResponseCode,
  CHARGE_RESPONSE_CODES,
} from '@tap-payments/node';

describeErrorCode('1100'); // → "Header values are missing"
describeResponseCode(CHARGE_RESPONSE_CODES, charge.response?.code); // → "Captured"

Response metadata

Every returned object carries a non-enumerable lastResponse with HTTP details, without interfering with JSON.stringify:

const charge = await tap.charges.create(params);
charge.lastResponse.statusCode; // 200
charge.lastResponse.requestId; // 'req_…' — quote this in support tickets
charge.lastResponse.retries; // how many retries were needed

Custom HTTP client

Provide any object implementing HTTPClient to route requests through a proxy, add instrumentation, or run without a global fetch:

import Tap, { type HTTPClient } from '@tap-payments/node';

const httpClient: HTTPClient = {
  async request(req) {
    const res = await myFetch(req.url, {
      method: req.method,
      headers: req.headers,
      body: req.body,
      signal: req.signal,
    });
    return {
      statusCode: res.status,
      headers: Object.fromEntries(res.headers),
      body: await res.text(),
    };
  },
};

const tap = new Tap({ apiKey: 'sk_test_XXXX', httpClient });

Security

  • Secret keys are backend-only. Never ship sk_test_… / sk_live_… to a browser, mobile app, or public repo. Publishable keys (pk_…) are for client-side tokenization only — the SDK warns if one is used here.
  • Keys are never logged. The Authorization header is redacted in error serialization, and tap.getConfig() masks the key.
  • TLS everywhere. The default base URL is HTTPS; the SDK warns on non-HTTPS, non-localhost base URLs.
  • Idempotency on mutating requests prevents duplicate charges on retry.
  • Load keys from environment variables or a secrets manager (TAP_SECRET_KEY), not source code.

See SECURITY.md to report a vulnerability.

Compatibility

  • Runtimes: Node.js 18, 20, 22 (and any runtime with a global fetch, or a custom httpClient).
  • Modules: ships both ESM (import) and CommonJS (require) builds via the exports map.
  • Types: first-class TypeScript declarations (.d.ts) are bundled.
  • Dependencies: zero runtime dependencies.

API coverage

Every method returns the API object augmented with non-enumerable lastResponse HTTP metadata, and accepts an optional final RequestOptions argument.

| Resource | Accessor | Methods | | --------- | --------------- | ---------------------------------------------------------------------------- | | Resource | Accessor | Methods | | --------- | --------------- | ---------------------------------------------------------------------------- | | Charges | tap.charges | create · retrieve · update · list · download | | Authorize | tap.authorize | create · retrieve · update · void · list · download | | Tokens | tap.tokens | create · retrieve | | Cards | tap.cards | verify · list · retrieve · delete | | Refunds | tap.refunds | create · retrieve · update · list · download | | Intents | tap.intents | create · retrieve · update · list · cancel | | Invoices | tap.invoices | create · retrieve · update · list · remind · finalize · cancel | | Files | tap.files | create · retrieve | | Disputes | tap.disputes | download |

// A token (any variant — card, saved card, Apple Pay, … — is one method):
const token = await tap.tokens.create({
  card: { number: '5123450000000008', exp_month: 5, exp_year: 27, cvc: '100' },
});

// Authorize a hold, then void it:
const auth = await tap.authorize.create({
  amount: 10,
  currency: 'KWD',
  customer: { first_name: 'Ahmed', email: '[email protected]' },
  source: { id: 'src_all' },
});
await tap.authorize.void(auth.id);

// An invoice (due/expiry are in MILLISECONDS since the epoch; order.items required):
const invoice = await tap.invoices.create({
  due: Date.now() + 30 * 86_400_000,
  expiry: Date.now() + 60 * 86_400_000,
  customer: { first_name: 'Ahmed', email: '[email protected]' },
  order: {
    amount: 25,
    currency: 'KWD',
    items: [{ name: 'Consulting', quantity: 1, amount: 25 }],
  },
});

// Refund a captured charge:
const refund = await tap.refunds.create({
  charge_id: 'chg_XXXX',
  amount: 10,
  currency: 'KWD',
  reason: 'requested_by_customer',
});

Downloads and file uploads

  • Download endpoints return the raw response body so you can save it: charges.download, authorize.download, and disputes.download resolve to { data: string } (CSV text) — write it to a .csv file.
  • files.create uploads via multipart/form-data. Pass a Blob or a Uint8Array (with a filename); the SDK builds the form for you.

Escape hatch

The client also exposes a typed low-level escape hatch for endpoints not yet wrapped:

const result = await tap.request<MyType>(
  'POST',
  'some/endpoint',
  body,
  options,
);

Support

This SDK is built and maintained by Tap Payments.

License

MIT © Tap Payments