opensettle

package module
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: May 14, 2026 License: MIT Imports: 16 Imported by: 0

README

OpenSettle Go SDK

Go Reference

Official Go SDK for the OpenSettle API — stablecoin billing on Base, Ethereum, Polygon, Arbitrum, Solana, and Tron. Non-custodial: OpenSettle never holds your funds.

Hand-written, zero third-party runtime dependencies, idiomatic context.Context throughout, typed errors reachable with errors.As, signed-webhook verifier in a separate sub-package.

Install

go get github.com/OpenSettle/opensettle-sdk-go

Requires Go 1.22+.

Quick start

package main

import (
    "context"
    "log"
    "os"

    opensettle "github.com/OpenSettle/opensettle-sdk-go"
)

func main() {
    client, err := opensettle.NewClient(
        os.Getenv("OPENSETTLE_KEY"),        // sk_test_... or sk_live_...
        os.Getenv("OPENSETTLE_WORKSPACE"),
        opensettle.WithTestMode(os.Getenv("ENV") != "production"),
    )
    if err != nil {
        log.Fatal(err)
    }

    ctx := context.Background()
    checkout, err := client.Checkouts.Create(ctx, opensettle.CreateCheckoutRequest{
        Mode:       opensettle.CheckoutPayment,
        CustomerID: "cu_1",
        InvoiceID:  "in_1",
        SuccessURL: "https://example.com/thanks",
    })
    if err != nil {
        log.Fatal(err)
    }
    log.Printf("checkout %s, expires %s", checkout.ID, checkout.ExpiresAt)
}

Resources

The SDK mirrors the OpenSettle REST API one-to-one:

Field Endpoint
client.Checkouts /v1/workspaces/<ws>/checkouts
client.Customers /v1/workspaces/<ws>/customers
client.Invoices /v1/workspaces/<ws>/invoices
client.Payments /v1/workspaces/<ws>/payments
client.Products /v1/workspaces/<ws>/products (+ prices)
client.Subscriptions /v1/workspaces/<ws>/subscriptions
client.WebhookEndpoints /v1/workspaces/<ws>/webhook_endpoints

Every method takes ctx context.Context as the first argument, returns (*ResultType, error), and propagates request IDs in error values.

Pagination

Every List method has a sibling ListIter that walks every page transparently:

it := client.Customers.ListIter(ctx, &opensettle.ListCustomersQuery{
    Status: opensettle.CustomerActive,
    Limit:  100,
})
for it.Next() {
    fmt.Println(it.Item().ID)
}
if err := it.Err(); err != nil {
    return err
}

The iterator lazy-fetches the first page on the first Next() call and keeps following the nextCursor until hasMore is false. Filters from the initial query (status, customerId, etc.) are preserved across every page.

Polling

WaitFor polls a Retrieve method until a predicate is satisfied. Useful in scripts and CI; production code should prefer webhooks.

pmt, err := opensettle.WaitFor(ctx,
    client.Payments.Retrieve,
    "pay_123",
    func(p *opensettle.Payment) bool { return p.Status == opensettle.PaymentConfirmed },
    opensettle.WaitOptions{Timeout: 2 * time.Minute, Interval: 2 * time.Second},
)
if err != nil {
    var to *opensettle.WaitTimeoutError
    if errors.As(err, &to) {
        last, _ := to.Last.(*opensettle.Payment)
        log.Printf("timed out; last status: %s", last.Status)
    }
    return err
}

Idempotency keys

Every money-adjacent write — Checkouts.Create, Customers.Create, Invoices.Create/Send/Remind, Payments.Refund/RefundBroadcast, Products.Create/CreatePrice, Subscriptions.Create/ChangePlan, WebhookEndpoints.Create/RotateSecret — auto-attaches an Idempotency-Key that is preserved across retry attempts. To supply your own deterministic key (e.g. your DB row id) so retries from multiple machines collapse to the same server-side operation, pass WithIdempotencyKey:

checkout, err := client.Checkouts.Create(ctx, req,
    opensettle.WithIdempotencyKey("order:" + order.ID),
)

Typed errors

checkout, err := client.Payments.Refund(ctx, paymentID, opensettle.InitiateRefundRequest{})
if err != nil {
    var rl *opensettle.RateLimitError
    if errors.As(err, &rl) {
        time.Sleep(time.Duration(rl.RetryAfter) * time.Second)
        return retry()
    }
    var stepUp *opensettle.StepUpRequiredError
    if errors.As(err, &stepUp) {
        return promptStepUp()
    }
    return err
}

Every API error code in the platform's 13-code taxonomy maps to a concrete Go type:

Error type Codes
*InvalidRequestError invalid_request
*InvalidStateTransitionError invalid_state_transition
*AuthenticationError unauthorized
*ForbiddenError forbidden
*NotFoundError not_found
*ConflictError conflict
*RateLimitError (.RetryAfter) rate_limited
*SettlementError chain_reverted, insufficient_confirmations, signing_required
*StepUpRequiredError aal_required
*APIError internal_error and unknown codes
*NetworkError transport-layer failures

OpenSettleError is the embedded base; all subtypes carry Code, Status, RequestID, and Param.

Webhooks

Verification lives in a sub-package so consumers can use it without dragging the rest of the SDK into their handler:

import "github.com/OpenSettle/opensettle-sdk-go/webhooks"

func handle(w http.ResponseWriter, r *http.Request) {
    body, _ := io.ReadAll(r.Body)
    v, err := webhooks.Verify(webhooks.Opts{
        RawBody:         body,
        SignatureHeader: r.Header.Get("x-opensettle-signature"),
        Secret:          os.Getenv("OPENSETTLE_WEBHOOK_SECRET"),
    })
    if err != nil {
        http.Error(w, "invalid", http.StatusBadRequest)
        return
    }
    process(v.Body, v.Timestamp)
}

webhooks.Decode[T] is the generic version when you want a typed event:

ev, _, err := webhooks.Decode[PaymentConfirmedEvent](opts)

Signature scheme: HMAC-SHA256 over <unix_seconds>.<raw_body>, hex-encoded. Constant-time comparison via hmac.Equal. Default tolerance window is 5 minutes; tune via Opts.Tolerance.

Configuration

client, err := opensettle.NewClient(apiKey, workspaceID,
    opensettle.WithBaseURL("https://api.opensettle.io"),
    opensettle.WithHTTPClient(myHTTPClient),
    opensettle.WithMaxRetries(3),       // default 3; set 0 to disable
    opensettle.WithTimeout(30 * time.Second),
    opensettle.WithTestMode(true),      // refuses sk_live_ keys; CI circuit breaker
    opensettle.WithUserAgent("my-app/1.0"),
)

Retries cover 5xx + 429 + transport errors with exponential backoff capped at 4 s. The SDK respects Retry-After (both delta-seconds and HTTP-date forms). Idempotency keys are automatically attached to money-adjacent writes and shared across retry attempts — supply WithIdempotencyKey if you want to provide your own.

Versioning

Strict semver. The major version tracks the HTTP API major version (v1). Minor versions add features without breakage; patch versions are bug fixes only. See CHANGELOG.md.

License

MIT

Documentation

Overview

Package opensettle is the official Go SDK for the OpenSettle API.

OpenSettle is a non-custodial stablecoin billing platform: USDC and USDT subscriptions, one-shot checkouts, and invoices on Base, Ethereum, Polygon, Arbitrum, Solana, and Tron.

Quick start

import "github.com/OpenSettle/opensettle-sdk-go"

client, err := opensettle.NewClient(
    os.Getenv("OPENSETTLE_KEY"),
    os.Getenv("OPENSETTLE_WORKSPACE"),
    opensettle.WithTestMode(os.Getenv("ENV") != "production"),
)
if err != nil { log.Fatal(err) }

checkout, err := client.Checkouts.Create(ctx, opensettle.CreateCheckoutRequest{ /* ... */ })

Typed errors

Every API error is mapped to a typed value reachable via errors.As:

if _, err := client.Payments.Refund(ctx, paymentID, req); err != nil {
    var rl *opensettle.RateLimitError
    if errors.As(err, &rl) {
        time.Sleep(time.Duration(rl.RetryAfter) * time.Second)
        return retry()
    }
    var settle *opensettle.SettlementError
    if errors.As(err, &settle) && settle.Code == opensettle.CodeSigningRequired {
        return promptCustomerToReapprove()
    }
    return err
}

Pagination

Every List endpoint has a sibling ListIter that returns an Iter walking every page transparently:

it := client.Customers.ListIter(ctx, &opensettle.ListCustomersQuery{Status: opensettle.CustomerActive})
for it.Next() {
    fmt.Println(it.Item().ID)
}
if err := it.Err(); err != nil { … }

Polling

WaitFor is a polling helper for scripts and CI; production code should prefer webhooks. It calls retrieve every opts.Interval until the until predicate succeeds, then returns the resource:

pmt, err := opensettle.WaitFor(ctx,
    client.Payments.Retrieve, "pay_…",
    func(p *opensettle.Payment) bool { return p.Status == opensettle.PaymentConfirmed },
    opensettle.WaitOptions{Timeout: 2 * time.Minute, Interval: 2 * time.Second},
)

On timeout you get a *WaitTimeoutError that carries the last-observed resource so you can inspect the partial state.

Idempotency keys

Every money-adjacent write (Create, Refund, RotateSecret, …) auto-attaches an Idempotency-Key that is preserved across retry attempts. Pass WithIdempotencyKey to use a caller-chosen key when you have a natural deterministic id (e.g. your DB row id):

checkout, err := client.Checkouts.Create(ctx, req,
    opensettle.WithIdempotencyKey("order:" + order.ID),
)

Webhooks

Webhook signature verification lives in the sub-package github.com/OpenSettle/opensettle-sdk-go/webhooks so consumers can use it without dragging the full SDK into their webhook handler.

Index

Examples

Constants

View Source
const Version = "0.5.0"

Version is the SDK release tag this build was cut from. It is sent as part of the User-Agent header on every request so the API can break out adoption by SDK version.

Variables

This section is empty.

Functions

func FromEnvelope

func FromEnvelope(body []byte, status int, retryAfter float64) error

FromEnvelope maps a parsed envelope + HTTP status to the right typed error. Unknown codes fall back to *APIError so a new server-side code can't crash older SDKs.

retryAfter is the parsed Retry-After header value (seconds); 0 means the server didn't send one.

func IsOpenSettleError

func IsOpenSettleError(err error) bool

IsOpenSettleError reports whether err is or wraps an *OpenSettleError. Convenience around errors.As for the broad case.

func WaitFor added in v0.3.0

func WaitFor[T any](
	ctx context.Context,
	retrieve func(ctx context.Context, id string) (T, error),
	resourceID string,
	until func(T) bool,
	opts WaitOptions,
) (T, error)

WaitFor polls retrieve(ctx, id) every opts.Interval until until(r) returns true, then returns the resource. Returns *WaitTimeoutError when opts.Timeout elapses first, or the wrapped ctx error on cancellation. Any error returned by retrieve aborts the loop immediately.

Type parameter T is the concrete resource pointed at by Retrieve (e.g. *Payment, *Invoice).

Example
package main

import (
	"context"
	"errors"
	"fmt"
	"log"
	"time"

	opensettle "github.com/OpenSettle/opensettle-sdk-go"
)

func main() {
	client, _ := opensettle.NewClient("sk_test_x", "ws_test", opensettle.WithTestMode(true))
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
	defer cancel()

	payment, err := opensettle.WaitFor(
		ctx,
		client.Payments.Retrieve,
		"pay_123",
		func(p *opensettle.Payment) bool { return p.Status == opensettle.PaymentConfirmed },
		opensettle.WaitOptions{Timeout: 2 * time.Minute, Interval: 2 * time.Second},
	)
	if err != nil {
		var timeoutErr *opensettle.WaitTimeoutError
		if errors.As(err, &timeoutErr) {
			last, _ := timeoutErr.Last.(*opensettle.Payment)
			log.Fatalf("timed out; last status: %s", last.Status)
		}
		log.Fatal(err)
	}
	fmt.Println(payment.TxHash)
}

Types

type APIError

type APIError struct{ *OpenSettleError }

APIError is the catch-all for server-side problems: internal errors and codes the SDK doesn't recognize. Forward-compatible — a new server code won't crash older SDKs, just classify here.

func (*APIError) Unwrap

func (e *APIError) Unwrap() error

Unwrap returns the embedded *OpenSettleError so errors.Is/As can match the base type.

type AuthenticationError

type AuthenticationError struct{ *OpenSettleError }

AuthenticationError signals a 401 — missing, malformed, expired, or revoked credentials. Rotate the API key or re-authenticate the session.

func (*AuthenticationError) Unwrap

func (e *AuthenticationError) Unwrap() error

Unwrap returns the embedded *OpenSettleError so errors.Is/As can match the base type.

type AutopayMode

type AutopayMode string

AutopayMode controls how a subscription's renewal charge is collected. allowance uses an ERC-20 spend approval against the merchant's collector; smart-wallet uses a session-key-style preauthorization; manual prompts the customer to sign each renewal in their wallet.

const (
	AutopayAllowance   AutopayMode = "allowance"
	AutopaySmartWallet AutopayMode = "smart-wallet"
	AutopayManual      AutopayMode = "manual"
)

type CancelMode

type CancelMode string

CancelMode controls whether cancellation is immediate or deferred to the next billing boundary.

const (
	CancelImmediately CancelMode = "immediately"
	CancelAtPeriodEnd CancelMode = "at_period_end"
)

type CancelSubscriptionRequest

type CancelSubscriptionRequest struct {
	Reason string     `json:"reason,omitempty"`
	Mode   CancelMode `json:"mode,omitempty"`
}

CancelSubscriptionRequest is the body for POST /subscriptions/<id>/cancel. Reason is recorded on the audit log. Mode defaults to at_period_end when empty.

type ChainID added in v0.5.0

type ChainID string

ChainID is a supported settlement chain. Mirrors `ChainId` in `@opensettle/shared/schemas/wallet`.

type ChainId deprecated

type ChainId = ChainID

ChainId is the original (pre-Go-idiomatic) name for ChainID. Kept as a type alias for backward compatibility; new code should prefer ChainID.

Deprecated: use ChainID instead.

const (
	ChainBase     ChainId = "base"
	ChainEthereum ChainId = "ethereum"
	ChainPolygon  ChainId = "polygon"
	ChainArbitrum ChainId = "arbitrum"
	ChainTron     ChainId = "tron"
	ChainSolana   ChainId = "solana"
)

type ChangePlanRequest

type ChangePlanRequest struct {
	PriceID       string        `json:"priceId"`
	ProrationMode ProrationMode `json:"prorationMode,omitempty"`
}

ChangePlanRequest is the body for POST /subscriptions/<id>/change_plan. ProrationMode controls when the swap takes effect; default is immediately when empty.

type Checkout

type Checkout struct {
	ID          string         `json:"id"`
	WorkspaceID string         `json:"workspaceId"`
	Mode        CheckoutMode   `json:"mode"`
	Status      CheckoutStatus `json:"status"`
	CustomerID  string         `json:"customerId"`
	InvoiceID   *string        `json:"invoiceId"`
	PriceID     *string        `json:"priceId"`
	AmountMinor int            `json:"amountMinor"`
	Currency    string         `json:"currency"`
	Chain       ChainId        `json:"chain"`
	Token       TokenSymbol    `json:"token"`
	Description *string        `json:"description"`
	SuccessURL  string         `json:"successUrl"`
	CancelURL   *string        `json:"cancelUrl"`
	ExpiresAt   string         `json:"expiresAt"`
	CompletedAt *string        `json:"completedAt"`
	Metadata    Metadata       `json:"metadata"`
	CreatedAt   string         `json:"createdAt"`
	// HostedURL is a relative URL path (e.g. "/checkout/<hostedToken>");
	// concatenate with the web origin (e.g. "https://opensettle.io"+HostedURL)
	// to get the buyer-facing redirect URL.
	HostedURL string `json:"hostedUrl"`
}

Checkout is a hosted payment session. HostedURL is the relative path (e.g. "/checkout/<token>") to redirect the buyer to; concatenate with the OpenSettle web origin. AmountMinor + Currency are populated once the underlying invoice/price is resolved.

type CheckoutMode

type CheckoutMode string

CheckoutMode selects what a hosted checkout session does. "payment" is a one-shot charge (typically attached to an invoice); "subscription" creates a recurring Subscription on successful completion.

const (
	CheckoutPayment      CheckoutMode = "payment"
	CheckoutSubscription CheckoutMode = "subscription"
)

type CheckoutStatus

type CheckoutStatus string

CheckoutStatus is the lifecycle bucket of a hosted checkout session. open is awaiting buyer; pending is buyer signed and broadcast, awaiting chain confirmation; succeeded, failed, and expired are terminal.

const (
	CheckoutOpen      CheckoutStatus = "open"
	CheckoutPending   CheckoutStatus = "pending"
	CheckoutSucceeded CheckoutStatus = "succeeded"
	CheckoutFailed    CheckoutStatus = "failed"
	CheckoutExpired   CheckoutStatus = "expired"
)

type CheckoutsResource

type CheckoutsResource struct {
	// contains filtered or unexported fields
}

CheckoutsResource exposes /v1/workspaces/<ws>/checkouts.

func (*CheckoutsResource) Create

Create starts a hosted checkout session. Body is required; the request is sent with an auto-generated Idempotency-Key to make retries safe. Supply WithIdempotencyKey to use a caller-chosen key instead.

Example
package main

import (
	"context"
	"fmt"
	"log"

	opensettle "github.com/OpenSettle/opensettle-sdk-go"
)

func main() {
	client, _ := opensettle.NewClient("sk_test_x", "ws_test", opensettle.WithTestMode(true))
	ctx := context.Background()

	checkout, err := client.Checkouts.Create(ctx, opensettle.CreateCheckoutRequest{
		Mode:       opensettle.CheckoutPayment,
		CustomerID: "cu_123",
		InvoiceID:  "in_123",
		SuccessURL: "https://example.com/thanks",
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(checkout.HostedURL)
}

func (*CheckoutsResource) Retrieve

func (r *CheckoutsResource) Retrieve(ctx context.Context, id string) (*Checkout, error)

Retrieve fetches a checkout session by id.

type Client

type Client struct {
	Checkouts        *CheckoutsResource
	Customers        *CustomersResource
	Invoices         *InvoicesResource
	Payments         *PaymentsResource
	Products         *ProductsResource
	Subscriptions    *SubscriptionsResource
	WebhookEndpoints *WebhookEndpointsResource
	// contains filtered or unexported fields
}

Client is the top-level handle. One per workspace. Safe for concurrent use by multiple goroutines.

Example (TypedErrors)
package main

import (
	"context"
	"errors"
	"log"
	"time"

	opensettle "github.com/OpenSettle/opensettle-sdk-go"
)

func main() {
	client, _ := opensettle.NewClient("sk_test_x", "ws_test", opensettle.WithTestMode(true))
	ctx := context.Background()

	_, err := client.Payments.Refund(ctx, "pay_123", opensettle.InitiateRefundRequest{})
	if err == nil {
		return
	}
	var rl *opensettle.RateLimitError
	if errors.As(err, &rl) {
		time.Sleep(time.Duration(rl.RetryAfter) * time.Second)
		return
	}
	var stepUp *opensettle.StepUpRequiredError
	if errors.As(err, &stepUp) {
		// Caller needs to re-auth with AAL=2 before the refund will succeed.
		log.Fatal("step-up required")
	}
	var settle *opensettle.SettlementError
	if errors.As(err, &settle) && settle.Code == opensettle.CodeSigningRequired {
		// Customer's wallet needs to re-approve the spend allowance.
		log.Fatal("re-approval required")
	}
	log.Fatal(err)
}

func NewClient

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

NewClient builds a Client. apiKey must start with sk_live_ or sk_test_; workspaceID is the merchant's workspace (required on every route).

Example
package main

import (
	"log"
	"os"
	"time"

	opensettle "github.com/OpenSettle/opensettle-sdk-go"
)

func main() {
	client, err := opensettle.NewClient(
		os.Getenv("OPENSETTLE_KEY"),
		os.Getenv("OPENSETTLE_WORKSPACE"),
		opensettle.WithTestMode(true),
		opensettle.WithTimeout(15*time.Second),
		opensettle.WithMaxRetries(3),
	)
	if err != nil {
		log.Fatal(err)
	}
	_ = client
}

type ConfigError

type ConfigError struct{ Message string }

ConfigError is returned by NewClient when the caller's config is internally inconsistent (e.g. test-mode with a live key). Kept distinct from API-side errors so callers don't have to know they need a transport before hitting an obvious config bug.

func (*ConfigError) Error

func (e *ConfigError) Error() string

type ConflictError

type ConflictError struct{ *OpenSettleError }

ConflictError signals a 409 — typically an idempotency-key collision with a different request body, or an attempt to delete a resource that is still referenced by another.

func (*ConflictError) Unwrap

func (e *ConflictError) Unwrap() error

Unwrap returns the embedded *OpenSettleError so errors.Is/As can match the base type.

type CreateCheckoutRequest

type CreateCheckoutRequest struct {
	Mode             CheckoutMode `json:"mode"`
	CustomerID       string       `json:"customerId,omitempty"`
	CustomerEmail    string       `json:"customerEmail,omitempty"`
	CustomerName     string       `json:"customerName,omitempty"`
	InvoiceID        string       `json:"invoiceId,omitempty"`
	PriceID          string       `json:"priceId,omitempty"`
	SuccessURL       string       `json:"successUrl"`
	CancelURL        string       `json:"cancelUrl,omitempty"`
	Chain            ChainId      `json:"chain,omitempty"`
	Token            TokenSymbol  `json:"token,omitempty"`
	ExpiresInMinutes int          `json:"expiresInMinutes,omitempty"`
	Metadata         Metadata     `json:"metadata,omitempty"`
}

CreateCheckoutRequest is the body for POST /checkouts. Exactly one of (CustomerID) or (CustomerEmail [+ CustomerName]) should be supplied — the latter form auto-creates a Customer on the fly. For Mode=payment pass InvoiceID; for Mode=subscription pass PriceID. Chain/Token are optional pre-selections; if omitted, the buyer picks on the hosted page.

type CreateCustomerRequest

type CreateCustomerRequest struct {
	Email    string   `json:"email"`
	Name     string   `json:"name,omitempty"`
	Wallet   string   `json:"wallet,omitempty"`
	Country  string   `json:"country,omitempty"`
	Metadata Metadata `json:"metadata,omitempty"`
}

CreateCustomerRequest is the body for POST /customers. Email is the only required field; Wallet (when present) is validated against the workspace's enabled chains on the server.

type CreateInvoiceRequest

type CreateInvoiceRequest struct {
	CustomerID     string      `json:"customerId"`
	Chain          ChainId     `json:"chain"`
	Token          TokenSymbol `json:"token"`
	Currency       string      `json:"currency,omitempty"`
	LineItems      []LineItem  `json:"lineItems"`
	Memo           string      `json:"memo,omitempty"`
	DueInDays      int         `json:"dueInDays,omitempty"`
	SubscriptionID string      `json:"subscriptionId,omitempty"`
	Metadata       Metadata    `json:"metadata,omitempty"`
}

CreateInvoiceRequest is the body for POST /invoices. DueInDays is an integer offset from the server clock; the API converts it to DueAt. SubscriptionID is set automatically for subscription-generated invoices and should normally be left empty by callers.

type CreatePriceRequest

type CreatePriceRequest struct {
	Amount   int           `json:"amount"`
	Currency string        `json:"currency,omitempty"`
	Interval PriceInterval `json:"interval"`
	Metadata Metadata      `json:"metadata,omitempty"`
}

CreatePriceRequest is the body for POST /products/<id>/prices. Amount is required and in minor units (e.g. cents). Currency defaults to the workspace's default fiat when empty.

type CreateProductRequest

type CreateProductRequest struct {
	Name        string   `json:"name"`
	Description string   `json:"description,omitempty"`
	Metadata    Metadata `json:"metadata,omitempty"`
}

CreateProductRequest is the body for POST /products.

type CreateSubscriptionRequest

type CreateSubscriptionRequest struct {
	CustomerID string      `json:"customerId"`
	PriceID    string      `json:"priceId"`
	Chain      ChainId     `json:"chain"`
	Token      TokenSymbol `json:"token"`
	Autopay    AutopayMode `json:"autopay,omitempty"`
	TrialDays  int         `json:"trialDays,omitempty"`
	Metadata   Metadata    `json:"metadata,omitempty"`
}

CreateSubscriptionRequest is the body for POST /subscriptions. Autopay defaults to manual when empty. TrialDays > 0 starts the subscription in trialing status; the first charge fires at trial end.

type CreateWebhookEndpointRequest

type CreateWebhookEndpointRequest struct {
	URL         string   `json:"url"`
	Description string   `json:"description,omitempty"`
	Events      []string `json:"events,omitempty"`
}

CreateWebhookEndpointRequest is the body for POST /webhook_endpoints. Events is an allow-list of event-type names (e.g. "payment.confirmed"); omit or pass an empty slice to subscribe to all events.

type CreateWebhookEndpointResponse

type CreateWebhookEndpointResponse struct {
	Endpoint      WebhookEndpoint `json:"endpoint"`
	SigningSecret string          `json:"signingSecret"`
}

CreateWebhookEndpointResponse is the multi-key envelope returned by POST /webhook_endpoints (and by /rotate). SigningSecret is the plaintext HMAC secret returned exactly once — store it immediately; subsequent reads only return the endpoint metadata.

type CursorPage

type CursorPage[T any] struct {
	Data       []T    `json:"data"`
	NextCursor string `json:"nextCursor"`
	HasMore    bool   `json:"hasMore,omitempty"`
}

CursorPage wraps a paginated list response. The API returns a cursor envelope around every listed collection.

type Customer

type Customer struct {
	ID                  string         `json:"id"`
	WorkspaceID         string         `json:"workspaceId"`
	Email               string         `json:"email"`
	Name                string         `json:"name"`
	Wallet              *string        `json:"wallet"`
	Country             *string        `json:"country"`
	Status              CustomerStatus `json:"status"`
	ActiveSubscriptions int            `json:"activeSubscriptions"`
	LifetimeValue       int            `json:"lifetimeValue"`
	Metadata            Metadata       `json:"metadata"`
	CreatedAt           string         `json:"createdAt"`
	DeletedAt           *string        `json:"deletedAt"`
}

Customer is a merchant's customer record. Email and Name are stored encrypted at rest; the API returns the decrypted view on Retrieve. ActiveSubscriptions and LifetimeValue are server-computed rollups. DeletedAt is set when the row is soft-deleted; reads still return it for audit purposes.

type CustomerStatus

type CustomerStatus string

CustomerStatus is the derived health bucket OpenSettle assigns based on recent payment + subscription activity. It is not directly settable; the platform updates it as a side effect of billing events.

const (
	CustomerActive  CustomerStatus = "active"
	CustomerAtRisk  CustomerStatus = "at_risk"
	CustomerChurned CustomerStatus = "churned"
)

type CustomersResource

type CustomersResource struct {
	// contains filtered or unexported fields
}

CustomersResource exposes /v1/workspaces/<ws>/customers.

func (*CustomersResource) Create

Create makes a new customer. Auto-attaches an Idempotency-Key; supply WithIdempotencyKey to use a caller-chosen key instead.

func (*CustomersResource) Delete

func (r *CustomersResource) Delete(ctx context.Context, customerID string) error

Delete is a soft-delete: PII is scrubbed but historical references continue to resolve.

func (*CustomersResource) List

List returns a cursor-paginated page of customers.

func (*CustomersResource) ListIter added in v0.3.0

func (r *CustomersResource) ListIter(ctx context.Context, query *ListCustomersQuery) *Iter[Customer]

ListIter returns a cursor-driven iterator over all customers matching the query. Auto-fetches subsequent pages.

it := c.Customers.ListIter(ctx, &opensettle.ListCustomersQuery{Status: opensettle.CustomerActive})
for it.Next() { fmt.Println(it.Item().ID) }
if err := it.Err(); err != nil { … }
Example
package main

import (
	"context"
	"fmt"
	"log"

	opensettle "github.com/OpenSettle/opensettle-sdk-go"
)

func main() {
	client, _ := opensettle.NewClient("sk_test_x", "ws_test", opensettle.WithTestMode(true))
	ctx := context.Background()

	it := client.Customers.ListIter(ctx, &opensettle.ListCustomersQuery{
		Status: opensettle.CustomerActive,
		Limit:  100,
	})
	for it.Next() {
		fmt.Println(it.Item().ID)
	}
	if err := it.Err(); err != nil {
		log.Fatal(err)
	}
}

func (*CustomersResource) Retrieve

func (r *CustomersResource) Retrieve(ctx context.Context, customerID string) (*Customer, error)

Retrieve fetches a customer by id.

func (*CustomersResource) Update

func (r *CustomersResource) Update(ctx context.Context, customerID string, input UpdateCustomerRequest) (*Customer, error)

Update applies a partial update.

type ErrorCode

type ErrorCode string

ErrorCode is the API's stable error taxonomy. Values mirror `packages/sdk/src/errors.ts` exactly.

const (
	CodeInvalidRequest            ErrorCode = "invalid_request"
	CodeInvalidStateTransition    ErrorCode = "invalid_state_transition"
	CodeUnauthorized              ErrorCode = "unauthorized"
	CodeForbidden                 ErrorCode = "forbidden"
	CodeNotFound                  ErrorCode = "not_found"
	CodeConflict                  ErrorCode = "conflict"
	CodeRateLimited               ErrorCode = "rate_limited"
	CodeInternalError             ErrorCode = "internal_error"
	CodeChainReverted             ErrorCode = "chain_reverted"
	CodeInsufficientConfirmations ErrorCode = "insufficient_confirmations"
	CodeSigningRequired           ErrorCode = "signing_required"
	CodeAALRequired               ErrorCode = "aal_required"
	CodeNetworkError              ErrorCode = "network_error"
)

type ForbiddenError

type ForbiddenError struct{ *OpenSettleError }

ForbiddenError signals a 403 — credentials are valid but lack permission for the requested action or workspace.

func (*ForbiddenError) Unwrap

func (e *ForbiddenError) Unwrap() error

Unwrap returns the embedded *OpenSettleError so errors.Is/As can match the base type.

type InitiateRefundRequest

type InitiateRefundRequest struct {
	AmountMinor      int    `json:"amountMinor,omitempty"`
	Reason           string `json:"reason,omitempty"`
	RecipientAddress string `json:"recipientAddress,omitempty"`
}

InitiateRefundRequest is the body for POST /payments/<id>/refund. AmountMinor omitted (zero) means refund the full remaining amount. RecipientAddress overrides the default refund destination (the original payer); the server validates it against the payment's chain.

type InitiateRefundResponse

type InitiateRefundResponse struct {
	Payment    Payment            `json:"payment"`
	UnsignedTx UnsignedTxEnvelope `json:"unsignedTx"`
}

InitiateRefundResponse is the multi-key envelope returned by POST /payments/<id>/refund. The Payment is in status refund_pending; the UnsignedTx must be signed by the merchant wallet and broadcast, then reported back via RefundBroadcast.

type InvalidRequestError

type InvalidRequestError struct{ *OpenSettleError }

InvalidRequestError signals a 4xx validation failure: malformed JSON, missing required fields, or values that fail server-side schema checks. Param often names the offending field.

func (*InvalidRequestError) Unwrap

func (e *InvalidRequestError) Unwrap() error

Unwrap returns the embedded *OpenSettleError so errors.Is/As can match the base type.

type InvalidStateTransitionError

type InvalidStateTransitionError struct{ *OpenSettleError }

InvalidStateTransitionError signals an attempt to move a resource to a state its current state forbids (e.g. voiding a paid invoice, canceling an already-canceled subscription).

func (*InvalidStateTransitionError) Unwrap

func (e *InvalidStateTransitionError) Unwrap() error

Unwrap returns the embedded *OpenSettleError so errors.Is/As can match the base type.

type Invoice

type Invoice struct {
	ID             string        `json:"id"`
	WorkspaceID    string        `json:"workspaceId"`
	Number         string        `json:"number"`
	CustomerID     string        `json:"customerId"`
	SubscriptionID *string       `json:"subscriptionId"`
	AmountMinor    int           `json:"amountMinor"`
	Currency       string        `json:"currency"`
	Chain          ChainId       `json:"chain"`
	Token          TokenSymbol   `json:"token"`
	Status         InvoiceStatus `json:"status"`
	LineItems      []LineItem    `json:"lineItems"`
	Memo           *string       `json:"memo"`
	PaymentID      *string       `json:"paymentId"`
	HostedURL      string        `json:"hostedUrl"`
	IssuedAt       *string       `json:"issuedAt"`
	DueAt          string        `json:"dueAt"`
	PaidAt         *string       `json:"paidAt"`
	VoidedAt       *string       `json:"voidedAt"`
	Metadata       Metadata      `json:"metadata"`
	CreatedAt      string        `json:"createdAt"`
}

Invoice is a billable document. AmountMinor is in minor units of Currency (fiat). Chain + Token specify how the customer will settle on chain. HostedURL is the buyer-facing page the customer pays from. PaymentID is set once a payment confirms.

type InvoiceStatus

type InvoiceStatus string

InvoiceStatus is the lifecycle state of an invoice. draft is the only editable state; open is sent-but-unpaid; paid and void are terminal; past_due is open + past DueAt.

const (
	InvoiceDraft   InvoiceStatus = "draft"
	InvoiceOpen    InvoiceStatus = "open"
	InvoicePaid    InvoiceStatus = "paid"
	InvoicePastDue InvoiceStatus = "past_due"
	InvoiceVoid    InvoiceStatus = "void"
)

type InvoicesResource

type InvoicesResource struct {
	// contains filtered or unexported fields
}

InvoicesResource exposes /v1/workspaces/<ws>/invoices.

func (*InvoicesResource) Create

func (r *InvoicesResource) Create(ctx context.Context, input CreateInvoiceRequest, opts ...RequestOption) (*Invoice, error)

Create makes a new invoice. Auto-attaches an Idempotency-Key; supply WithIdempotencyKey to use a caller-chosen key instead.

func (*InvoicesResource) List

List returns one page of invoices for the workspace. Pass nil for an unfiltered first page; use ListIter for cursor-driven full iteration.

func (*InvoicesResource) ListIter added in v0.3.0

func (r *InvoicesResource) ListIter(ctx context.Context, query *ListInvoicesQuery) *Iter[Invoice]

ListIter returns a cursor-driven iterator over all invoices matching the query.

func (*InvoicesResource) Remind

func (r *InvoicesResource) Remind(ctx context.Context, invoiceID string, opts ...RequestOption) (*Invoice, error)

Remind re-sends a reminder for an unpaid invoice. Auto-attaches an Idempotency-Key; supply WithIdempotencyKey to override.

func (*InvoicesResource) Retrieve

func (r *InvoicesResource) Retrieve(ctx context.Context, invoiceID string) (*Invoice, error)

Retrieve fetches a single invoice by ID.

func (*InvoicesResource) Send

func (r *InvoicesResource) Send(ctx context.Context, invoiceID string, opts ...RequestOption) (*Invoice, error)

Send emails the hosted invoice link to the customer. Auto-attaches an Idempotency-Key; supply WithIdempotencyKey to override.

func (*InvoicesResource) Void

func (r *InvoicesResource) Void(ctx context.Context, invoiceID string) (*Invoice, error)

Void marks an unpaid invoice as void. Terminal state.

type Iter added in v0.3.0

type Iter[T any] struct {
	// contains filtered or unexported fields
}

Iter is a cursor-driven iterator over a paginated API resource. It auto-fetches the next page when the current page is drained and stops when the server signals hasMore=false.

Resource List methods continue to return a single page for backward compatibility; iterators are returned by separate ListIter (or Iter-suffixed) methods on each resource — see the resource files.

Usage:

it := c.Customers.ListIter(ctx, &opensettle.ListCustomersQuery{Status: opensettle.CustomerActive})
for it.Next() {
    fmt.Println(it.Item().ID)
}
if err := it.Err(); err != nil { … }

func (*Iter[T]) Err added in v0.3.0

func (it *Iter[T]) Err() error

Err returns any error that stopped the iteration. Nil on a clean end.

func (*Iter[T]) Item added in v0.3.0

func (it *Iter[T]) Item() *T

Item returns the most-recently-yielded item. Only valid after Next has returned true.

func (*Iter[T]) Next added in v0.3.0

func (it *Iter[T]) Next() bool

Next advances the iterator. Returns false when the iteration is exhausted (call Err to distinguish "natural end" from "error").

type LineItem

type LineItem struct {
	Description     string `json:"description"`
	Quantity        int    `json:"quantity"`
	UnitAmountMinor int    `json:"unitAmountMinor"`
}

LineItem is a single row on an invoice. UnitAmountMinor is in minor units (e.g. cents); the line total is Quantity * UnitAmountMinor.

type ListCustomersQuery

type ListCustomersQuery struct {
	Status CustomerStatus
	Q      string
	Cursor string
	Limit  int
}

ListCustomersQuery filters GET /customers. Q is a free-text search over email + name. Status filters by derived health bucket; leave empty for all. Cursor + Limit drive pagination (Limit max 100).

type ListInvoicesQuery

type ListInvoicesQuery struct {
	Cursor     string
	Limit      int
	CustomerID string
	Status     InvoiceStatus
}

ListInvoicesQuery filters GET /invoices. CustomerID narrows to one customer; Status filters by lifecycle bucket. Cursor + Limit drive pagination.

type ListPaymentsQuery

type ListPaymentsQuery struct {
	Cursor     string
	Limit      int
	CustomerID string
	Status     PaymentStatus
}

ListPaymentsQuery filters GET /payments. CustomerID narrows to one customer; Status filters by on-chain lifecycle bucket. Cursor + Limit drive pagination.

type ListProductsQuery

type ListProductsQuery struct {
	Cursor string
	Limit  int
	Active *bool
}

ListProductsQuery filters GET /products. Active is a tri-state pointer: nil = both active and inactive, &true = active only, &false = inactive only.

type ListSubscriptionsQuery

type ListSubscriptionsQuery struct {
	Cursor     string
	Limit      int
	CustomerID string
	Status     SubscriptionStatus
}

ListSubscriptionsQuery filters GET /subscriptions. CustomerID narrows to one customer; Status filters by lifecycle bucket. Cursor + Limit drive pagination.

type Metadata

type Metadata map[string]any

Metadata is the free-form key/value blob attached to most resources. Values are arbitrary JSON; Go callers can marshal whatever the API accepts on their side. Nil = no metadata.

type NetworkError

type NetworkError struct{ *OpenSettleError }

NetworkError is the transport-layer failure: DNS, ECONNREFUSED, TLS handshake errors, context-deadline. Status is always 0.

func (*NetworkError) Unwrap

func (e *NetworkError) Unwrap() error

Unwrap returns the embedded *OpenSettleError so errors.Is/As can match the base type.

type NotFoundError

type NotFoundError struct{ *OpenSettleError }

NotFoundError signals a 404 — the resource ID doesn't exist or isn't visible to the calling credentials.

func (*NotFoundError) Unwrap

func (e *NotFoundError) Unwrap() error

Unwrap returns the embedded *OpenSettleError so errors.Is/As can match the base type.

type OpenSettleError

type OpenSettleError struct {
	Code      ErrorCode
	Message   string
	Status    int
	RequestID string
	Param     string
}

OpenSettleError is the base type. Every concrete error in this package embeds it, so callers can either match the broad type via errors.As(err, &*OpenSettleError{}) or branch on the specific subtype. Param, when non-empty, names the request field the server rejected (e.g. "amountMinor") — useful for surfacing per-field validation messages.

func (*OpenSettleError) Error

func (e *OpenSettleError) Error() string

Error formats the error for log output. Includes the stable code, HTTP status, and (when available) request ID for support correlation.

type Option

type Option func(*clientConfig)

Option configures a Client. Use NewClient(...).

func WithBaseURL

func WithBaseURL(url string) Option

WithBaseURL overrides the API host. Defaults to https://api.opensettle.io.

func WithHTTPClient

func WithHTTPClient(hc *http.Client) Option

WithHTTPClient replaces the underlying *http.Client. Useful for injecting custom transports, proxies, or mocks. The SDK still applies its own retry and timeout logic on top.

func WithMaxRetries

func WithMaxRetries(n int) Option

WithMaxRetries sets the retry budget for transient failures (5xx, 429, transport errors). Default 3. Set 0 to disable retries entirely.

func WithTestMode

func WithTestMode(test bool) Option

WithTestMode asserts the apiKey environment matches. With true, the SDK refuses sk_live_… keys; with false it refuses sk_test_…. Useful as a circuit breaker in CI. Default is unasserted (either accepted).

func WithTimeout

func WithTimeout(d time.Duration) Option

WithTimeout sets the per-request timeout. Default 30s.

func WithUserAgent

func WithUserAgent(ua string) Option

WithUserAgent appends a caller-supplied product token to the SDK's User-Agent. The default is `opensettle-go/<Version>`; with this option the header becomes `opensettle-go/<Version> <userAgent>`.

type Payment

type Payment struct {
	ID                string        `json:"id"`
	WorkspaceID       string        `json:"workspaceId"`
	CustomerID        *string       `json:"customerId"`
	SubscriptionID    *string       `json:"subscriptionId"`
	InvoiceID         *string       `json:"invoiceId"`
	WalletID          *string       `json:"walletId"`
	AmountMinor       int           `json:"amountMinor"`
	FeeMinor          int           `json:"feeMinor"`
	NetMinor          int           `json:"netMinor"`
	Currency          string        `json:"currency"`
	Token             TokenSymbol   `json:"token"`
	Chain             ChainId       `json:"chain"`
	Status            PaymentStatus `json:"status"`
	FailureReason     *string       `json:"failureReason"`
	Description       *string       `json:"description"`
	TxHash            *string       `json:"txHash"`
	BlockNumber       *int          `json:"blockNumber"`
	Confirmations     int           `json:"confirmations"`
	RefundTxHash      *string       `json:"refundTxHash"`
	RefundAmountMinor *int          `json:"refundAmountMinor"`
	RefundBroadcastAt *string       `json:"refundBroadcastAt"`
	RefundedAt        *string       `json:"refundedAt"`
	RefundReason      *string       `json:"refundReason"`
	CreatedAt         string        `json:"createdAt"`
	ConfirmedAt       *string       `json:"confirmedAt"`
}

Payment is a single on-chain settlement attempt. AmountMinor, FeeMinor, and NetMinor are denominated in minor units of Currency (fiat); the on-chain settlement value is in Token base units, derived at quote time. TxHash is set once the customer broadcasts. Refund fields are populated only after Refund + RefundBroadcast are called.

type PaymentStatus

type PaymentStatus string

PaymentStatus is the on-chain lifecycle of a payment. pending means broadcast-but-unconfirmed; confirmed has met the workspace's required confirmation depth; refunded means a refund tx has confirmed; reorged means a previously-confirmed payment was rolled back by a chain reorganization.

const (
	PaymentPending   PaymentStatus = "pending"
	PaymentConfirmed PaymentStatus = "confirmed"
	PaymentFailed    PaymentStatus = "failed"
	PaymentRefunded  PaymentStatus = "refunded"
	PaymentReorged   PaymentStatus = "reorged"
)

type PaymentsResource

type PaymentsResource struct {
	// contains filtered or unexported fields
}

PaymentsResource exposes /v1/workspaces/<ws>/payments.

func (*PaymentsResource) List

List returns one page of payments for the workspace. Pass nil for an unfiltered first page; use ListIter for cursor-driven full iteration.

func (*PaymentsResource) ListIter added in v0.3.0

func (r *PaymentsResource) ListIter(ctx context.Context, query *ListPaymentsQuery) *Iter[Payment]

ListIter returns a cursor-driven iterator over all payments matching the query.

func (*PaymentsResource) Refund

Refund initiates a refund. Returns a multi-key envelope {payment, unsignedTx} — the merchant's wallet signs unsignedTx and broadcasts it; OpenSettle never holds funds.

Step-up auth (AAL=2) is required on this route, surfaced as *StepUpRequiredError for API-key callers. Sessions get through if they re-authed within freshWithinSeconds.

Auto-attaches an Idempotency-Key; supply WithIdempotencyKey to use a caller-chosen key (recommended for refund flows where the caller has a natural deterministic id, e.g. the refund's row id in your DB).

func (*PaymentsResource) RefundBroadcast

func (r *PaymentsResource) RefundBroadcast(ctx context.Context, paymentID string, input RecordRefundBroadcastRequest, opts ...RequestOption) (*Payment, error)

RefundBroadcast tells OpenSettle the merchant has signed and broadcast the refund tx. The chain reader picks it up and flips status to refunded once it confirms. Auto-attaches an Idempotency-Key; supply WithIdempotencyKey to override.

func (*PaymentsResource) Retrieve

func (r *PaymentsResource) Retrieve(ctx context.Context, paymentID string) (*Payment, error)

Retrieve fetches a single payment by ID.

type Price

type Price struct {
	ID          string        `json:"id"`
	WorkspaceID string        `json:"workspaceId"`
	ProductID   string        `json:"productId"`
	Amount      int           `json:"amount"`
	Currency    string        `json:"currency"`
	Interval    PriceInterval `json:"interval"`
	Active      bool          `json:"active"`
	Metadata    Metadata      `json:"metadata"`
	CreatedAt   string        `json:"createdAt"`
}

Price is a (product, amount, interval) tuple. Amount is in minor units (e.g. cents). Currency is the fiat denomination (USD, EUR, …); the chain + token used to settle are chosen per-charge, not on the price.

type PriceInterval

type PriceInterval string

PriceInterval is the billing cadence of a Price. "one_time" is a non-recurring price suitable for invoices and single-payment checkouts; the others are recurring intervals consumed by subscriptions.

const (
	PriceOneTime PriceInterval = "one_time"
	PriceWeek    PriceInterval = "week"
	PriceMonth   PriceInterval = "month"
	PriceYear    PriceInterval = "year"
)

type Product

type Product struct {
	ID          string   `json:"id"`
	WorkspaceID string   `json:"workspaceId"`
	Name        string   `json:"name"`
	Description *string  `json:"description"`
	Active      bool     `json:"active"`
	Metadata    Metadata `json:"metadata"`
	CreatedAt   string   `json:"createdAt"`
}

Product is a sellable item in the workspace catalog. Active=false hides it from new checkouts but does not affect subscriptions already using its prices.

type ProductsResource

type ProductsResource struct {
	// contains filtered or unexported fields
}

ProductsResource exposes /v1/workspaces/<ws>/products and the nested /prices collection.

func (*ProductsResource) Create

func (r *ProductsResource) Create(ctx context.Context, input CreateProductRequest, opts ...RequestOption) (*Product, error)

Create makes a new product. Auto-attaches an Idempotency-Key; supply WithIdempotencyKey to use a caller-chosen key instead.

func (*ProductsResource) CreatePrice

func (r *ProductsResource) CreatePrice(ctx context.Context, productID string, input CreatePriceRequest, opts ...RequestOption) (*Price, error)

CreatePrice attaches a new price to a product. Auto-attaches an Idempotency-Key; supply WithIdempotencyKey to override.

func (*ProductsResource) Delete

func (r *ProductsResource) Delete(ctx context.Context, productID string) error

Delete is a hard-delete. Returns *ConflictError (409) if any subscription still references the product.

func (*ProductsResource) DeletePrice

func (r *ProductsResource) DeletePrice(ctx context.Context, priceID string) error

DeletePrice hard-deletes a price. Returns *ConflictError (409) if any subscription still references it.

func (*ProductsResource) List

List returns one page of products. Pass nil for an unfiltered first page; use ListIter for cursor-driven full iteration.

func (*ProductsResource) ListIter added in v0.3.0

func (r *ProductsResource) ListIter(ctx context.Context, query *ListProductsQuery) *Iter[Product]

ListIter returns a cursor-driven iterator over all products matching the query.

func (*ProductsResource) ListPrices

func (r *ProductsResource) ListPrices(ctx context.Context, productID string) ([]Price, error)

ListPrices returns the prices attached to a product.

func (*ProductsResource) Retrieve

func (r *ProductsResource) Retrieve(ctx context.Context, productID string) (*Product, error)

Retrieve fetches a single product by ID.

func (*ProductsResource) Update

func (r *ProductsResource) Update(ctx context.Context, productID string, input UpdateProductRequest) (*Product, error)

Update patches a product. Fields left nil on input are unchanged.

type ProrationMode

type ProrationMode string

ProrationMode controls when a plan change takes effect.

const (
	ProrationImmediately ProrationMode = "immediately"
	ProrationAtPeriodEnd ProrationMode = "at_period_end"
)

type RateLimitError

type RateLimitError struct {
	*OpenSettleError
	RetryAfter float64
}

RateLimitError carries the optional Retry-After hint advertised by the API. Value is in seconds. A zero RetryAfter means the server didn't advertise one — caller should fall back to its own backoff.

func (*RateLimitError) Unwrap

func (e *RateLimitError) Unwrap() error

Unwrap returns the embedded *OpenSettleError so errors.Is/As can match the base type.

type RecordRefundBroadcastRequest

type RecordRefundBroadcastRequest struct {
	RefundTxHash string `json:"refundTxHash"`
}

RecordRefundBroadcastRequest is the body for POST /payments/<id>/refund/broadcast. The caller supplies the tx hash returned by their wallet after broadcasting the unsigned refund tx.

type RequestOption added in v0.5.0

type RequestOption func(*requestConfig)

RequestOption is a per-call knob that can be supplied to any money-adjacent write method (Create, Refund, RotateSecret, etc.). The SDK applies all options after its own defaults, so options always win.

Today the only option is WithIdempotencyKey; the type is exported so the SDK can grow per-call options without breaking method signatures.

func WithIdempotencyKey added in v0.5.0

func WithIdempotencyKey(key string) RequestOption

WithIdempotencyKey supplies a caller-chosen Idempotency-Key for this request. Use this when you want to assign a deterministic key (e.g. your own order id) so that retries from different machines or processes collapse to the same server-side operation.

If WithIdempotencyKey is not supplied, the SDK auto-generates a UUIDv4 key for every money-adjacent write. Either way, the same key is reused across all retry attempts of a single call.

Example
package main

import (
	"context"
	"fmt"
	"log"

	opensettle "github.com/OpenSettle/opensettle-sdk-go"
)

func main() {
	client, _ := opensettle.NewClient("sk_test_x", "ws_test", opensettle.WithTestMode(true))
	ctx := context.Background()

	// Use the merchant's order id as the idempotency key — any number of
	// retries (from this machine or another) will collapse to the same
	// server-side checkout.
	checkout, err := client.Checkouts.Create(ctx,
		opensettle.CreateCheckoutRequest{
			Mode:       opensettle.CheckoutPayment,
			CustomerID: "cu_123",
			InvoiceID:  "in_123",
			SuccessURL: "https://example.com/thanks",
		},
		opensettle.WithIdempotencyKey("order:42"),
	)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(checkout.ID)
}

type RotateWebhookSecretRequest

type RotateWebhookSecretRequest struct {
	GraceSeconds int `json:"graceSeconds,omitempty"`
}

RotateWebhookSecretRequest is the body for POST /webhook_endpoints/<id>/rotate. GraceSeconds is the dual-signing window during which both old and new secrets produce valid signatures; default is server-side when zero.

type RotateWebhookSecretResponse deprecated

type RotateWebhookSecretResponse = CreateWebhookEndpointResponse

RotateWebhookSecretResponse is now an alias for CreateWebhookEndpointResponse — the rotate endpoint returns the same {endpoint, signingSecret} envelope as create.

Deprecated: use *CreateWebhookEndpointResponse directly. This alias will be removed in a future release.

type SettlementError

type SettlementError struct{ *OpenSettleError }

SettlementError covers chain-level failures: reverted txs, insufficient confirmations, signing/re-approval required.

func (*SettlementError) Unwrap

func (e *SettlementError) Unwrap() error

Unwrap returns the embedded *OpenSettleError so errors.Is/As can match the base type.

type StepUpRequiredError

type StepUpRequiredError struct{ *OpenSettleError }

StepUpRequiredError is returned when a route requires AAL=2 (step-up auth) that the current session hasn't met. API-key callers see this on money-adjacent writes that need explicit re-authorization.

func (*StepUpRequiredError) Unwrap

func (e *StepUpRequiredError) Unwrap() error

Unwrap returns the embedded *OpenSettleError so errors.Is/As can match the base type.

type Subscription

type Subscription struct {
	ID                 string             `json:"id"`
	WorkspaceID        string             `json:"workspaceId"`
	CustomerID         string             `json:"customerId"`
	ProductID          string             `json:"productId"`
	PriceID            string             `json:"priceId"`
	AmountMinor        int                `json:"amountMinor"`
	Currency           string             `json:"currency"`
	Chain              ChainId            `json:"chain"`
	Token              TokenSymbol        `json:"token"`
	Status             SubscriptionStatus `json:"status"`
	Autopay            AutopayMode        `json:"autopay"`
	AllowanceTx        *string            `json:"allowanceTx"`
	AllowanceRemaining *int               `json:"allowanceRemaining"`
	TrialEndsAt        *string            `json:"trialEndsAt"`
	StartedAt          string             `json:"startedAt"`
	CurrentPeriodEnd   string             `json:"currentPeriodEnd"`
	NextBillingDate    string             `json:"nextBillingDate"`
	CanceledAt         *string            `json:"canceledAt"`
	CancelReason       *string            `json:"cancelReason"`
	PausedAt           *string            `json:"pausedAt"`
	MRRMinor           int                `json:"mrrMinor"`
	Metadata           Metadata           `json:"metadata"`
	CreatedAt          string             `json:"createdAt"`
}

Subscription is a recurring billing arrangement. AmountMinor is in minor units of Currency (fiat); MRRMinor is the normalized monthly recurring revenue contribution. AllowanceTx/AllowanceRemaining are populated only when Autopay=allowance. CurrentPeriodEnd and NextBillingDate are server-managed.

type SubscriptionStatus

type SubscriptionStatus string

SubscriptionStatus is the lifecycle bucket of a subscription. trialing is pre-billing; active is current; past_due is one or more failed renewals; paused stops billing without canceling; canceled is terminal.

const (
	SubTrialing SubscriptionStatus = "trialing"
	SubActive   SubscriptionStatus = "active"
	SubPastDue  SubscriptionStatus = "past_due"
	SubPaused   SubscriptionStatus = "paused"
	SubCanceled SubscriptionStatus = "canceled"
)

type SubscriptionsResource

type SubscriptionsResource struct {
	// contains filtered or unexported fields
}

SubscriptionsResource exposes /v1/workspaces/<ws>/subscriptions.

func (*SubscriptionsResource) Cancel

Cancel ends a subscription. Mode controls timing (immediately vs at_period_end). Default is at_period_end when input.Mode is empty. Reason is recorded on the audit log.

func (*SubscriptionsResource) ChangePlan

func (r *SubscriptionsResource) ChangePlan(ctx context.Context, subID string, input ChangePlanRequest, opts ...RequestOption) (*Subscription, error)

ChangePlan swaps the subscription to a new price. ProrationMode controls whether the customer is billed immediately for the delta or at the next period boundary. Auto-attaches an Idempotency-Key; supply WithIdempotencyKey to override.

func (*SubscriptionsResource) Create

Create starts a new subscription. Auto-attaches an Idempotency-Key; supply WithIdempotencyKey to use a caller-chosen key instead.

func (*SubscriptionsResource) List

List returns one page of subscriptions. Pass nil for an unfiltered first page; use ListIter for cursor-driven full iteration.

func (*SubscriptionsResource) ListIter added in v0.3.0

ListIter returns a cursor-driven iterator over all subscriptions matching the query.

func (*SubscriptionsResource) Pause

func (r *SubscriptionsResource) Pause(ctx context.Context, subID string) (*Subscription, error)

Pause stops billing on a subscription without canceling it. Resume to continue. No proration; the next billing date shifts forward by the paused interval.

func (*SubscriptionsResource) Resume

func (r *SubscriptionsResource) Resume(ctx context.Context, subID string) (*Subscription, error)

Resume reactivates a paused subscription. Billing restarts from the shifted next-billing date set when the subscription was paused.

func (*SubscriptionsResource) Retrieve

func (r *SubscriptionsResource) Retrieve(ctx context.Context, subID string) (*Subscription, error)

Retrieve fetches a single subscription by ID.

type TestWebhookEndpointRequest

type TestWebhookEndpointRequest struct {
	EventType string `json:"eventType"`
}

TestWebhookEndpointRequest is the body for POST /webhook_endpoints/<id>/test. EventType is the event-type name to fire as a sample (e.g. "payment.confirmed"); the payload is server-generated.

type TestWebhookEndpointResponse

type TestWebhookEndpointResponse struct {
	OK        bool `json:"ok"`
	Status    int  `json:"status"`
	LatencyMs int  `json:"latencyMs"`
}

TestWebhookEndpointResponse reports the result of a synchronous test delivery. Status is the HTTP status the endpoint returned to OpenSettle; LatencyMs is the round-trip latency observed by the dispatcher. OK is true when Status was 2xx.

type TokenSymbol

type TokenSymbol string

TokenSymbol is a supported stablecoin. The platform settles only in USDC and USDT.

const (
	TokenUSDC TokenSymbol = "USDC"
	TokenUSDT TokenSymbol = "USDT"
)

type UnsignedEVMTx added in v0.5.0

type UnsignedEVMTx struct {
	To              string `json:"to"`
	Data            string `json:"data"`
	Value           string `json:"value"`
	ChainID         int    `json:"chainId"`
	TokenAddress    string `json:"tokenAddress"`
	Recipient       string `json:"recipient"`
	AmountBaseUnits string `json:"amountBaseUnits"`
}

UnsignedEVMTx is the EVM-shaped portion of an unsigned refund payload. Value is the native-currency wei value (typically "0" for ERC-20 transfers); AmountBaseUnits is the token amount in its smallest unit (e.g. 6-decimal USDC). The merchant signs this with their wallet and broadcasts; OpenSettle never holds keys.

type UnsignedEvmTx deprecated

type UnsignedEvmTx = UnsignedEVMTx

UnsignedEvmTx is the pre-Go-idiomatic name for UnsignedEVMTx. Kept as a type alias for backward compatibility; new code should prefer UnsignedEVMTx.

Deprecated: use UnsignedEVMTx instead.

type UnsignedTxEnvelope

type UnsignedTxEnvelope struct {
	Chain        ChainId        `json:"chain"`
	Token        TokenSymbol    `json:"token"`
	To           string         `json:"to"`
	AmountMinor  int            `json:"amountMinor"`
	Memo         string         `json:"memo,omitempty"`
	Instructions string         `json:"instructions"`
	EVM          *UnsignedEvmTx `json:"evm,omitempty"`
}

UnsignedTxEnvelope wraps a chain-agnostic refund description plus an optional chain-specific payload (today: EVM). Instructions is a human-readable hint for wallet UIs. AmountMinor is in fiat minor units; the chain-specific block carries the token-base-unit amount.

type UpdateCustomerRequest

type UpdateCustomerRequest struct {
	Name     *string  `json:"name,omitempty"`
	Wallet   *string  `json:"wallet,omitempty"`
	Country  *string  `json:"country,omitempty"`
	Metadata Metadata `json:"metadata,omitempty"`
}

UpdateCustomerRequest is the body for PATCH /customers/<id>. Fields are PATCH-style: omit a field (leave the pointer nil) to leave the existing value unchanged. To clear a string field, pass a pointer to the empty string.

type UpdateProductRequest

type UpdateProductRequest struct {
	Name        *string  `json:"name,omitempty"`
	Description *string  `json:"description,omitempty"`
	Active      *bool    `json:"active,omitempty"`
	Metadata    Metadata `json:"metadata,omitempty"`
}

UpdateProductRequest is the body for PATCH /products/<id>. Fields are PATCH-style: omit a field (leave the pointer nil) to leave the existing value unchanged. Setting Active=false hides the product from new checkouts.

type UpdateWebhookEndpointRequest

type UpdateWebhookEndpointRequest struct {
	URL         *string                `json:"url,omitempty"`
	Description *string                `json:"description,omitempty"`
	Events      []string               `json:"events,omitempty"`
	Status      *WebhookEndpointStatus `json:"status,omitempty"`
}

UpdateWebhookEndpointRequest is the body for PATCH /webhook_endpoints/<id>. Fields are PATCH-style: omit a field (leave the pointer nil) to leave the existing value unchanged. Passing a non-nil Events replaces the allow-list entirely.

type WaitOptions added in v0.3.0

type WaitOptions struct {
	// Hard timeout. Defaults to 2 minutes when zero.
	Timeout time.Duration
	// Poll interval. Defaults to 2 seconds when zero.
	Interval time.Duration
	// contains filtered or unexported fields
}

WaitOptions tunes polling behaviour.

type WaitTimeoutError added in v0.3.0

type WaitTimeoutError struct {
	ResourceID string
	Timeout    time.Duration
	// Last is the most-recently-observed resource (typed as the
	// concrete *T that the Retrieve closure returns).
	Last any
}

WaitTimeoutError is returned when the target state is not reached before the timeout elapses. It carries the last-observed resource as an opaque any value — type-assert to the expected pointer type to inspect it.

func (*WaitTimeoutError) Error added in v0.3.0

func (e *WaitTimeoutError) Error() string

type WebhookEndpoint

type WebhookEndpoint struct {
	ID                 string                `json:"id"`
	WorkspaceID        string                `json:"workspaceId"`
	URL                string                `json:"url"`
	Description        *string               `json:"description"`
	Events             []string              `json:"events"`
	Status             WebhookEndpointStatus `json:"status"`
	SuccessRate        float64               `json:"successRate"`
	RotationGraceUntil *string               `json:"rotationGraceUntil"`
	CreatedAt          string                `json:"createdAt"`
}

WebhookEndpoint is a merchant-configured HTTPS destination for event deliveries. SuccessRate is the server-computed rolling success ratio over the recent delivery window. RotationGraceUntil is set during a signing-secret rotation: until then, both the old and new secrets produce valid signatures.

type WebhookEndpointStatus

type WebhookEndpointStatus string

WebhookEndpointStatus is the delivery state of a webhook endpoint. disabled stops new deliveries (existing in-flight retries still run); enabled is the normal state.

const (
	WebhookEnabled  WebhookEndpointStatus = "enabled"
	WebhookDisabled WebhookEndpointStatus = "disabled"
)

type WebhookEndpointsResource

type WebhookEndpointsResource struct {
	// contains filtered or unexported fields
}

WebhookEndpointsResource exposes /v1/workspaces/<ws>/webhook_endpoints.

func (*WebhookEndpointsResource) Create

Create makes a new endpoint. The response includes the plaintext signing secret exactly once — store it immediately. Multi-key envelope {endpoint, signingSecret} preserved.

Auto-attaches an Idempotency-Key; supply WithIdempotencyKey to override.

func (*WebhookEndpointsResource) Delete

func (r *WebhookEndpointsResource) Delete(ctx context.Context, endpointID string) error

Delete permanently removes a webhook endpoint. In-flight retries are dropped; consider Update with Status=disabled instead if you want to pause without losing the configuration.

func (*WebhookEndpointsResource) List

List returns every webhook endpoint configured for the workspace. The endpoint is small enough that the API doesn't paginate.

func (*WebhookEndpointsResource) Retrieve

func (r *WebhookEndpointsResource) Retrieve(ctx context.Context, endpointID string) (*WebhookEndpoint, error)

Retrieve fetches a single webhook endpoint by ID. The signing secret is never returned here — only Create and RotateSecret produce it.

func (*WebhookEndpointsResource) RotateSecret

RotateSecret rotates the signing secret. Returns the same {endpoint, signingSecret} envelope as Create — store SigningSecret immediately. Step-up auth (AAL=2) required; API-key callers receive *StepUpRequiredError. Auto-attaches an Idempotency-Key; supply WithIdempotencyKey to override.

func (*WebhookEndpointsResource) Test

Test fires a sample event at the endpoint synchronously to verify wiring. The returned status is the response status the endpoint returned to OpenSettle.

func (*WebhookEndpointsResource) Update

Update patches a webhook endpoint. Fields left nil on input are unchanged. A non-nil Events replaces the allow-list entirely.

Directories

Path Synopsis
Package webhooks verifies signed OpenSettle webhook deliveries.
Package webhooks verifies signed OpenSettle webhook deliveries.

Jump to

Keyboard shortcuts

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