Skip to content

Repository files navigation

sumup-client

npm CI License TypeScript

A production-grade, fully-typed TypeScript client for the SumUp API — Checkouts, Readers, Customers, Payment Instruments, Transactions, Payouts, Receipts, Members, Memberships, Roles, and Merchants.

Why this exists

SumUp publishes official SDKs for Node, Go, Python, Java, .NET, PHP and Rust — but the Node one is a thin, loosely-typed wrapper. This package takes a stricter approach, modeling the API's real shape rather than papering over it:

  • Zero runtime dependencies. Built entirely on native fetch, Headers, URL, and AbortController — no axios, no request library, nothing to audit in your supply chain beyond this package itself.
  • Two error formats, normalized into one. Legacy resources (Checkouts, Customers, Transactions, Payouts) return {"error_code", "message", "param"} (sometimes as a list); newer resources (Readers, Members, Merchants) and every 401 return RFC 9457 Problem Details. SumUpError decodes both into one class, thrown so you get idiomatic try/catch or .catch() handling.
  • Two money formats, kept distinct. Legacy endpoints represent money as a float major-unit amount (FloatMoney); Readers use an integer minor-unit MinorUnitMoney. They're deliberately not unified into one type — doing so would either lose precision or require guessing a currency's decimal exponent.
  • Per-resource API versioning. Each resource method hits whatever version SumUp actually ships for it (v0.1 for Checkouts, v2.1 for Transaction reads but v1.0 for refunds, v1 for Merchants, etc.) rather than assuming one version for the whole client.
  • Reader checkouts modeled as genuinely async. Pushing a payment to a physical Solo reader resolves as soon as it's been sent to the device; this library returns that intermediate state honestly instead of faking synchronicity with an internal poll loop.

Installation

npm install sumup-client

Requires Node.js >= 18.17 (for global fetch), or any runtime that provides one (Bun, Deno, Cloudflare Workers, modern browsers).

Quick start

import { SumUpClient } from "sumup-client";

const client = new SumUpClient({ apiKey: process.env.SUMUP_API_KEY });

const checkout = await client.checkouts.create({
  checkoutReference: "order-1234",
  amount: 10.5,
  currency: "EUR",
  merchantCode: "MC12345",
});

const paid = await client.checkouts.process(checkout.id, {
  paymentType: "card",
  card: {
    name: "Jane Doe",
    number: "4111111111111111",
    expiryMonth: "12",
    expiryYear: "2030",
    cvv: "123",
  },
});

Configuration

const client = new SumUpClient({
  apiKey: process.env.SUMUP_API_KEY, // or accessToken for OAuth2
  maxRetries: 5,
  receiveTimeoutMs: 15_000,
});

See SumUpClientOptions in the type definitions for the full option list (base URL override for testing, retry/backoff tuning, a custom fetch implementation, extra headers, etc).

Resources

Property Covers
client.checkouts create, get, find by reference, process (card/token/APM), deactivate, Apple Pay sessions, list payment methods
client.readers pair, list, get (with If-Modified-Since support), get live status, rename/update, delete, push a checkout, terminate
client.customers create, get, update
client.paymentInstruments list, deactivate
client.transactions get by id/code/foreign id/client id, list (with bracketed array filters), listAll() async-generator stream, refund
client.payouts date-ranged report, JSON (list) or CSV (listCsv)
client.receipts detailed receipt lookup
client.members create, get, list, listAll(), update (full replace via PUT), delete
client.memberships list the current authenticated user's own memberships (no merchant/user id in the path)
client.roles create, list, get, update, delete custom roles
client.merchants get profile (version + change-status pattern), list/get persons — read-only, SumUp's public API has no merchant-update endpoint

client.webhooks isn't a REST resource — see Webhooks below.

Error handling

Every method throws a SumUpError (API/transport failures) or a SumUpValidationError (bad arguments, caught before any request is sent). SumUpError exposes a consistent set of fields (status, code, message, param, type, title, instance, errors, raw) no matter which underlying shape the API returned:

try {
  await client.checkouts.process(checkoutId, { paymentType: "card", card });
} catch (error) {
  if (error instanceof SumUpError && error.code === "CARD_DECLINED") {
    return { declined: true, reason: error.message };
  }
  throw error;
}

Retries and telemetry

429 and 5xx responses (and transport failures) are retried with exponential backoff and jitter, honoring a numeric retry-after header when SumUp sends one. Every request emits typed telemetry events you can subscribe to without modifying this library:

client.on("request:success", ({ resource, operation, status, durationMs }) => {
  logger.info(`sumup ${resource}.${operation} -> ${status} (${durationMs}ms)`);
});

client.on("request:retry", ({ resource, operation, attempt, delayMs }) => {
  logger.warn(`sumup ${resource}.${operation} retrying (attempt ${attempt}, waiting ${delayMs}ms)`);
});

Streaming large result sets

client.transactions.listAll() and client.members.listAll() return async generators built on a generic paginate() helper, so you can use a plain for await loop and stop whenever you like without fetching more pages than you need:

for await (const tx of client.transactions.listAll("M1", { statuses: ["SUCCESSFUL"] })) {
  if (tx.amount && tx.amount.amount > 100) {
    console.log(tx.id, tx.amount.amount);
  }
}

Or collect everything into an array with the collect() helper:

import { collect } from "sumup-client";

const allMembers = await collect(client.members.listAll("M1"));

Webhooks

SumUp's Checkout webhooks are not signed — there's no HMAC or signature header to check for this API (that's a different SumUp product entirely; see the Webhooks class doc comment for the full explanation). SumUp's own recommendation is to treat the webhook body as an unauthenticated pointer and re-fetch the real state from the API:

app.post("/webhooks/sumup", express.text({ type: "*/*" }), async (req, res) => {
  try {
    const checkout = await client.webhooks.handle(req.body);
    await handleCheckoutUpdate(checkout);
  } catch {
    // still ack below, even on a parse/lookup failure
  }

  // Always ack quickly with 2xx, however processing went — SumUp retries
  // non-2xx deliveries at 1 min, 5 min, 20 min, and 2 hours.
  res.sendStatus(200);
});

client.webhooks.parse() decodes the payload without trusting it; client.webhooks.verify() does the recommended re-fetch; client.webhooks.handle() does both in one call, as above.

Testing your own integration

This library's own test suite uses a small hand-rolled fetch stub (see test/support/mock-fetch.ts) rather than a mocking library — inject your own via the fetch client option:

const client = new SumUpClient({
  apiKey: "sk_test_123",
  fetch: async (url, init) => {
    // return a Response however you like
    return new Response(JSON.stringify({ id: "chk_1", status: "PAID" }), {
      headers: { "content-type": "application/json" },
    });
  },
});

Development

npm install
npm run ci   # format check + lint + typecheck + test with coverage + build

Individual scripts: npm run format, npm run lint, npm run typecheck, npm test, npm run test:coverage, npm run build, npm run docs (generates API docs into docs/ via TypeDoc).

A note on endpoint coverage

Every path, parameter, and response field in this library was cross-checked against SumUp's published OpenAPI spec — including some easy-to-miss details it corrects for: client.memberships has no merchant/user id in its path (it's always the calling user's own memberships), client.transactions.refund() is scoped under /merchants/{merchantCode}/payments/{id}/refunds and returns no body, client.merchants has no update method in the public API, reader checkouts use tipRates/tipTimeout rather than a flat tipAmount, and transaction history's array filters (statuses[], paymentTypes[], entryModes[], types[]) are sent as repeated bracketed query keys, not comma-joined values. SumUp does still evolve its API surface over time, so if you hit a mismatch, please open an issue or PR — every resource module follows the same pattern, so adding or correcting an endpoint is usually a small, self-contained change.

License

MIT. See LICENSE.

About

A production-grade, fully-typed TypeScript client for the [SumUp API](https://developer.sumup.com/api) — Checkouts, Readers, Customers, Payment Instruments, Transactions, Payouts, Receipts, Members, Memberships, Roles, and Merchants.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages