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

@ionicfi/sdk

v0.4.3

Published

Server-side TypeScript SDK for the Ionic payments API.

Downloads

534

Readme

@ionicfi/sdk

Server-side TypeScript SDK for the Ionic payments API. Create payments, manage customers and subscriptions, and verify webhooks with typed requests and responses.

Install

npm install @ionicfi/sdk

Requires Node 18 or later. This is an ES module package; CommonJS projects can require() it on Node 20.19+ / 22.12+, or use dynamic import() on older runtimes.

Quickstart

Construct a client with a secret key and create a checkout session:

import { Ionic } from "@ionicfi/sdk";

const secretKey = process.env.IONIC_SECRET_KEY;
if (!secretKey) {
  throw new Error("IONIC_SECRET_KEY is required and was not set");
}
const ionic = new Ionic({ token: secretKey });

const session = await ionic.checkout.sessions.create({
  mode: "payment",
  line_items: [{ price_id: "price_Ab1Cd2Ef3Gh4Ij5Kl6Mn7Op8", quantity: 1 }],
  success_url: "https://example.com/return?session_id={CHECKOUT_SESSION_ID}",
  cancel_url: "https://example.com/",
});

console.log(session.url); // redirect the buyer here

Connected accounts and payment fields

Use a Platform Connect secret key to read connected accounts:

const platform = new Ionic({ token: process.env.IONIC_CONNECT_SECRET_KEY });
const accounts = await platform.connect.connectedAccounts.list({ limit: 100 });
for await (const account of accounts) {
  console.log(account.id, account.business_name);
}
const account = await platform.connect.connectedAccounts.retrieve({ id: accountId });

Start a redirect authorization with platform.connect.accountAuthorizations.create() and exchange the approved callback code with .exchange(). Preserve the original state and PKCE verifier and validate the callback state before exchanging the code. These endpoints use the Platform credential directly and do not accept Ionic-Account.

Use a merchant secret key with sessions:write to create a payment-fields session:

const merchant = new Ionic({ token: process.env.IONIC_SECRET_KEY });
const session = await merchant.tokenizationSessions.create({
  parent_origin: "https://pay.example.com",
});

Register that exact origin as a web domain for the merchant and key mode first. Return the session to your browser integration and pass it to mountPaymentFields. Sessions expire after 30 minutes; create a new one after expiry.

Webhooks

Verify inbound deliveries with webhooks.unwrap(). It checks the signature and replay window, then returns a typed event. Pass the raw request body exactly as received; a body that's been parsed and re-serialized by framework middleware will not verify:

import { WebhookParseError, WebhookVerificationError, webhooks } from "@ionicfi/sdk";

app.post("/webhooks/ionic", async (req, res) => {
  let event;
  try {
    event = webhooks.unwrap(req.rawBody, req.headers, process.env.IONIC_WEBHOOK_SECRET);
  } catch (err) {
    if (err instanceof WebhookVerificationError || err instanceof WebhookParseError) {
      return res.status(400).send(`webhook rejected: ${err.message}`);
    }
    throw err;
  }

  if (event.type === "checkout.session.completed") {
    await fulfill(event.data.object); // typed CheckoutSession
  }
  res.status(200).json({ received: true });
});

An unverifiable delivery throws WebhookVerificationError (bad signature, missing headers, stale timestamp) or WebhookParseError (authenticated body that isn't a webhook envelope). Respond 400 in both cases so the sender's retry logic kicks in. Never 200 a delivery you didn't process.

Fulfill from the webhook, not from the browser redirect. The buyer's success_url redirect proves they returned to your site; it doesn't prove the payment settled. Treat success_url/return handling as a fallback that reconciles state via checkout.sessions.retrieve, and do the actual order fulfillment when checkout.session.completed arrives.

The signing secret is shown once, in the response from webhookEndpoints.create. Store it immediately; it isn't retrievable afterward.

Typed imports

Every resource shape is exported alongside the client:

import type { PaymentIntent, CheckoutSession } from "@ionicfi/sdk";

Request deadlines

timeoutInSeconds covers the HTTP request, retry waits, and reading the response body for resource methods. The default is 60 seconds. A timeout throws IonicApiTimeoutError; it does not prove a payment failed. Reconcile the resource state before attempting another mutation. Caller cancellation through abortSignal remains a separate IonicApiError.

For raw fetch() responses and streaming endpoints, the same deadline includes all attempts and retry waits until the response is returned. The caller owns reading or cancelling the returned stream; its body has no SDK deadline. Caller abort signals still cancel that body after headers arrive. Raw fetch() preserves the caller's abort reason; resource methods report cancellation as IonicApiError. The SDK removes its abort listener when the body finishes, errors, or is cancelled, so the same caller signal can be reused across requests.

Error handling

Every failed API call throws IonicApiError. Catch that one class and branch on statusCode and the error body; it carries the parsed API error envelope plus a requestId to quote when contacting support:

import {
  CardError,
  InvalidRequestError,
  ApiError,
  IonicApiTimeoutError,
} from "@ionicfi/sdk";

try {
  await ionic.paymentIntents.confirm({ id: intentId });
} catch (err) {
  // Check the timeout FIRST: IonicApiTimeoutError extends IonicApiError, so a
  // broader check would swallow it.
  if (err instanceof IonicApiTimeoutError) {
    // No response arrived, which does NOT mean the operation failed: it may
    // have committed. Reconcile by retrieving the resource rather than
    // re-creating it, or you risk charging twice.
    await reconcile(intentId);
  } else if (err instanceof CardError) {
    // err.detail.decline_code carries the issuer's reason.
    showDeclineMessage(err.detail?.message);
  } else if (err instanceof InvalidRequestError) {
    // Retrying unchanged fails identically — fix the request.
    log.error("bad request", err.detail?.code, err.requestId);
  } else if (err instanceof ApiError) {
    // On a mutating call this does NOT prove the operation did not happen.
    await reconcile(intentId);
  } else {
    throw err;
  }
}

The hierarchy is CardError, InvalidRequestError, AuthenticationError, PermissionError, NotFoundError, ConflictError, RateLimitError and ApiError, all extending IonicApiError. There is exactly one of each in the package: the per-resource names the API reference uses (BadRequestError, UnauthorizedError, ...) are aliases of these, so IonicApi.checkout.BadRequestError and InvalidRequestError are the same class and instanceof cannot pick a wrong one.

Every error carries statusCode, a requestId worth quoting to support, and detail — the typed error body (code, message, decline_code, decline_type, retryable, doc_url), or undefined when the response had no parsable envelope, as with a gateway 502. errorDetail(err) reads the same value from an unknown.

The specific class comes from the statuses an endpoint documents. InvalidRequestError, AuthenticationError, PermissionError, RateLimitError and ApiError are documented on every endpoint, so those always arrive as their class.

CardError, NotFoundError and ConflictError are documented only where the endpoint can produce them: a list call cannot 404, and a customer lookup cannot raise a card decline. Anything undocumented arrives as IonicApiError with the correct statusCode and detail, so it is still catchable.

Reliability

  • Idempotency keys are automatic. Every mutating request (POST, PUT, PATCH, DELETE) carries an Idempotency-Key; the SDK generates one per call when you don't supply your own, so the automatic retries below replay the original operation instead of repeating it (a second charge, a second refund). To also make your own application-level retries safe, pass the same "Idempotency-Key" field with the same request body: the original response is replayed instead of the operation running twice.

  • Retries. Requests that fail with 408, 429, 502, 503, or 504 are retried automatically with backoff. A bare 500 is never retried: on a mutating endpoint it can mean the operation already committed.

  • Auto-pagination. List calls on paymentIntents, charges, refunds, customers, invoices, subscriptions, creditNotes, paymentMethods, setupIntents, catalog.products, catalog.prices, paymentLinks, and checkout.sessions and webhookEndpoints return a Page that exposes hasNextPage() / getNextPage() and is directly for await-able. Iteration advances with starting_after cursors, so rows created while you page are never skipped or repeated:

    const page = await ionic.paymentIntents.list({ limit: 100 });
    for await (const intent of page) {
      console.log(intent.id);
    }

    If the first request carried an offset, continuation requests drop it: the API rejects offset combined with starting_after, and iteration advances by cursor alone. Webhook-endpoint lists preserve their legacy full-result behavior when limit is omitted; the returned Page is terminal in that case.

Security

  • The secret key (sk_v1_test_… / sk_v1_live_…) authenticates every request in this SDK. Keep it server-side only, read from an environment variable. Never ship it to a browser or commit it to source control.
  • The webhook signing secret (whsec_…) is returned once, at endpoint creation. If you lose it, call webhookEndpoints.rotateSecret() for a new one; the previous secret is revoked immediately, and it isn't recoverable otherwise.
  • Test keys (_test_) return livemode: false objects and never move real money. Live keys (_live_) do. Keep the two separate in your environment configuration and never charge a live key from a test script.

Docs

Full API reference and guides: docs.ionicfi.com