Metadata-Version: 2.4
Name: altpay-py
Version: 0.1.3
Summary: Official Python SDK for the AltPay crypto-payments API (sync + async).
Project-URL: Homepage, https://altpay.money
Project-URL: Documentation, https://docs.altpay.money/
Project-URL: Source, https://github.com/altpaysdk/py-sdk
Author: AltPayments
License: MIT
License-File: LICENSE
Keywords: altpay,api,crypto,payments,sdk
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business :: Financial
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx>=0.24
Requires-Dist: pydantic>=2.0
Provides-Extra: dev
Requires-Dist: mypy>=1.5; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Requires-Dist: respx>=0.20; extra == 'dev'
Requires-Dist: ruff>=0.1; extra == 'dev'
Description-Content-Type: text/markdown

# altpay-py

Official Python SDK for the [AltPay](https://altpay.money) crypto-payments API. Synchronous
and asynchronous clients, typed models, request signing and webhook verification in one
package. Built on `httpx` and `pydantic` v2.

```bash
pip install altpay-py
```

```python
import altpay  # the distribution is altpay-py; the import name is altpay
```

## Quick start

```python
from decimal import Decimal
from altpay import AltPay, Credentials

client = AltPay(Credentials(
    merchant_id="YOUR_MERCHANT_ID",
    api_key="vc_live_...",
    api_secret="YOUR_API_SECRET",
))

invoice = client.invoices.create(
    uuid="order-42",                 # your idempotency key / order reference
    amount=Decimal("100.00"),
    fiat_currency="USD",
    url_callback="https://example.com/altpay/webhook",
)
print(invoice.url)                   # hosted checkout URL to redirect the payer to

# later, check on it
invoice = client.invoices.get(order_id=invoice.order_id)
print(invoice.status)                # PaymentStatus.WAITING | PAID | EXPIRED | ...
```

### Async

The async client mirrors the sync one. `await` the calls.

```python
from altpay import AsyncAltPay, Credentials

async with AsyncAltPay(creds) as client:
    invoice = await client.invoices.create(
        uuid="order-42", amount="100.00", fiat_currency="USD",
    )
    print(invoice.url)
```

## What you can do

| Resource | Method | Endpoint |
| --- | --- | --- |
| `client.invoices` | `create(...)` | create an invoice |
| | `get(order_id=... \| uuid=...)` | fetch one invoice |
| | `list(status=..., cursor=..., limit=...)` | page through invoices |
| | `services()` | available methods, limits and fees |
| | `balance(fiat_currency=...)` | balance valued in one fiat, per asset |
| | `assets(fiat_currency=...)` | per-token balance with allocation + `withdrawable` |
| | `create_wallet(network=..., order_id=...)` | static deposit wallet |
| | `list_wallets(limit=..., offset=...)` | list static wallets |
| | `wallet_deposits(wallet_uuid=...)` | deposits received at a static wallet |
| `client.withdrawals` | `estimate_fee(asset=..., network=...)` | preview payout fees |
| | `create(asset=..., amount=..., address=..., idempotency_key=...)` | request a payout to a trusted address |
| | `list(limit=..., offset=...)` | list payout requests |
| `client.account` | `get()` | merchant + API-key identity |
| | `balance()` | paid volume per method (native asset) |
| | `statistics()` | aggregate counts and USD volume |

## Webhooks

AltPay POSTs a signed `payment.updated` event to your `url_callback` when an invoice is
paid. **Always verify the signature before trusting the body**, and pass the raw request
bytes, not the re-serialized JSON.

```python
from altpay import WebhookVerifier

verifier = WebhookVerifier(WEBHOOK_SECRET, target="/altpay/webhook")

# inside your handler (framework-agnostic):
event = verifier.parse(raw_body, request_headers)   # raises AuthenticationError on a bad sig
if event.status == "paid":
    fulfil_order(event.merchant_reference)
```

`verifier.verify(raw_body, headers)` returns a bool if you prefer to branch yourself.

Verification is not deduplication: AltPay may deliver the same event more than once, and both
copies verify. Make fulfilment idempotent by keying it on `event.payment_id`, so a repeat is a no-op.

## Withdrawals (payouts)

Request a payout with `client.withdrawals`. For safety the public API can **only** send to an
address you already trusted in the dashboard (adding one requires your 2FA), so a leaked API
key cannot invent a new destination. At most it can repeat a payout to an address you already
trust.

```python
fee = client.withdrawals.estimate_fee(asset="USDT", network="USDT_TRC20")

payout = client.withdrawals.create(
    asset="USDT",
    amount="50.00",
    address="T...",                 # must already be a trusted address for USDT
    network="USDT_TRC20",
    idempotency_key="payout-42",    # a retry with the same key returns the original payout
)
print(payout.status)                # WithdrawalStatus.PENDING (an operator approves it)
```

Always pass `idempotency_key`: the amount is reserved from your balance the moment the request
is accepted, and the key is what makes a network retry return the original payout instead of
debiting you twice. A trusted-but-wrong `address` still moves funds, so verify it before calling.

## Errors

Every failure is an `AltPayError`. Network problems raise `AltPayTransportError`; anything the
API rejected raises an `APIError` subclass keyed by HTTP status. Each carries the server's
machine-readable `detail`, a human `hint`, and the `request_id` for support.

```python
from altpay import AuthenticationError, RateLimitError, ValidationError, NotFoundError

try:
    client.invoices.create(uuid="o1", amount="100", fiat_currency="USD")
except AuthenticationError as e:
    ...   # bad credentials / clock skew / revoked key  (HTTP 401, detail="invalid_signature")
except ValidationError as e:
    ...   # a field failed validation                   (HTTP 400, detail="invalid_request")
except RateLimitError as e:
    sleep(e.retry_after or 1)                            # HTTP 429
except NotFoundError as e:
    ...                                                  # HTTP 404
```

The full catalogue, with every `detail` value and how to fix it, is at
<https://docs.altpay.money/docs/http-codes>.

## Configuration

```python
AltPay(
    credentials,
    base_url="https://api.altpay.money",  # override for staging
    timeout=30.0,                          # per-request seconds
    max_retries=2,                         # retry transient errors (5xx, 429, network) with backoff
    http_client=my_httpx_client,           # bring your own httpx.Client (proxies, custom TLS)
)
```

## Authentication

Each request is signed with HMAC-SHA256 over a canonical string of the merchant id, API key,
timestamp, nonce, body hash, method and path. The SDK signs every request; the secret never
leaves your process. The primitives (for custom transport or testing) live in
`altpay.signing`. Full spec: <https://docs.altpay.money/docs/authentication>.

## Requirements

- Python 3.10+
- `httpx >= 0.24`, `pydantic >= 2.0`

## License

MIT
