mercurygo

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Jul 21, 2026 License: MIT Imports: 1 Imported by: 0

README

mercury-go

Production-grade Go client for the Mercury Banking API.

Go Reference Zero dependencies Tests

Features

  • Full API coverage — all 59 Mercury endpoints across 16 resource services
  • Zero runtime dependencies — standard library only (net/http, encoding/json, context)
  • Idiomatic Gocontext.Context on every call, typed errors with errors.As/errors.Is support, functional options
  • Generics-based paginationPages, Items, Collect, CollectN work uniformly across every resource
  • Exponential backoff retries — automatic retry with jitter on 429/5xx; 400/401/404/409 fail immediately
  • Rich, inspectable errorsIsAuthError, IsNotFoundError, IsRateLimitError, etc.
  • Request/response hooks — plug in your own logger, tracer, or metrics exporter
  • Thread-safe — a single *Client can be shared and reused across goroutines

Installation

go get github.com/iamkanishka/mercury-go

Requires Go 1.22 or later.

The canonical, fully-documented implementation lives in the mercury subpackage. A thin root-level package re-exports the most common entry points (NewClient, Option, Ptr, the Is*Error helpers) so either import path works identically:

import "github.com/iamkanishka/mercury-go/mercury" // canonical
// or
import mercury "github.com/iamkanishka/mercury-go" // flat alias, same Client

Anything not re-exported at the root (individual resource types, less common options) is available by importing the mercury subpackage directly.


Quick start

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/iamkanishka/mercury-go/mercury"
)

func main() {
	client := mercury.NewClient("secret-token:mercury_production_...")
	ctx := context.Background()

	account, err := client.Accounts.Get(ctx, "account-id")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s: $%.2f\n", account.Name, account.AvailableBalance)

	// Auto-paginate every transaction
	all, err := client.Transactions.Collect(ctx, &mercury.ListTransactionsParams{
		Status: []mercury.TransactionStatus{mercury.TransactionStatusSent},
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Found %d sent transactions\n", len(all))
}

Client options

client := mercury.NewClient(
	"secret-token:...",
	mercury.WithBaseURL("https://sandbox.mercury.com/api/v1"), // override for testing
	mercury.WithTimeout(15 * time.Second),                     // default: 30s
	mercury.WithRetry(mercury.RetryConfig{
		MaxRetries:   5,
		InitialDelay: 250 * time.Millisecond,
		MaxDelay:     20 * time.Second,
		JitterFactor: 0.3,
	}),
	mercury.WithHTTPClient(customHTTPClient), // bring your own *http.Client / transport
	mercury.WithRequestHook(func(method, url, requestID string) {
		log.Printf("→ %s %s [%s]", method, url, requestID)
	}),
	mercury.WithResponseHook(func(method, url, requestID string, status int, d time.Duration) {
		log.Printf("← %d in %s [%s]", status, d, requestID)
	}),
)

Resources

Every resource is a field on *mercury.Client:

Field Service
client.Accounts accounts, cards, statements, transactions, send money
client.Transactions get, list, update, upload attachment
client.Recipients full CRUD, attachments
client.Invoices full CRUD, cancel, PDF download, attachments
client.Payments internal transfers, send-money approval requests
client.Categories full CRUD
client.Customers full CRUD
client.Treasury accounts, transactions, statements
client.Webhooks full CRUD, verify
client.Events get, list, collect
client.Organization get, submit onboarding
client.Users get, list, collect
client.SAFERequests get, list, download document
client.Credit list
client.Attachments get
client.OAuth2 authorize URL, code exchange
Example: send money
txn, err := client.Accounts.SendMoney(ctx, accountID, &mercury.SendMoneyRequest{
	RecipientID:    recipientID,
	Amount:         500.00,
	PaymentMethod:  mercury.PostTransactionPaymentMethodACH,
	IdempotencyKey: uuid.NewString(),
	Note:           mercury.Ptr("Invoice #1234"),
})
Example: domestic wire with required purpose
txn, err := client.Accounts.SendMoney(ctx, accountID, &mercury.SendMoneyRequest{
	RecipientID:    recipientID,
	Amount:         10000.00,
	PaymentMethod:  mercury.PostTransactionPaymentMethodDomesticWire,
	IdempotencyKey: uuid.NewString(),
	Purpose: &mercury.SendMoneyPurpose{
		Simple: &mercury.SimplePurpose{
			Category:       mercury.WirePurposeCategoryVendor,
			AdditionalInfo: mercury.Ptr("Acme Supplies Inc"),
		},
	},
})
Example: internal transfer (returns both legs)
result, err := client.Payments.CreateInternalTransfer(ctx, &mercury.InternalTransferRequest{
	SourceAccountID:      checkingID,
	DestinationAccountID: savingsID,
	Amount:               5000.00,
	IdempotencyKey:       uuid.NewString(),
})
// result.DebitTransaction  — the outgoing leg
// result.CreditTransaction — the incoming leg
Example: create a recipient with ACH routing
recipient, err := client.Recipients.Create(ctx, &mercury.CreateRecipientRequest{
	Name:   "Acme Supplies",
	Emails: []string{"billing@acmesupplies.com"},
	ElectronicRoutingInfo: &mercury.ElectronicRoutingInfoInput{
		AccountNumber:         "0123456789",
		RoutingNumber:         "021000021",
		ElectronicAccountType: mercury.ElectronicAccountTypeBusinessChecking,
		Address: mercury.AddressWithoutName{
			Address1:   "123 Main St",
			City:       "San Francisco",
			Region:     "CA",
			PostalCode: "94105",
			Country:    "US",
		},
	},
})

Pagination

Every list-capable resource exposes the same three-tier pattern, powered by generics:

// 1. Single page — manual cursor control
page1, _ := client.Recipients.List(ctx, &mercury.ListParams{Limit: mercury.Ptr(25)})
page2, _ := client.Recipients.List(ctx, &mercury.ListParams{StartAfter: page1.Page.NextPage})

// 2. Channel-based streaming — memory-efficient for large datasets.
// client.Recipients.Pages(params) returns a mercury.PageFetcher[Recipient];
// pass it to mercury.Pages or mercury.Items to get a streaming channel.
for result := range mercury.Pages(ctx, client.Recipients.Pages(nil)) {
	if result.Error != nil {
		log.Fatal(result.Error)
	}
	for _, r := range result.Page.Items {
		fmt.Println(r.Name)
	}
}

for result := range mercury.Items(ctx, client.Recipients.Pages(nil)) {
	if result.Error != nil {
		log.Fatal(result.Error)
	}
	fmt.Println(result.Item.Name)
}

// 3. Collect — convenience for smaller datasets
all, err := client.Recipients.Collect(ctx, nil)

// Or cap the number of items fetched:
first50, err := mercury.CollectN(ctx, 50, client.Recipients.Pages(nil))

mercury-go targets Go 1.22, so pagination uses buffered channels (<-chan PageResult[T]) rather than the iter.Seq range-over-func form introduced in Go 1.23. The API shape is identical in spirit — Pages, Items, Collect, CollectN — and will gain native iter.Seq2 support in a future major version once Go 1.23 is broadly available.


Error handling

import "github.com/iamkanishka/mercury-go/mercury"

account, err := client.Accounts.Get(ctx, id)
switch {
case mercury.IsAuthError(err):
	// 401 — bad or missing API token
case mercury.IsNotFoundError(err):
	var nfErr *mercury.NotFoundError
	errors.As(err, &nfErr)
	fmt.Println("missing resource:", nfErr.Resource)
case mercury.IsRateLimitError(err):
	var rlErr *mercury.RateLimitError
	errors.As(err, &rlErr)
	fmt.Println("retry after seconds:", rlErr.RetryAfter)
case mercury.IsValidationError(err):
	// 400 — bad request body
case mercury.IsConflictError(err):
	// 409 — e.g. duplicate idempotency key
case mercury.IsServerError(err):
	// 5xx
case mercury.IsNetworkError(err):
	// transport-level failure; unwrap with errors.Unwrap(err) for the cause
case err != nil:
	// unexpected
}

Every typed error embeds mercury.APIError, which exposes:

  • Codemercury.ErrorCode ("unauthorized", "not_found", "rate_limited", …)
  • StatusCode — the HTTP status code
  • Message — human-readable message from the API
  • RequestID — Mercury's or the SDK's request ID, for support tickets
  • Body — the raw decoded JSON error body

Retry behaviour

Condition Retried?
429 Too Many Requests ✅ respects Retry-After header
500 / 502 / 503 / 504 ✅ exponential backoff with jitter
400 / 401 / 403 / 404 / 409 ❌ fails immediately
Network error / timeout

Configure via mercury.WithRetry(mercury.RetryConfig{...}). Defaults: 3 retries, 500ms initial delay, 30s cap, 25% jitter.


Idempotency

SendMoneyRequest and InternalTransferRequest both require an IdempotencyKey. Always generate a fresh UUID per logical operation and persist it alongside your own records so a retried request — yours or the SDK's — can't double-send money:

import "github.com/google/uuid"

req := &mercury.SendMoneyRequest{
	// ...
	IdempotencyKey: uuid.NewString(),
}

Optional fields

Mercury request structs use pointers for optional fields, matching Go convention. The generic mercury.Ptr helper makes literals easy:

mercury.Ptr("a note")
mercury.Ptr(42)
mercury.Ptr(mercury.SortOrderDesc)

Development

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

License

MIT

Documentation

Overview

Package mercurygo re-exports the most commonly used identifiers from the github.com/iamkanishka/mercury-go/mercury package, so callers who prefer a flat import can write:

import mercury "github.com/iamkanishka/mercury-go"

instead of:

import "github.com/iamkanishka/mercury-go/mercury"

Both import paths construct the exact same *mercury.Client and share all types, since every alias and wrapper below simply forwards to the canonical implementation in the mercury subpackage. New code can use either form interchangeably; this package exists purely for import-path convenience and carries no behaviour of its own.

The canonical, fully-documented API surface (all resource services, every request/response type, error helpers, and pagination utilities) lives in the mercury subpackage at https://pkg.go.dev/github.com/iamkanishka/mercury-go/mercury. This file only re-exports the entry points most callers reach for first; for anything not aliased here (resource-specific types, less common options), import the mercury subpackage directly alongside this one.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func IsAuthError

func IsAuthError(err error) bool

IsAuthError reports whether err is a Mercury authentication error (401).

func IsConflictError

func IsConflictError(err error) bool

IsConflictError reports whether err is a Mercury conflict error (409).

func IsNetworkError

func IsNetworkError(err error) bool

IsNetworkError reports whether err is a Mercury network/transport error.

func IsNotFoundError

func IsNotFoundError(err error) bool

IsNotFoundError reports whether err is a Mercury not-found error (404).

func IsRateLimitError

func IsRateLimitError(err error) bool

IsRateLimitError reports whether err is a Mercury rate-limit error (429).

func IsServerError

func IsServerError(err error) bool

IsServerError reports whether err is a Mercury server error (5xx).

func IsValidationError

func IsValidationError(err error) bool

IsValidationError reports whether err is a Mercury validation error (400).

func Ptr

func Ptr[T any](v T) *T

Ptr is a generic helper that returns a pointer to any value. Useful for setting optional fields in request structs, e.g. mercury.Ptr("a note").

Types

type APIError

type APIError = mercury.APIError

APIError is an alias for mercury.APIError, the base error type for all Mercury HTTP API errors.

type Client

type Client = mercury.Client

Client is an alias for mercury.Client, the Mercury Banking API client.

func NewClient

func NewClient(apiKey string, opts ...Option) *Client

NewClient creates a new Mercury API client. It forwards directly to mercury.NewClient; see that function's documentation for full details.

apiKey must include the "secret-token:" prefix, e.g. "secret-token:mercury_production_...".

type Option

type Option = mercury.Option

Option is an alias for mercury.Option, a functional option for NewClient.

func WithBaseURL

func WithBaseURL(baseURL string) Option

WithBaseURL overrides the Mercury API base URL (useful for sandbox/testing).

func WithRetry

func WithRetry(r RetryConfig) Option

WithRetry configures retry behaviour for transient failures.

type RetryConfig

type RetryConfig = mercury.RetryConfig

RetryConfig is an alias for mercury.RetryConfig.

Directories

Path Synopsis
examples
basic command
Command basic demonstrates common mercury-go usage patterns.
Command basic demonstrates common mercury-go usage patterns.
internal
testutil
Package testutil provides HTTP test server helpers for mercury-go tests.
Package testutil provides HTTP test server helpers for mercury-go tests.
Package mercury provides a production-grade Go client for the Mercury Banking API.
Package mercury provides a production-grade Go client for the Mercury Banking API.

Jump to

Keyboard shortcuts

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