Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,70 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.2.3-beta] - 2025-02-20

### Added
- **TaxCloud Cart Routing**: `CalculateCart()` now automatically routes to TaxCloud's `POST /tax/connections/{connectionId}/carts` when TaxCloud credentials are configured
- Same input contract (`CalculateCartRequest`) regardless of backend
- SDK transforms request internally: parses single-string addresses into structured components, maps `taxabilityCode` to `tic`, adds 0-based `index` to line items
- Returns `TaxCloudCalculateCartResponse` (with `connectionId`, `transactionDate`, structured addresses, `deliveredBySeller`, `exemption`) when routed to TaxCloud
- Returns `CalculateCartResponse` (existing behavior) when TaxCloud is not configured
- Return type: `Union[CalculateCartResponse, TaxCloudCalculateCartResponse]`
- **Address Parsing Utility**: `parse_address_string()` in `utils/validation.py`
- Parses `"street, city, ST zip"` format into structured `{line1, city, state, zip}` dict
- Supports 5-digit and 9-digit ZIP codes (e.g., `92618` or `92618-1905`)
- Raises `ZipTaxValidationError` with descriptive messages on parse failure
- **3 New Pydantic Models** for TaxCloud cart responses:
- `TaxCloudCalculateCartResponse`: top-level response with `connection_id`, `items`, `transaction_date`
- `TaxCloudCartItemResponse`: per-cart result with structured addresses, `currency`, `delivered_by_seller`, `exemption`
- `TaxCloudCartLineItemResponse`: per-item result with `index`, `item_id`, `price`, `quantity`, `tax`, `tic`
- **17 New Tests** in `TestCalculateCartTaxCloudRouting`:
- Routing logic (ZipTax vs TaxCloud based on config)
- Request transformation (address parsing, index, tic mapping, field passthrough)
- Response parsing (top-level, cart fields, addresses, line items)
- Error handling (unparseable addresses, invalid state/zip)

### Changed
- Version bumped from `0.2.1-beta` to `0.2.3-beta`
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Version 0.2.2-beta appears to have been skipped (jumping from 0.2.1-beta to 0.2.3-beta). While this is not necessarily incorrect, it's unusual and could cause confusion. Consider adding a note explaining why 0.2.2 was skipped, or if this was unintentional, consider using 0.2.2-beta instead.

Suggested change
- Version bumped from `0.2.1-beta` to `0.2.3-beta`
- Version bumped from `0.2.1-beta` to `0.2.3-beta`
- Version `0.2.2-beta` was reserved for an internal build and was never released publicly; the version number intentionally skips from `0.2.1-beta` to `0.2.3-beta`.

Copilot uses AI. Check for mistakes.
- `CalculateCart()` return type changed from `CalculateCartResponse` to `Union[CalculateCartResponse, TaxCloudCalculateCartResponse]`
- `CalculateCart()` implementation refactored into routing method with two private helpers: `_calculate_cart_ziptax()` and `_calculate_cart_taxcloud()`

### Technical Details
- Request transformation handled by static method `_transform_cart_for_taxcloud()` in `Functions`
- TaxCloud cart uses `taxcloud_http_client` (separate auth) with path `/tax/connections/{connectionId}/carts`
- Retry logic with exponential backoff applies to both ZipTax and TaxCloud routes
- All quality checks pass: black, ruff, mypy, pytest (125 tests, 96% coverage)

## [0.2.1-beta] - 2025-02-19

### Added
- **Cart Tax Calculation**: `CalculateCart()` function for calculating sales tax on shopping carts
- Accepts a `CalculateCartRequest` with customer info, addresses, currency, and line items
- Sends cart to `POST /calculate/cart` on the ZipTax API
- Returns per-item tax rate and amount via `CalculateCartResponse`
- Origin/destination sourcing is handled by the API internally
- **9 New Pydantic Models** for cart tax calculation:
- Request models: `CalculateCartRequest`, `CartItem`, `CartAddress`, `CartCurrency`, `CartLineItem`
- Response models: `CalculateCartResponse`, `CartItemResponse`, `CartLineItemResponse`, `CartTax`
- **Pydantic Validation** on cart models:
- `CartLineItem.price` and `quantity`: must be greater than 0 (`gt=0`)
- `CalculateCartRequest.items`: exactly 1 element (`min_length=1, max_length=1`)
- `CartItem.line_items`: 1-250 elements (`min_length=1, max_length=250`)
- `CartCurrency.currency_code`: must be `"USD"` (`Literal["USD"]`)
- **Documentation**:
- CalculateCart usage guide with code examples in README.md
- Cart endpoint specification in `docs/spec.yaml`
- Actual API request/response examples in spec

### Changed
- Version bumped from `0.2.0-beta` to `0.2.1-beta`

### Technical Details
- Cart calculation uses the ZipTax HTTP client (not TaxCloud) with `X-API-Key` authentication
- Request bodies serialized with `model_dump(by_alias=True, exclude_none=True)` for camelCase API fields
- `taxabilityCode` is optional and excluded from the payload when not set
- All quality checks pass: black, ruff, mypy, pytest

## [0.2.0-beta] - 2025-02-16

### Added
Expand Down
19 changes: 16 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,9 @@ def has_taxcloud_config(self) -> bool:
- Uses decorator pattern for retry logic
- TaxCloud methods check credentials before executing

**Critical Pattern**:
**Critical Patterns**:
```python
# TaxCloud methods check credentials first
def _check_taxcloud_config(self) -> None:
"""Check if TaxCloud credentials are configured."""
if not self.config.has_taxcloud_config or self.taxcloud_http_client is None:
Expand All @@ -131,6 +132,16 @@ def _check_taxcloud_config(self) -> None:
def CreateOrder(self, request: CreateOrderRequest, ...) -> OrderResponse:
self._check_taxcloud_config() # Guards against missing credentials
# ... implementation

# CalculateCart sends the cart directly to the API for tax calculation
# The API handles origin/destination sourcing internally
def CalculateCart(self, request: CalculateCartRequest) -> CalculateCartResponse:
# Serialize and POST to /calculate/cart
response_data = self.http_client.post(
"/calculate/cart",
json=request.model_dump(by_alias=True, exclude_none=True),
)
return CalculateCartResponse(**response_data)
```

#### 4. **HTTP Client (`utils/http.py`)**
Expand All @@ -155,6 +166,7 @@ self.session.headers.update({"X-API-Key": api_key})
- **Purpose**: Pydantic models for request/response validation
- **Structure**:
- V60 models for ZipTax API responses
- Cart models for cart tax calculation (with Pydantic field constraints)
- TaxCloud models for order management
- Uses `Field` with `alias` for camelCase ↔ snake_case mapping

Expand Down Expand Up @@ -545,6 +557,7 @@ This file is used as a reference for code generation and documentation.
**Endpoints**:
- `GET /request/v60/` - Tax rate lookup by address or geolocation
- `GET /account/v60/metrics` - Account usage metrics
- `POST /calculate/cart` - Cart tax calculation with per-item rates

**Response Format**: JSON with nested structure

Expand Down Expand Up @@ -773,6 +786,6 @@ For API-specific questions:

---

**Last Updated**: 2025-02-16
**SDK Version**: 0.2.0-beta
**Last Updated**: 2025-02-19
**SDK Version**: 0.2.3-beta
**Maintained By**: ZipTax Team
75 changes: 75 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Official Python SDK for the [Ziptax API](https://zip-tax.com) - Get accurate sal

### Core Features (ZipTax API)
- 🚀 Simple and intuitive API
- 🛒 Cart tax calculation with per-item tax rates
- 🔄 Automatic retry logic with exponential backoff
- ✅ Input validation
- 🔍 Type hints for better IDE support
Expand Down Expand Up @@ -148,6 +149,79 @@ print(f"Account Active: {metrics.is_active}")
print(f"Message: {metrics.message}")
```

### Calculate Cart Tax

Calculate sales tax for a shopping cart with multiple line items. `CalculateCart` uses **dual-routing**: when TaxCloud credentials are configured on the client, the request is automatically routed to the TaxCloud API; otherwise it is sent to the ZipTax API. The input is the same `CalculateCartRequest` in both cases, but the response type differs:

- **Without TaxCloud credentials** -- returns a `CalculateCartResponse` (ZipTax API)
- **With TaxCloud credentials** -- returns a `TaxCloudCalculateCartResponse` (TaxCloud API)

```python
from ziptax.models import (
CalculateCartRequest,
CartItem,
CartAddress,
CartCurrency,
CartLineItem,
)

# Build the cart request
request = CalculateCartRequest(
items=[
CartItem(
customer_id="customer-453",
currency=CartCurrency(currency_code="USD"),
destination=CartAddress(
address="200 Spectrum Center Dr, Irvine, CA 92618"
),
origin=CartAddress(
address="323 Washington Ave N, Minneapolis, MN 55401"
),
line_items=[
CartLineItem(
item_id="item-1",
price=10.75,
quantity=1.5,
),
CartLineItem(
item_id="item-2",
price=25.00,
quantity=2.0,
taxability_code=0,
),
],
)
]
)

# Calculate tax (routes to ZipTax or TaxCloud based on client config)
result = client.request.CalculateCart(request)

# Access results
cart = result.items[0]
print(f"Cart ID: {cart.cart_id}")
for item in cart.line_items:
print(f" {item.item_id}: rate={item.tax.rate}, amount=${item.tax.amount:.2f}")
```

#### Validation

The cart models enforce constraints at construction time via Pydantic:

- `items` must contain exactly 1 cart
- `line_items` must contain 1-250 items
- `price` and `quantity` must be greater than 0
- `currency_code` must be `"USD"`

```python
from pydantic import ValidationError

try:
CartLineItem(item_id="item-1", price=-5.00, quantity=1.0)
except ValidationError as e:
print(e) # price must be greater than 0
```

## TaxCloud Order Management

The SDK includes optional support for TaxCloud order management features. To use these features, you need both a ZipTax API key and TaxCloud credentials (Connection ID and API Key).
Expand Down Expand Up @@ -522,6 +596,7 @@ API endpoint functions accessible via `client.request`.
- `GetSalesTaxByGeoLocation(lat, lng, **kwargs)` - Get tax rates by coordinates
- `GetRatesByPostalCode(postal_code, **kwargs)` - Get tax rates by US postal code
- `GetAccountMetrics(**kwargs)` - Get account usage metrics
- `CalculateCart(request)` - Calculate sales tax for a shopping cart

#### TaxCloud API Methods (Optional)

Expand Down
Loading
Loading