tipalti

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Aug 6, 2026 License: MIT Imports: 26 Imported by: 0

README

tipalti-go

A production-grade Go client for the full Tipalti API surface: the modern OAuth2 REST API, the legacy HMAC-signed SOAP Payee/Payer API, and the Procurement REST API.

Zero external dependencies. go.mod declares none — the entire module is built on the standard library (net/http, encoding/json, encoding/xml, crypto/hmac, context, generics). Nothing to vendor, nothing to audit upstream.

Architecture: Domain-Driven Design

tipalti-go/
  tipalti.go                        # the ONLY .go file at the module root:
                                     # public Client facade + re-exports
  internal/
    domain/                         # entities, value objects, Port interfaces
      shared/                       #   — no HTTP/XML/JSON knowledge at all
      payee/  invoice/  payment/
      soap/   purchaseorder/  employee/
    application/                    # use-case services orchestrating a
      payeeapp/  invoiceapp/        #   domain Port (e.g. adding pagination
      paymentapp/                   #   streaming) — still transport-agnostic
      soappayeeapp/  soappayerapp/
      procurementapp/
    infrastructure/                 # adapters implementing each domain Port
      httptransport/                #   against Tipalti's actual wire format
      oauth2/
      restadapter/
      soapadapter/
      procurementadapter/
    config/  apperrors/  pagination/
    webhook/  ratelimit/  telemetry/

The dependency direction is strictly one-way and acyclic: domain ← application ← infrastructure ← root. Domain packages import nothing from this module except other domain packages; verified by grep in CI (see below) as well as go vet.

Since Go's internal/ convention makes those packages unimportable outside this module, tipalti.go re-exports every public type via type aliases (type Resource = shared.Resource, etc.) so external callers get a clean, flat API without ever touching the internal layout.

Installation

go get github.com/iamkanishka/tipalti-go

Quick start

client, err := tipalti.New(
	tipalti.WithMode(tipalti.ModeSandbox),
	tipalti.WithREST("client-id", "client-secret"),
	tipalti.WithSOAP("payer-name", "api-key"),
	tipalti.WithProcurement("procurement-api-key"),
)
if err != nil {
	log.Fatal(err)
}

// Modern REST API
page, err := client.Payees.List(ctx, tipalti.PayeeListParams{})

stream := client.PayeeStream(ctx, tipalti.PayeeListParams{})
for payee := range stream.Items() {
	fmt.Println(payee.ID(), payee.String("name"))
}
if err := stream.Err(); err != nil {
	log.Fatal(err)
}

// Legacy SOAP API
result, err := client.SOAP.Payer.ProcessPayments(ctx, []map[string]any{
	{"idap": "vendor-123", "amount": 100.00, "currency": "USD", "refCode": "pay-1"},
}, tipalti.ProcessPaymentsOptions{PaymentGroupTitle: "Weekly payout"})

// Procurement REST API
pos, err := client.Procurement.PurchaseOrders.List(ctx, tipalti.PurchaseOrderListParams{})

Only populate the credential options for the API families you actually use — a Client built with just WithSOAP is fine as long as you only call client.SOAP.* methods.

Error handling

Every error is one of six typed errors; use errors.As:

_, err := client.Payees.Get(ctx, "p_123")

var authErr *tipalti.AuthenticationError
var rlErr *tipalti.RateLimitError
var valErr *tipalti.ValidationError
var faultErr *tipalti.SOAPFaultError

switch {
case errors.As(err, &authErr):
	// bad/expired credentials
case errors.As(err, &rlErr):
	// rate limited; rlErr.RetryAfter has the hint, if any
case errors.As(err, &valErr):
	// malformed request; valErr.Errors has field-level details
case errors.As(err, &faultErr):
	// SOAP <soap:Fault>; faultErr.FaultCode / FaultString
}

Pagination

Every REST list use case has a matching client.<X>Stream method returning a channel-based Stream[Resource] (channel-based rather than iter.Seq since this module targets Go 1.22):

stream := client.InvoiceStream(ctx, tipalti.InvoiceListParams{Status: "pending"})
items, err := tipalti.Collect(stream) // or range over stream.Items()

SOAP signing

HMAC-SHA256 request signing is handled automatically — every client.SOAP.Payee/client.SOAP.Payer method knows its operation's EAT (Encryption Additional Terms) parameter and folds it into the signed request for you. All 45 legacy operations (21 Payee + 24 Payer) are covered.

Procurement employee import

csv, _ := os.ReadFile("employees.csv")
_, err := client.Procurement.Employees.ImportEmployees(ctx, csv)

IPN webhooks

func handleTipaltiWebhook(w http.ResponseWriter, r *http.Request) {
	body, _ := io.ReadAll(r.Body)
	event, err := tipalti.ParseWebhook(string(body))
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	handleEvent(event.EventType(), event)
}

Telemetry

client, _ := tipalti.New(
	tipalti.WithREST(id, secret),
	tipalti.WithTelemetryHook(tipalti.TelemetryFunc(func(e tipalti.TelemetryEvent) {
		log.Printf("%s.%s -> status=%d err=%v in %s", e.API, e.Operation, e.Status, e.Err, e.Duration)
	})),
)

Rate limiting

limiter := tipalti.NewRateLimiter(5, time.Minute) // Procurement API's documented PO-update limit
if err := limiter.Wait(ctx); err != nil {
	return err
}
client.Procurement.PurchaseOrders.Update(ctx, attrs)

Design notes

  • Concurrency: the OAuth2 TokenManager is safe for concurrent use — concurrent callers during a token refresh share the single in-flight request rather than each firing their own. Verified with go test -race.
  • Generics: Stream[T] (pagination) and a couple of small internal helpers use generics; no code generation.
  • REST endpoint shapes (Payees/Invoices/Payments) follow Tipalti's documented conventions for the modern REST API — see the doc comment on internal/infrastructure/restadapter/client.go if your instance's exact response envelope differs; the request/auth/error-handling machinery there is meant to be reused as-is.

Quality

gofmt -l .                    # clean
go vet ./...                  # clean
go build ./...                # clean
go test ./... -race -count=1  # all passing
golangci-lint run ./...       # 0 issues

.golangci.yml enables a production-grade linter set well beyond the tool's 6-linter default — including gosec, errorlint, gocyclo, bodyclose, contextcheck, exhaustive, prealloc, unparam, revive, and more (see the file for the full list). All clean, verified in a fresh checkout.

golangci-lint itself is installed from a GitHub release binary rather than go install, and dependencies are fetched via GOPROXY=direct — see comments in this repo's build notes if proxy.golang.org isn't reachable in your environment either; none of that affects consuming this module, since it has zero dependencies of its own.

License

MIT

Documentation

Overview

Package tipalti is a production-grade Go client for the full Tipalti API surface: the modern OAuth2 REST API, the legacy HMAC-signed SOAP Payee/Payer API, and the Procurement REST API.

Architecture

Internally this module is organized by Domain-Driven Design layers under internal/: domain (entities, value objects, and Port interfaces — no HTTP/XML/JSON knowledge), application (use-case services that orchestrate a domain Port, e.g. adding pagination streaming), infrastructure (adapters implementing each domain Port against Tipalti's actual REST/SOAP/Procurement wire formats). This file is the only .go file at the module root: it wires the layers together behind a single public Client and re-exports the types external callers need, since Go's internal/ convention means none of the packages above are importable outside this module.

Quick start

client, err := tipalti.New(
	tipalti.WithMode(tipalti.ModeSandbox),
	tipalti.WithREST("client-id", "client-secret"),
	tipalti.WithSOAP("payer-name", "api-key"),
	tipalti.WithProcurement("procurement-api-key"),
)
if err != nil {
	log.Fatal(err)
}

page, err := client.Payees.List(ctx, tipalti.PayeeListParams{})

result, err := client.SOAP.Payer.ProcessPayments(ctx, []map[string]any{
	{"idap": "vendor-123", "amount": 100.00, "currency": "USD", "refCode": "pay-1"},
}, tipalti.ProcessPaymentsOptions{PaymentGroupTitle: "Weekly payout"})

pos, err := client.Procurement.PurchaseOrders.List(ctx, tipalti.PurchaseOrderListParams{})

Only populate the credential options for the API families you actually use — a Client built with just WithSOAP is fine as long as you only call client.SOAP.* methods.

Index

Constants

View Source
const (
	ModeSandbox    = config.ModeSandbox
	ModeProduction = config.ModeProduction
)
View Source
const (
	PayeeStatusActive    = soappayeeapp.PayeeStatusActive
	PayeeStatusSuspended = soappayeeapp.PayeeStatusSuspended
	PayeeStatusBlocked   = soappayeeapp.PayeeStatusBlocked
)

Variables

View Source
var (
	WithMode               = config.WithMode
	WithREST               = config.WithREST
	WithSOAP               = config.WithSOAP
	WithProcurement        = config.WithProcurement
	WithRESTBaseURL        = config.WithRESTBaseURL
	WithSOAPBaseURL        = config.WithSOAPBaseURL
	WithProcurementBaseURL = config.WithProcurementBaseURL
	WithSOAPVersion        = config.WithSOAPVersion
	WithReceiveTimeout     = config.WithReceiveTimeout
	WithMaxRetries         = config.WithMaxRetries
	WithTelemetryHook      = config.WithTelemetryHook
)

Functions

func Collect

func Collect[T any](s *Stream[T]) ([]T, error)

Collect drains an entire Stream into a slice. Convenient for small result sets; for large ones, range over Items() instead.

Types

type APIError

type APIError = apperrors.APIError

type AuthenticationError

type AuthenticationError = apperrors.AuthenticationError

type Client

type Client struct {
	// Payees, Invoices, Payments cover the modern OAuth2 REST API.
	Payees   *payeeapp.Service
	Invoices *invoiceapp.Service
	Payments *paymentapp.Service

	// SOAP covers the legacy HMAC-signed SOAP API.
	SOAP struct {
		Payee *soappayeeapp.Service
		Payer *soappayerapp.Service
	}

	// Procurement covers the Procurement REST API.
	Procurement struct {
		PurchaseOrders *procurementapp.PurchaseOrderService
		Employees      *procurementapp.EmployeeService
	}
}

Client is the entry point for every Tipalti API family this module covers. Build one with New.

func New

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

New builds a Client from the given options. See With* below.

func (*Client) InvoiceStream

func (c *Client) InvoiceStream(ctx context.Context, params InvoiceListParams) *Stream[Resource]

InvoiceStream returns every invoice across all pages, fetching pages on demand as the returned Stream's channel is consumed.

func (*Client) PayeeStream

func (c *Client) PayeeStream(ctx context.Context, params PayeeListParams) *Stream[Resource]

PayeeStream returns every payee across all pages, fetching pages on demand as the returned Stream's channel is consumed.

func (*Client) PaymentStream

func (c *Client) PaymentStream(ctx context.Context, params PaymentListParams) *Stream[Resource]

PaymentStream returns every payment across all pages, fetching pages on demand as the returned Stream's channel is consumed.

type InvoiceListParams

type InvoiceListParams = invoice.ListParams

type Mode

type Mode = config.Mode

Mode selects sandbox vs. production default base URLs.

type NetworkError

type NetworkError = apperrors.NetworkError

type Option

type Option = config.Option

Option configures a Client. See the With* functions.

type Page

type Page = shared.Page

Page is a single page of results from a cursor-paginated list endpoint.

type PayeeListParams

type PayeeListParams = payee.ListParams

type PayeeStatus

type PayeeStatus = soappayeeapp.PayeeStatus

type PaymentListParams

type PaymentListParams = payment.ListParams

type ProcessPaymentsOptions

type ProcessPaymentsOptions = soappayerapp.ProcessPaymentsOptions

type PurchaseOrderListParams

type PurchaseOrderListParams = purchaseorder.ListParams

type RateLimitError

type RateLimitError = apperrors.RateLimitError

type RateLimiter

type RateLimiter = ratelimit.Limiter

RateLimiter is an optional, self-contained token-bucket rate limiter, useful for staying under the Procurement API's documented request limits client-side rather than relying solely on 429 retries.

func NewRateLimiter

func NewRateLimiter(rate int, per time.Duration) *RateLimiter

NewRateLimiter builds a RateLimiter allowing rate operations per the given duration (e.g. NewRateLimiter(5, time.Minute)).

type Resource

type Resource = shared.Resource

Resource is a decoded JSON API resource returned by REST/Procurement calls.

type SOAPFaultError

type SOAPFaultError = apperrors.SOAPFaultError

type SOAPResult

type SOAPResult = soap.Result

SOAPResult is the parsed, flattened set of child elements from a SOAP operation's response element.

type Stream

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

Stream is a lazily-fetched sequence of paginated results. See PayeeStream et al.

func (*Stream[T]) Err

func (s *Stream[T]) Err() error

Err returns the error that stopped iteration, if any. Must be called after the Items() channel has been fully drained/closed.

func (*Stream[T]) Items

func (s *Stream[T]) Items() <-chan T

Items returns the channel to range over.

type TelemetryEvent

type TelemetryEvent = telemetry.RequestEvent

TelemetryEvent describes one completed (or failed) HTTP request attempt.

type TelemetryFunc

type TelemetryFunc = telemetry.Func

TelemetryFunc adapts a plain function to the TelemetryHook interface.

type TelemetryHook

type TelemetryHook = telemetry.Hook

TelemetryHook receives a callback for every HTTP request attempt this module makes, across all three API families. See WithTelemetryHook.

type UploadURL

type UploadURL = employee.UploadURL

type ValidationError

type ValidationError = apperrors.ValidationError

type WebhookEvent

type WebhookEvent = webhook.Event

WebhookEvent is a parsed Tipalti IPN notification. See ParseWebhook.

func ParseWebhook

func ParseWebhook(rawBody string) (WebhookEvent, error)

ParseWebhook parses a raw application/x-www-form-urlencoded IPN request body. See the webhook package doc comment (internal/webhook/webhook.go) for Tipalti's IPN delivery model and verification guidance.

Directories

Path Synopsis
internal
apperrors
Package apperrors defines the typed error hierarchy returned by every package in this module.
Package apperrors defines the typed error hierarchy returned by every package in this module.
application/invoiceapp
Package invoiceapp orchestrates invoice use cases over invoice.Port.
Package invoiceapp orchestrates invoice use cases over invoice.Port.
application/payeeapp
Package payeeapp orchestrates payee use cases over the payee.Port, adding cross-cutting behavior (pagination streaming) the domain port itself doesn't need to know about.
Package payeeapp orchestrates payee use cases over the payee.Port, adding cross-cutting behavior (pagination streaming) the domain port itself doesn't need to know about.
application/paymentapp
Package paymentapp orchestrates payment use cases over payment.Port.
Package paymentapp orchestrates payment use cases over payment.Port.
application/procurementapp
Package procurementapp orchestrates Procurement API use cases: purchase order sync and the employee CSV import flow.
Package procurementapp orchestrates Procurement API use cases: purchase order sync and the employee CSV import flow.
application/soappayeeapp
Package soappayeeapp provides typed wrappers over soap.Caller for every legacy SOAP Payee Functions operation.
Package soappayeeapp provides typed wrappers over soap.Caller for every legacy SOAP Payee Functions operation.
application/soappayerapp
Package soappayerapp provides typed wrappers over soap.Caller for every legacy SOAP Payer Functions operation.
Package soappayerapp provides typed wrappers over soap.Caller for every legacy SOAP Payer Functions operation.
config
Package config holds credentials and connection settings for all three Tipalti API families, built via functional options.
Package config holds credentials and connection settings for all three Tipalti API families, built via functional options.
domain/employee
Package employee defines the domain contract for the Procurement REST API's bulk employee CSV import flow (get a signed upload URL, upload the CSV, trigger the import).
Package employee defines the domain contract for the Procurement REST API's bulk employee CSV import flow (get a signed upload URL, upload the CSV, trigger the import).
domain/invoice
Package invoice defines the domain contract for the modern REST API's invoice/bill management operations.
Package invoice defines the domain contract for the modern REST API's invoice/bill management operations.
domain/payee
Package payee defines the domain contract for the modern REST API's payee management operations.
Package payee defines the domain contract for the modern REST API's payee management operations.
domain/payment
Package payment defines the domain contract for the modern REST API's mass-payout initiation and tracking operations.
Package payment defines the domain contract for the modern REST API's mass-payout initiation and tracking operations.
domain/purchaseorder
Package purchaseorder defines the domain contract for the Procurement REST API's purchase-order sync operations.
Package purchaseorder defines the domain contract for the Procurement REST API's purchase-order sync operations.
domain/shared
Package shared holds value objects and types used across every domain package (payee, invoice, payment, purchaseorder, employee).
Package shared holds value objects and types used across every domain package (payee, invoice, payment, purchaseorder, employee).
domain/soap
Package soap defines the domain contract for the legacy, HMAC-signed SOAP API shared by both the Payee and Payer Functions services.
Package soap defines the domain contract for the legacy, HMAC-signed SOAP API shared by both the Payee and Payer Functions services.
infrastructure/httptransport
Package httptransport is the shared HTTP transport used by every Tipalti API adapter (REST, SOAP, Procurement).
Package httptransport is the shared HTTP transport used by every Tipalti API adapter (REST, SOAP, Procurement).
infrastructure/oauth2
Package oauth2 caches and refreshes OAuth 2.0 client-credentials access tokens for the modern Tipalti REST API.
Package oauth2 caches and refreshes OAuth 2.0 client-credentials access tokens for the modern Tipalti REST API.
infrastructure/procurementadapter
Package procurementadapter implements the purchaseorder/employee domain ports against Tipalti's Procurement REST API (static x-api-key auth).
Package procurementadapter implements the purchaseorder/employee domain ports against Tipalti's Procurement REST API (static x-api-key auth).
infrastructure/restadapter
Package restadapter implements the payee/invoice/payment domain ports against Tipalti's modern OAuth2 REST API.
Package restadapter implements the payee/invoice/payment domain ports against Tipalti's modern OAuth2 REST API.
infrastructure/soapadapter
Package soapadapter implements soap.Caller against Tipalti's legacy HMAC-signed SOAP API (PayeeFunctions.asmx / PayerFunctions.asmx).
Package soapadapter implements soap.Caller against Tipalti's legacy HMAC-signed SOAP API (PayeeFunctions.asmx / PayerFunctions.asmx).
pagination
Package pagination provides generic, channel-based auto-pagination over cursor-paginated list endpoints.
Package pagination provides generic, channel-based auto-pagination over cursor-paginated list endpoints.
ratelimit
Package ratelimit provides an optional, self-contained token-bucket rate limiter.
Package ratelimit provides an optional, self-contained token-bucket rate limiter.
telemetry
Package telemetry defines a minimal, dependency-free observability hook that every HTTP request in this module reports through.
Package telemetry defines a minimal, dependency-free observability hook that every HTTP request in this module reports through.
webhook
Package webhook parses Tipalti IPN (Instant Payment Notification) callbacks.
Package webhook parses Tipalti IPN (Instant Payment Notification) callbacks.

Jump to

Keyboard shortcuts

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