Skip to content

ZipTax/ziptax-go

Repository files navigation

ZipTax Go SDK

Official Go SDK for the ZipTax API - get accurate sales and use tax rates for any US address or geolocation.

Go Reference Go Report Card License: MIT

Features

  • 🚀 Simple, idiomatic Go API
  • 🔄 Automatic retry with exponential backoff
  • ⏱️ Context support for timeouts and cancellation
  • 🔍 Input validation
  • 🧪 Comprehensive test coverage (>88%)
  • 📝 Full type safety with Go structs
  • 🔐 Secure API key authentication
  • 🌐 Support for US and Canadian addresses
  • 📍 Geolocation-based lookups
  • 🏷️ Product Code (TIC) Search - Search and AI-powered recommendation for Taxability Information Codes
  • 🛒 Cart Tax Calculation - Calculate sales tax on shopping carts (ZipTax or TaxCloud)
  • 📦 TaxCloud Order Management - Create, retrieve, update, and refund orders

Installation

go get github.com/ziptax/ziptax-go

Quick Start

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/ziptax/ziptax-go"
)

func main() {
    // Create a new client with your API key
    client, err := ziptax.NewClient("your-api-key-here")
    if err != nil {
        log.Fatal(err)
    }

    // Get sales tax by address
    ctx := context.Background()
    response, err := client.GetSalesTaxByAddress(ctx, "200 Spectrum Center Drive, Irvine, CA 92618")
    if err != nil {
        log.Fatal(err)
    }

    // Print the results
    fmt.Printf("Address: %s\n", response.AddressDetail.NormalizedAddress)
    if len(response.TaxSummaries) > 0 {
        fmt.Printf("Total Tax Rate: %.4f%%\n", response.TaxSummaries[0].Rate*100)
    }
}

Usage

Client Initialization

Create a client with default settings (ZipTax API only):

client, err := ziptax.NewClient("your-api-key")

Or customize the client with options:

client, err := ziptax.NewClient(
    "your-api-key",
    ziptax.WithTimeout(60*time.Second),
    ziptax.WithMaxRetries(5),
    ziptax.WithRetryWait(2*time.Second, 60*time.Second),
)

Enable TaxCloud Order Management (Optional):

To use TaxCloud order features, provide both TaxCloud credentials during initialization:

client, err := ziptax.NewClient(
    "your-ziptax-api-key",
    ziptax.WithTaxCloudConnectionID("your-taxcloud-connection-id"),
    ziptax.WithTaxCloudAPIKey("your-taxcloud-api-key"),
)

Get Sales Tax by Address

response, err := client.GetSalesTaxByAddress(
    ctx,
    "200 Spectrum Center Drive, Irvine, CA 92618",
)

With optional parameters:

response, err := client.GetSalesTaxByAddress(
    ctx,
    "200 Spectrum Center Drive, Irvine, CA 92618",
    ziptax.WithHistorical("202401"),
    ziptax.WithCountryCode("USA"),
    ziptax.WithFormat("json"),
)

Get Sales Tax by Geolocation

response, err := client.GetSalesTaxByGeoLocation(
    ctx,
    "33.65253",  // latitude
    "-117.74794", // longitude
)

Get Rates by Postal Code

response, err := client.GetRatesByPostalCode(ctx, "92694")
for _, result := range response.Results {
    fmt.Printf("City: %s, Total Tax Rate: %.4f%%\n", result.GeoCity, result.TaxSales*100)
}

Get Account Metrics

metrics, err := client.GetAccountMetrics(ctx)
fmt.Printf("Core Usage: %.2f%%\n", metrics.CoreUsagePercent)
fmt.Printf("Geo Usage: %.2f%%\n", metrics.GeoUsagePercent)

Product Code Search (TIC)

Search for Taxability Information Codes (TICs) using natural language product descriptions. No TaxCloud credentials required -- these endpoints use the ZipTax API.

Search Product Codes

Returns all matching TICs ranked and scored by relevance:

response, err := client.SearchProductCodes(ctx, "baked goods sold in plastic packaging")
if err != nil {
    log.Fatal(err)
}

for _, result := range response.Results {
    fmt.Printf("TIC %s: %s (rank %s, score %s)\n",
        result.TicID, result.Label, result.Rank, result.Score)
}

Recommend Product Code

Get an AI-powered best-match recommendation (slightly higher latency):

response, err := client.RecommendProductCode(ctx, "baked goods sold in plastic packaging")
if err != nil {
    log.Fatal(err)
}

prediction := response.Predictions[0]
if prediction.Status == "success" {
    fmt.Printf("Recommended TIC: %s (%s)\n", prediction.TicID, prediction.Label)
    fmt.Printf("Description: %s\n", prediction.TicDescription)
}

Using TICs with Cart Line Items

Use the returned TicID as the TaxabilityCode in cart line items:

import (
    "log"
    "strconv"
)

ticID := response.Results[0].TicID
tic, err := strconv.ParseInt(ticID, 10, 64)
if err != nil {
    log.Fatalf("failed to parse TIC ID %q: %v", ticID, err)
}

lineItem := models.CartLineItem{
    ItemID:          "item-1",
    Price:           10.00,
    Quantity:        1,
    TaxabilityCode:  &tic,
}

Cart Tax Calculation

Calculate sales tax on a shopping cart. The SDK routes the request automatically based on your client configuration:

  • Without TaxCloud credentials: Routes to the ZipTax /calculate/cart API
  • With TaxCloud credentials: Routes to the TaxCloud /tax/connections/{connectionId}/carts API
cartReq := &models.CalculateCartRequest{
    Items: []models.CartItem{
        {
            CustomerID: "customer-453",
            Currency:   models.CartCurrency{CurrencyCode: "USD"},
            Destination: models.CartAddress{
                Address: "200 Spectrum Center Dr, Irvine, CA 92618",
            },
            Origin: models.CartAddress{
                Address: "323 Washington Ave N, Minneapolis, MN 55401-2427",
            },
            LineItems: []models.CartLineItem{
                {
                    ItemID:   "item-1",
                    Price:    10.75,
                    Quantity: 1.5,
                },
            },
        },
    },
}

result, err := client.CalculateCart(ctx, cartReq)
if err != nil {
    log.Fatal(err)
}

// Use a type switch to handle the polymorphic response
switch r := result.(type) {
case *models.CalculateCartResponse:
    fmt.Printf("ZipTax cart total tax: %.2f\n", r.Items[0].LineItems[0].Tax.Amount)
case *models.TaxCloudCalculateCartResponse:
    fmt.Printf("TaxCloud cart ID: %s\n", r.Items[0].CartID)
}

Create Order from Cart

After calculating a cart with TaxCloud credentials, convert it into a finalized order. This requires the cartId returned from a previous CalculateCart call:

req := &models.CreateOrderFromCartRequest{
    CartID:  "ce4a1234-5678-90ab-cdef-1234567890ab", // from CalculateCart response
    OrderID: "my-order-1",                            // your internal order ID
}

order, err := client.CreateOrderFromCart(ctx, req)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Order created: %s\n", order.OrderID)

To set a completed date on the order, use UpdateOrder after creation.

TaxCloud Order Management

The SDK supports comprehensive order lifecycle management through the TaxCloud API. To use these features, you must configure TaxCloud credentials during client initialization.

Create Order

Create a new order for tax filing:

orderReq := &models.CreateOrderRequest{
    OrderID:         "order-123",
    CustomerID:      "customer-456",
    TransactionDate: "2024-01-15T09:30:00Z",
    CompletedDate:   "2024-01-15T09:30:00Z",
    Origin: models.TaxCloudAddress{
        Line1: "200 Spectrum Center Drive Suite 300",
        City:  "Irvine",
        State: "CA",
        Zip:   "92618",
    },
    Destination: models.TaxCloudAddress{
        Line1: "323 Washington Ave N",
        City:  "Minneapolis",
        State: "MN",
        Zip:   "55401-2427",
    },
    LineItems: []models.CartItemWithTax{
        {
            Index:    0,
            ItemID:   "item-1",
            Price:    10.8,
            Quantity: 1.5,
            Tax: models.Tax{
                Amount: 1.31,
                Rate:   0.0813,
            },
        },
    },
    Currency: &models.Currency{},
}

response, err := client.CreateOrder(ctx, orderReq)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Order created: %s\n", response.OrderID)

Get Order

Retrieve an existing order by ID:

order, err := client.GetOrder(ctx, "order-123")
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Order %s has %d line items\n", order.OrderID, len(order.LineItems))

Update Order

Update an order's completed date (when it was shipped/completed):

updateReq := &models.UpdateOrderRequest{
    CompletedDate: "2024-01-16T10:00:00Z",
}

order, err := client.UpdateOrder(ctx, "order-123", updateReq)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Order updated with new completed date: %s\n", order.CompletedDate)

Refund Order

Create a full or partial refund against an order:

Partial Refund:

refundReq := &models.RefundTransactionRequest{
    Items: []models.CartItemRefundWithTaxRequest{
        {
            ItemID:   "item-1",
            Quantity: 1.0,
        },
    },
}

refunds, err := client.RefundOrder(ctx, "order-123", refundReq)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Refunded %d items\n", len(refunds[0].Items))

Full Refund:

// Empty request for full order refund
refundReq := &models.RefundTransactionRequest{}

refunds, err := client.RefundOrder(ctx, "order-123", refundReq)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Full order refund created\n")

Note: An order can only be refunded once, regardless of whether it's a partial or full refund.

Error Handling

The SDK provides structured error types for proper error handling:

response, err := client.GetSalesTaxByAddress(ctx, address)
if err != nil {
    // Check for validation errors
    var validationErr *ziptax.ValidationError
    if errors.As(err, &validationErr) {
        fmt.Printf("Validation error: %s\n", validationErr.Message)
        return
    }

    // Check for API errors
    var apiErr *ziptax.APIError
    if errors.As(err, &apiErr) {
        fmt.Printf("API error: %s (code %d)\n", apiErr.Message, apiErr.Code)
        return
    }

    // Check for sentinel errors
    if errors.Is(err, ziptax.ErrInvalidAPIKey) {
        fmt.Println("Invalid API key")
        return
    }

    // Check for TaxCloud errors
    if errors.Is(err, ziptax.ErrTaxCloudNotConfigured) {
        fmt.Println("TaxCloud credentials not configured")
        return
    }

    // Generic error
    fmt.Printf("Error: %v\n", err)
    return
}

Context Support

All API methods accept a context.Context for cancellation and timeouts:

// With timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

response, err := client.GetSalesTaxByAddress(ctx, address)
// With cancellation
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

// Cancel from another goroutine
go func() {
    time.Sleep(100 * time.Millisecond)
    cancel()
}()

response, err := client.GetSalesTaxByAddress(ctx, address)

Concurrent Requests

The client is safe for concurrent use:

var wg sync.WaitGroup
addresses := []string{"address1", "address2", "address3"}

for _, addr := range addresses {
    wg.Add(1)
    go func(address string) {
        defer wg.Done()
        resp, err := client.GetSalesTaxByAddress(ctx, address)
        // Handle response...
    }(addr)
}

wg.Wait()

Configuration Options

Client Options

Core Options:

  • WithBaseURL(url string) - Set a custom API base URL
  • WithHTTPClient(client *http.Client) - Use a custom HTTP client
  • WithTimeout(timeout time.Duration) - Set request timeout
  • WithMaxRetries(max int) - Set maximum retry attempts (0 to disable)
  • WithRetryWait(min, max time.Duration) - Set retry backoff times
  • WithLogger(logger Logger) - Enable request/response logging
  • WithUserAgent(ua string) - Set a custom User-Agent header

TaxCloud Options:

  • WithTaxCloudConnectionID(id string) - Set TaxCloud Connection ID (required for order management)
  • WithTaxCloudAPIKey(key string) - Set TaxCloud API Key (required for order management)
  • WithTaxCloudBaseURL(url string) - Set custom TaxCloud API base URL

Request Options

  • WithHistorical(date string) - Get historical rates (YYYYMM format, e.g., "202401")
  • WithCountryCode(code string) - Specify country code (USA or CAN)
  • WithFormat(format string) - Set response format (json or xml)

Examples

See the examples directory for complete examples:

API Reference

For detailed API documentation, see pkg.go.dev.

Development

Prerequisites

  • Go 1.21 or higher
  • Make (optional, for using Makefile commands)

Running Tests

# Run all tests
make test

# Run tests with coverage
make test-coverage

# Run tests with verbose output
make test-verbose

Linting and Formatting

# Format code
make fmt

# Organize imports
make imports

# Run linter
make lint

# Run all checks
make check

Building

# Build the SDK
make build

Changelog

See CHANGELOG.md for a detailed list of changes in each release.

Versioning

This project follows Semantic Versioning. The version is defined in version.go.

Version Format: MAJOR.MINOR.PATCH

  • MAJOR: Breaking changes (incompatible API changes)
  • MINOR: New features (backward compatible)
  • PATCH: Bug fixes (backward compatible)

For Contributors

When submitting a PR, you must bump the version in version.go. See .github/VERSION_BUMP_GUIDE.md for detailed instructions.

The GitHub Actions workflow will automatically verify that the semantic version has been properly bumped.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Before submitting:

  1. Update the version in version.go following semantic versioning
  2. Update CHANGELOG.md with your changes
  3. Add tests for new functionality
  4. Update documentation (README, GoDoc comments)
  5. Ensure all tests pass (make test)
  6. Run linter (make lint)

License

This project is licensed under the MIT License - see the LICENSE file for details.

Support

For API-related questions and support, please contact support@zip.tax.

For SDK issues, please open an issue on GitHub.

Links

About

Ziptax Go SDK

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors