Skip to content

fintech-sdk/checkout-client

Repository files navigation

checkout-client

npm CI License: MIT

Production-grade TypeScript client for the Checkout.com API.

All 168 endpoints · OAuth 2.0 · Full-jitter retry · Idempotency · Webhook verification · Zero runtime dependencies


Features

100% API coverage All 168 endpoints, 43 typed service namespaces
Zero runtime dependencies Only Node.js built-ins (node:crypto, node:http)
Dual-module ESM + CommonJS, full TypeScript declarations
Deep imports checkout-client/payments, /vault, /disputes, /issuing, /webhook, /errors
OAuth 2.0 Singleflight token fetch, in-memory cache, proactive refresh 90s before expiry
Idempotency UUID v4 auto-injected on all eligible endpoints; override per-request
Full-jitter retry Exponential backoff on 429 + 5xx; configurable attempts and delays
AbortSignal Pass signal to any request for cancellation and timeout control
Webhook verification HMAC-SHA256 constant-time signature checking
AWS PrivateLink privateLink: truepl-{prefix}.api.checkout.com
Typed errors CheckoutError with status predicates and full response metadata
Structured logging Bring your own logger via the Logger interface
68 tests Vitest, mock fetch only — zero live API calls

Requirements

Node.js ≥ 18 (uses native fetch and crypto.randomUUID).


Installation

npm install checkout-client

Quick start

import { CheckoutClient } from "checkout-client";

const client = new CheckoutClient({
  prefix: process.env.CHECKOUT_PREFIX!, // first 8 chars of client_id
  environment: "sandbox",
  accessKeyId: process.env.CHECKOUT_ACCESS_KEY_ID,
  accessKeySecret: process.env.CHECKOUT_ACCESS_KEY_SECRET,
});

// Request a payment
const payment = await client.payments.request({
  amount: 10_000, // GBP £100.00 in minor units
  currency: "GBP",
  source: { type: "token", token: "tok_..." },
  reference: "ORD-001",
});

// Capture it
await client.payments.capture(payment.id);

// Partial refund (£25)
await client.payments.refund(payment.id, { amount: 2_500 });

Authentication

OAuth 2.0 (recommended)

new CheckoutClient({
  prefix: "vkuhvk4v",
  accessKeyId: "ack_...",
  accessKeySecret: "...",
});

Tokens are fetched once, cached in-memory, and proactively refreshed 90 s before expiry. Concurrent requests coalesce onto a single in-flight token fetch.

Static secret key

new CheckoutClient({ prefix: "vkuhvk4v", secretKey: "sk_..." });

Configuration

import type { CheckoutConfig } from "checkout-client";

const config: CheckoutConfig = {
  prefix: "vkuhvk4v", // required
  environment: "production", // "production" | "sandbox"
  accessKeyId: "ack_...", // OAuth 2.0
  accessKeySecret: "...",
  secretKey: "sk_...", // static key (alternative to OAuth)
  publicKey: "pk_...", // tokenization only
  privateLink: false, // AWS PrivateLink
  idempotencyKeyPrefix: "myapp", // auto-key prefix: "myapp-{uuid}"
  timeoutMs: 30_000, // per-request timeout
  retry: {
    maxAttempts: 4, // 1 initial + 3 retries
    baseDelayMs: 500,
    maxDelayMs: 30_000,
  },
  fetch: customFetch, // injectable for testing/proxies
  logger: {
    debug: (msg, meta) => console.log(msg, meta),
    warn: (msg, meta) => console.warn(msg, meta),
  },
};

Error handling

import { CheckoutError } from "checkout-client";
// or: import { CheckoutError } from "checkout-client/errors";

try {
  await client.payments.request({ currency: "GBP" });
} catch (err) {
  if (err instanceof CheckoutError) {
    // HTTP status predicates
    err.isNotFound(); // 404
    err.isRateLimited(); // 429 — already retried maxAttempts times
    err.isConflict(); // 409 — idempotency key collision
    err.isUnauthorized(); // 401
    err.isForbidden(); // 403
    err.isValidationError(); // 422
    err.isServerError(); // 5xx

    // Rich metadata
    err.statusCode; // number
    err.requestId; // string — include in Checkout.com support tickets
    err.errorType; // e.g. "request_invalid"
    err.errorCodes; // e.g. ["amount_invalid", "currency_required"]
    err.apiMessage; // human-readable summary
    err.rawBody; // complete response body for debugging
  }
}

Idempotency

Cko-Idempotency-Key (UUID v4) is auto-injected on all eligible endpoints: POST /payments (and all action sub-paths), POST /payment-contexts, POST /transfers.

// Custom key — safe to retry with the same value
await client.payments.request(req, { idempotencyKey: "order-42-attempt-1" });

// Prefix auto-generated keys
new CheckoutClient({ idempotencyKeyPrefix: "myapp" });
// → "myapp-550e8400-e29b-41d4-..."

Retry

Full-jitter exponential backoff: delay = random(0, min(base × 2^attempt, max))

new CheckoutClient({
  retry: {
    maxAttempts: 4, // 1 initial + 3 retries
    baseDelayMs: 500,
    maxDelayMs: 30_000,
  },
});

Retried: 429, 5xx, network errors. Not retried: 4xx except 429, 409 Conflict.


AbortSignal

Every service method accepts { signal } as its last options argument:

const controller = new AbortController();
setTimeout(() => controller.abort(), 5_000);

const payment = await client.payments.request(
  { amount: 10_000, currency: "GBP" },
  { signal: controller.signal },
);

AWS PrivateLink

new CheckoutClient({ prefix: "vkuhvk4v", privateLink: true });
// → https://pl-vkuhvk4v.api.checkout.com

Webhook verification

import {
  verifyWebhook,
  InvalidWebhookSignatureError,
} from "checkout-client/webhook";

app.post("/webhooks/checkout", express.raw({ type: "*/*" }), (req, res) => {
  try {
    const event = verifyWebhook(
      req.headers["cko-signature"],
      req.body, // Buffer or string
      process.env.CKO_WEBHOOK_KEY!,
    );
    console.log(event.type, event.id);
    res.sendStatus(200);
  } catch (err) {
    if (err instanceof InvalidWebhookSignatureError) {
      res.sendStatus(403);
    } else {
      res.sendStatus(500);
    }
  }
});

Or import directly from the root:

import { verifyWebhook, InvalidWebhookSignatureError } from "checkout-client";

Custom fetch (testing, proxies, OpenTelemetry)

// Inject a custom fetch for testing
import { CheckoutClient } from "checkout-client";

const client = new CheckoutClient({
  prefix: "testpfx1",
  secretKey: "sk_test",
  fetch: async (url, init) => {
    console.log("→", url);
    return globalThis.fetch(url, init);
  },
});

Deep imports

Each sub-module is independently importable with full tree-shaking support:

import type { PaymentRequest, PaymentResponse } from "checkout-client/payments";
import type { FXRatesResponse } from "checkout-client/vault";
import type { Dispute } from "checkout-client/disputes";
import type { CardResponse } from "checkout-client/issuing";
import { verifyWebhook } from "checkout-client/webhook";
import { CheckoutError } from "checkout-client/errors";

Complete API

Namespace Methods
client.payments request, list, get, getActions, incrementAuthorization, cancelRetry, capture, refund, reverse, void, search
client.flow createSession, submitSession, createAndSubmit
client.paymentLinks create, get
client.hostedPayments create, get
client.paymentContexts request, get
client.paymentSetups create, update, get, confirm
client.paymentMethods list
client.tokens create
client.instruments create, get, update, delete, getBankAccountFieldFormatting
client.customers create, get, update, delete
client.transfers create, get
client.balances get
client.forex getRates
client.cardMetadata get
client.networkTokens provision, get, requestCryptogram, delete
client.accountUpdater getUpdatedCredentials
client.applePay generateCSR, uploadCertificate, enrollDomain
client.googlePay enrollEntity, registerDomain, getRegisteredDomains, getEnrollmentState
client.forward sendRequest, getRequest, createSecret, listSecrets, updateSecret, deleteSecret
client.sessions create, get, update, complete, update3DSMethodCompletion
client.compliance get, respond
client.agenticCommerce createDelegatedToken
client.reports list, get, getFile
client.financialActions list
client.disputes list, get, accept, provideEvidence, getEvidence, submitEvidence, submitArbitrationEvidence, getSubmittedArbitrationEvidence, getSubmittedEvidence, getSchemeFiles, uploadFile, getFile
client.workflows list, create, get, patch, delete, addAction, updateAction, removeAction, addCondition, updateCondition, removeCondition, test, getEventTypes, getEvent, getActionInvocations, getSubjectEvents, reflowByEvent, reflowByEventAndWorkflow, reflow, reflowBySubject, reflowBySubjectAndWorkflow
client.cardholders create, get, update, listCards
client.cardholderAccessTokens request
client.cards create, get, update, getCredentials, activate, suspend, revoke, renew, scheduleRevocation, deleteScheduledRevocation, enrol3DS, get3DSEnrollment, update3DSEnrollment
client.controls create, listByTarget, get, update, delete
client.controlProfiles create, list, get, update, delete, addTarget, removeTarget
client.controlGroups create, listByTarget, get, delete
client.issuingTransactions list, get
client.issuingDisputes create, get, cancel, escalate
client.issuingSandbox simulateAuthorization, simulateIncrementalAuthorization, simulateClearing, simulateRefund, simulateReversal
client.platformEntities onboard, get, update, uploadFile, getFile, listMembers, reinviteMember
client.platformInstruments add, get, update, list
client.payoutSchedules get, update
client.reserveRules add, get, update, list
client.identityApplicants create, get, update, anonymize
client.identityVerification createAndStart, create, get, anonymize, createAttempt, listAttempts, getAttempt, getReport
client.amlScreening create, get
client.faceAuth create, get, anonymize, createAttempt, listAttempts, getAttempt
client.idDocuments create, get, anonymize, createAttempt, listAttempts, getAttempt, getReport

Testing

npm test              # vitest run
npm run test:watch    # vitest watch mode
npm run test:coverage # coverage report

All 68 tests use mock fetch — no live API calls, no credentials needed.


Building

npm run build   # ESM + CJS + type declarations
npm run clean   # remove dist/

License

MIT — see LICENSE.

About

Unofficial Production-grade TypeScript client for the Checkout.com API

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors