sumup

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 7, 2026 License: MIT Imports: 19 Imported by: 0

README

sumup-go

Go Reference CI Go Report Card License

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

Why this exists

SumUp doesn't publish an official Go SDK. This package fills that gap, modeling the API's real shape rather than papering over it:

  • Zero dependencies. Built entirely on the standard library (net/http, encoding/json, context, iter) — nothing to audit in your supply chain beyond this package and Go itself.
  • Idiomatic error handling. Every method returns a plain error; recover a typed *sumup.Error with errors.As for API/transport failures (unifying SumUp's two real response shapes — RFC 9457 Problem Details and its legacy {error_code, message, param} format), or a *sumup.ValidationError for arguments that failed local validation.
  • context.Context everywhere. Every method takes a context.Context as its first argument, for cancellation and deadlines the way Go code expects.
  • Go 1.23 range-over-func iterators for pagination. ListAll methods return iter.Seq2[T, error], so a plain for tx, err := range client.Transactions.ListAll(...) loop just works — with early-exit support baked in.
  • Two money types, 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 returns 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

go get github.com/iamkanishka/sumup-go

Requires Go 1.23+ (for iter.Seq2-based pagination).

Quick start

package main

import (
	"context"
	"log"
	"os"

	sumup "github.com/iamkanishka/sumup-go"
	"github.com/iamkanishka/sumup-go/checkouts"
)

func main() {
	client, err := sumup.NewClient(sumup.WithAPIKey(os.Getenv("SUMUP_API_KEY")))
	if err != nil {
		log.Fatal(err)
	}

	ctx := context.Background()

	checkout, err := client.Checkouts.Create(ctx, checkouts.CreateCheckoutParams{
		CheckoutReference: "order-1234",
		Amount:            10.50,
		Currency:          "EUR",
		MerchantCode:      "MC12345",
	})
	if err != nil {
		log.Fatal(err)
	}

	paid, err := client.Checkouts.Process(ctx, checkout.ID, checkouts.ProcessCheckoutParams{
		PaymentType: "card",
		Card: &checkouts.CardDetails{
			Name: "Jane Doe", Number: "4111111111111111",
			ExpiryMonth: "12", ExpiryYear: "2030", CVV: "123",
		},
	})
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("checkout %s is now %s", paid.ID, paid.Status)
}

Package structure

The SDK is organized by domain, one bounded context per subpackage, with the root sumup package as the composition root that wires them together:

sumup/                    Client, NewClient, Option — the composition root
├── checkouts/            Checkouts resource
├── customers/            Customers resource
├── paymentinstruments/   Payment Instruments resource
├── transactions/         Transactions resource
├── payouts/               Payouts resource
├── receipts/              Receipts resource
├── members/               Members resource
├── memberships/           Memberships resource (the current user's own memberships)
├── roles/                  Roles resource
├── merchants/              Merchants resource (read-only)
├── webhooks/                Webhooks (depends on checkouts, to re-verify events)
├── money/                    Shared money value objects (FloatMoney, MinorUnitMoney)
├── pagination/                Shared range-over-func pagination primitive
├── tracing/                    Shared request-lifecycle observability hooks
├── shared/                      Cross-domain value objects (e.g. PersonalDetails)
├── apierror/                     The normalized Error / ValidationError types
└── internal/                      Transport engine, decode/validation/wire helpers (not part of the public API)

Every resource is exposed as a field on Client (Checkouts, Readers, Customers, ...), typed to its own domain package — e.g. client.Checkouts.Create takes a checkouts.CreateCheckoutParams and returns a *checkouts.Checkout. The root package re-exports the small set of cross-cutting shared-kernel types you'll touch constantly (sumup.Error, sumup.ValidationError, sumup.FloatMoney, sumup.Trace, sumup.WithTrace, ...) as aliases, so common usage doesn't require importing apierror, money, or tracing directly.

Configuration

NewClient uses the functional-options pattern:

client, err := sumup.NewClient(
	sumup.WithAPIKey(os.Getenv("SUMUP_API_KEY")), // or WithAccessToken for OAuth2
	sumup.WithMaxRetries(5),
	sumup.WithReceiveTimeout(15*time.Second),
)

See the Option docs for the full list (base URL override for testing, retry/backoff tuning, a custom *http.Client, extra headers, etc).

Resources

Field 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 iterator, 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 returns a plain error. Use errors.As to recover a typed *sumup.Error (API/transport failures) or *sumup.ValidationError (bad arguments, caught before any request is sent):

_, err := client.Checkouts.Process(ctx, checkoutID, params)
if err != nil {
	var sumErr *sumup.Error
	if errors.As(err, &sumErr) && sumErr.Code == "CARD_DECLINED" {
		return fmt.Errorf("card declined: %s", sumErr.Message)
	}
	return err
}

*sumup.Error exposes a consistent set of fields (Status, Code, Message, Param, Type, Title, Instance, Errors, Raw) no matter which underlying shape the API returned, and supports errors.Is/errors.Unwrap against the underlying transport error for network failures.

Retries and tracing

429 and 5xx responses (and transport failures) are retried with exponential backoff and jitter, honoring a numeric retry-after header when SumUp sends one. Attach a *sumup.Trace to a context — in the same spirit as the standard library's net/http/httptrace.ClientTrace — to observe every request's lifecycle without modifying this library:

ctx := sumup.WithTrace(context.Background(), &sumup.Trace{
	OnRequestSuccess: func(info sumup.RequestSuccessInfo) {
		log.Printf("sumup %s.%s -> %d (%dms)", info.Resource, info.Operation, info.Status, info.DurationMS)
	},
	OnRequestRetry: func(info sumup.RequestRetryInfo) {
		log.Printf("sumup %s.%s retrying (attempt %d, waiting %dms)", info.Resource, info.Operation, info.Attempt, info.DelayMS)
	},
})

checkout, err := client.Checkouts.Get(ctx, "chk_123")

Streaming large result sets

client.Transactions.ListAll and client.Members.ListAll return Go 1.23 range-over-func iterators (iter.Seq2[T, error]), so a plain for ... range loop handles pagination for you and stops fetching further pages the moment you break:

for tx, err := range client.Transactions.ListAll(ctx, "M1", transactions.ListTransactionsParams{
	Statuses: []string{"SUCCESSFUL"},
}) {
	if err != nil {
		log.Fatal(err)
	}
	if tx.Amount.Amount > 100 {
		fmt.Println(tx.ID, tx.Amount.Amount)
	}
}

Or collect everything into a slice with the pagination.Collect helper:

members, err := pagination.Collect(client.Members.ListAll(ctx, "M1", 0))

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 package 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:

func handleWebhook(w http.ResponseWriter, r *http.Request) {
	body, _ := io.ReadAll(r.Body)

	checkout, err := client.Webhooks.Handle(r.Context(), body)
	switch {
	case err == nil:
		handleCheckoutUpdate(checkout)
	case errors.Is(err, webhooks.ErrUnsupportedWebhookEventType):
		// a future event type this version predates — ignore it, per SumUp's guidance
	default:
		log.Printf("webhook handling failed: %v", err)
	}

	// Always ack quickly with 2xx, however processing went — SumUp
	// retries non-2xx deliveries at 1 min, 5 min, 20 min, and 2 hours.
	w.WriteHeader(http.StatusOK)
}

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 net/http/httptest.Server (see testing_helpers_test.go) rather than a mocking library — inject your own base URL via WithBaseURL:

server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	// respond however you like
}))
defer server.Close()

client, _ := sumup.NewClient(sumup.WithAPIKey("sk_test_123"), sumup.WithBaseURL(server.URL))

Development

go build ./...
go vet ./...
gofmt -l .          # should print nothing
golangci-lint run ./...
go test -race -cover ./...

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[], payment_types[], entry_modes[], 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 file follows the same pattern, so adding or correcting an endpoint is usually a small, self-contained change.

License

MIT. See LICENSE.

Documentation

Overview

Package sumup is a production-grade, fully-typed Go client for the SumUp API (https://developer.sumup.com/api).

Structure

The SDK is organized by domain, one bounded context per subpackage: checkouts, customers, paymentinstruments, transactions, payouts, receipts, members, memberships, roles, merchants, and webhooks. This package is the composition root — it owns the shared HTTP engine and wires up one Service per domain package, exposed as a field on Client.

Quick start

client, err := sumup.NewClient(sumup.WithAPIKey(os.Getenv("SUMUP_API_KEY")))
if err != nil {
	log.Fatal(err)
}

checkout, err := client.Checkouts.Create(ctx, checkouts.CreateCheckoutParams{
	CheckoutReference: "order-1234",
	Amount:            10.50,
	Currency:          "EUR",
	MerchantCode:      "MC12345",
})

Every resource is exposed as a field on Client (Checkouts, Readers, Customers, ...), typed to its own domain package. Every method takes a context.Context as its first argument and returns a plain Go error — use errors.As to recover a typed *Error for API/transport failures, or a *ValidationError for arguments that failed local validation before any request was sent.

Index

Constants

View Source
const (
	ErrorSourceUnknown    = apierror.ErrorSourceUnknown
	ErrorSourceProblem    = apierror.ErrorSourceProblem
	ErrorSourceLegacy     = apierror.ErrorSourceLegacy
	ErrorSourceLegacyList = apierror.ErrorSourceLegacyList
	ErrorSourceTransport  = apierror.ErrorSourceTransport
)

ErrorSource values. See the equivalent constants in apierror.

Variables

View Source
var ExponentFor = money.ExponentFor

ExponentFor returns the ISO 4217 decimal exponent for a currency code. See money.ExponentFor.

View Source
var IsZeroDecimalCurrency = money.IsZeroDecimalCurrency

IsZeroDecimalCurrency reports whether a currency has no minor unit (e.g. JPY, KRW).

View Source
var NewMinorUnitMoneyFromDecimal = money.NewMinorUnitMoneyFromDecimal

NewMinorUnitMoneyFromDecimal builds a MinorUnitMoney from a major-unit decimal amount.

View Source
var WithTrace = tracing.WithTrace

WithTrace attaches a Trace to ctx; every client method called with the resulting context reports its lifecycle events to trace's callbacks. See tracing.WithTrace.

Functions

This section is empty.

Types

type Client

type Client struct {
	Checkouts          *checkouts.Service
	Readers            *readers.Service
	Customers          *customers.Service
	PaymentInstruments *paymentinstruments.Service
	Transactions       *transactions.Service
	Payouts            *payouts.Service
	Receipts           *receipts.Service
	Members            *members.Service
	Memberships        *memberships.Service
	Roles              *roles.Service
	Merchants          *merchants.Service
	Webhooks           *webhooks.Service
	// contains filtered or unexported fields
}

Client is the main entry point for the SumUp SDK. Construct one per credential set with NewClient and reuse it — it is safe for concurrent use by multiple goroutines.

func NewClient

func NewClient(opts ...Option) (*Client, error)

NewClient builds a Client. Requires exactly one of WithAPIKey or WithAccessToken.

type Error

type Error = apierror.Error

Error is the normalized SumUp API/transport error type. See apierror.Error.

type ErrorSource

type ErrorSource = apierror.ErrorSource

ErrorSource identifies which of SumUp's response shapes an Error was decoded from. See apierror.ErrorSource.

type FloatMoney

type FloatMoney = money.FloatMoney

FloatMoney is SumUp's legacy major-unit money shape. See money.FloatMoney.

type MinorUnitMoney

type MinorUnitMoney = money.MinorUnitMoney

MinorUnitMoney is SumUp's minor-unit money shape, used by Readers. See money.MinorUnitMoney.

type Option

type Option func(*clientConfig) error

Option configures a Client. Pass one or more to NewClient.

func WithAPIKey

func WithAPIKey(key string) Option

WithAPIKey sets the SumUp secret API key (sk_live_... / sk_test_...), or a restricted key. Mutually exclusive with WithAccessToken — if both are given, the one applied last wins.

func WithAccessToken

func WithAccessToken(token string) Option

WithAccessToken sets an OAuth2 access token, as an alternative to WithAPIKey.

func WithBaseURL

func WithBaseURL(baseURL string) Option

WithBaseURL overrides the API base URL. Useful for testing against a mock server.

func WithHTTPClient

func WithHTTPClient(httpClient *http.Client) Option

WithHTTPClient supplies a custom *http.Client (e.g. one with a custom Transport for proxying, mTLS, or test interception). Its Timeout is left untouched by this package — set WithReceiveTimeout to control the per-request timeout that this package applies via context, or set Timeout on the client yourself.

func WithHeader

func WithHeader(key, value string) Option

WithHeader adds an extra header sent with every request.

func WithMaxRetries

func WithMaxRetries(n int) Option

WithMaxRetries sets the max retry attempts for retryable errors (429 and 5xx, and transport failures). Default: 3. Pass 0 to disable retries.

func WithReceiveTimeout

func WithReceiveTimeout(d time.Duration) Option

WithReceiveTimeout sets the per-request timeout applied via context. Default: 30s.

func WithRetryBaseDelay

func WithRetryBaseDelay(d time.Duration) Option

WithRetryBaseDelay sets the base delay for exponential backoff (doubles per attempt, with jitter). Default: 250ms.

func WithRetryMaxDelay

func WithRetryMaxDelay(d time.Duration) Option

WithRetryMaxDelay sets the maximum backoff delay. Default: 5s.

func WithUserAgent

func WithUserAgent(userAgent string) Option

WithUserAgent overrides the User-Agent header.

type RequestErrorInfo

type RequestErrorInfo = tracing.RequestErrorInfo

RequestErrorInfo is passed to [Trace.OnRequestError]. See tracing.RequestErrorInfo.

type RequestInfo

type RequestInfo = tracing.RequestInfo

RequestInfo carries metadata common to every trace event. See tracing.RequestInfo.

type RequestRetryInfo

type RequestRetryInfo = tracing.RequestRetryInfo

RequestRetryInfo is passed to [Trace.OnRequestRetry]. See tracing.RequestRetryInfo.

type RequestSuccessInfo

type RequestSuccessInfo = tracing.RequestSuccessInfo

RequestSuccessInfo is passed to [Trace.OnRequestSuccess]. See tracing.RequestSuccessInfo.

type Trace

type Trace = tracing.Trace

Trace holds request-lifecycle callbacks. See tracing.Trace.

type ValidationError

type ValidationError = apierror.ValidationError

ValidationError is returned when caller-supplied parameters fail local validation before any request is sent. See apierror.ValidationError.

Directories

Path Synopsis
Package apierror holds the normalized error types returned by every resource in the SumUp SDK (Error and ValidationError), plus the decoding logic that builds an Error from an HTTP response or a transport-level failure.
Package apierror holds the normalized error types returned by every resource in the SumUp SDK (Error and ValidationError), plus the decoding logic that builds an Error from an HTTP response or a transport-level failure.
Package checkouts handles the Checkouts resource: create a payment intent, then process it with a card, saved token, or an alternative payment method (Boleto, iDEAL, Bancontact, Blik, Google Pay, Apple Pay).
Package checkouts handles the Checkouts resource: create a payment intent, then process it with a card, saved token, or an alternative payment method (Boleto, iDEAL, Bancontact, Blik, Google Pay, Apple Pay).
Package customers handles the Customers resource: a lightweight vault for storing payer identity and reusable payment instruments.
Package customers handles the Customers resource: a lightweight vault for storing payer identity and reusable payment instruments.
internal
decode
Package decode provides small, panic-free type assertion helpers for pulling typed Go values out of a loosely-typed map[string]any decoded from a JSON response body.
Package decode provides small, panic-free type assertion helpers for pulling typed Go values out of a loosely-typed map[string]any decoded from a JSON response body.
transport
Package transport is the low-level HTTP engine behind every domain package's Service: it owns the *http.Client, base URL, auth header, and retry policy, and turns a RequestParams into a RequestResult or a normalized apierror.Error, reporting lifecycle events to any tracing.Trace attached to the request context.
Package transport is the low-level HTTP engine behind every domain package's Service: it owns the *http.Client, base URL, auth header, and retry policy, and turns a RequestParams into a RequestResult or a normalized apierror.Error, reporting lifecycle events to any tracing.Trace attached to the request context.
transporttest
Package transporttest provides a small httptest-backed harness for exercising a transport.Client (and, by extension, any domain package's Service) against a fake server, without pulling in the root sumup package.
Package transporttest provides a small httptest-backed harness for exercising a transport.Client (and, by extension, any domain package's Service) against a fake server, without pulling in the root sumup package.
validation
Package validation provides small parameter-validation helpers shared by every domain package's Params.validate methods.
Package validation provides small parameter-validation helpers shared by every domain package's Params.validate methods.
wire
Package wire provides small helpers for building outgoing request bodies and query strings, used by every domain package's Params.wire and Params.query methods.
Package wire provides small helpers for building outgoing request bodies and query strings, used by every domain package's Params.wire and Params.query methods.
Package members handles the Members resource: user accounts under a merchant, including virtual/managed users.
Package members handles the Members resource: user accounts under a merchant, including virtual/managed users.
Package memberships handles the Memberships resource: lists the currently authenticated user's memberships across merchants/organizations, optionally filtered by resource type, role, status, or parent.
Package memberships handles the Memberships resource: lists the currently authenticated user's memberships across merchants/organizations, optionally filtered by resource type, role, status, or parent.
Package merchants handles the Merchants resource: legal entity and KYC profile data.
Package merchants handles the Merchants resource: legal entity and KYC profile data.
Package money holds the two money value objects used across SumUp's API — FloatMoney (legacy major-unit decimal amounts) and MinorUnitMoney (the newer, numerically-safe minor-unit representation) — plus currency-exponent helpers shared by every domain package that decodes or encodes an amount.
Package money holds the two money value objects used across SumUp's API — FloatMoney (legacy major-unit decimal amounts) and MinorUnitMoney (the newer, numerically-safe minor-unit representation) — plus currency-exponent helpers shared by every domain package that decodes or encodes an amount.
Package pagination provides the generic, range-over-func pagination primitive (Paginate) that every domain package's ListAll-style method (e.g.
Package pagination provides the generic, range-over-func pagination primitive (Paginate) that every domain package's ListAll-style method (e.g.
Package paymentinstruments handles the Payment Instruments resource: tokenized cards saved against a customer, created as a side effect of a checkouts.Service.Process call with Purpose "SETUP_RECURRING_PAYMENT" (or a regular charge that opts to save the method).
Package paymentinstruments handles the Payment Instruments resource: tokenized cards saved against a customer, created as a side effect of a checkouts.Service.Process call with Purpose "SETUP_RECURRING_PAYMENT" (or a regular charge that opts to save the method).
Package payouts handles the Payouts resource: a date-ranged report of funds settled to (or deducted from) a merchant's bank account.
Package payouts handles the Payouts resource: a date-ranged report of funds settled to (or deducted from) a merchant's bank account.
Package readers handles the Readers resource: pair, manage, and push payments to physical SumUp Solo card readers.
Package readers handles the Readers resource: pair, manage, and push payments to physical SumUp Solo card readers.
Package receipts handles the Receipts resource: a detailed, receipt-formatted view of a transaction, keyed by transaction id plus the merchant id (MID).
Package receipts handles the Receipts resource: a detailed, receipt-formatted view of a transaction, keyed by transaction id plus the merchant id (MID).
Package roles handles the Roles resource: role definitions (built-in and custom) grouping permissions, assignable to Members.
Package roles handles the Roles resource: role definitions (built-in and custom) grouping permissions, assignable to Members.
Package shared holds small value objects that cross bounded-context boundaries — used by more than one domain package — so that neither domain package needs to import the other just to share a struct shape.
Package shared holds small value objects that cross bounded-context boundaries — used by more than one domain package — so that neither domain package needs to import the other just to share a struct shape.
Package tracing provides request-lifecycle observability hooks (Trace) that the internal transport layer reports to, attached to a context with WithTrace.
Package tracing provides request-lifecycle observability hooks (Trace) that the internal transport layer reports to, attached to a context with WithTrace.
Package transactions handles the Transactions resource: read-heavy history and lookup endpoints (API version v2.1), plus refunds (API version v1.0).
Package transactions handles the Transactions resource: read-heavy history and lookup endpoints (API version v2.1), plus refunds (API version v1.0).
Package webhooks handles SumUp's online-payments webhooks.
Package webhooks handles SumUp's online-payments webhooks.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL