Metadata-Version: 2.4
Name: invoq
Version: 0.1.0
Summary: Python SDK package for invoq server APIs and webhook verification.
Author: invoq
License-Expression: MIT
Project-URL: Homepage, https://invoq.money
Project-URL: Source, https://github.com/invoqmoney/sdk-python
Project-URL: Issues, https://github.com/invoqmoney/sdk-python/issues
Keywords: invoq,stablecoin,payments,sdk
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Office/Business :: Financial
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# invoq Python SDK

Python SDK for invoq server APIs and webhook verification. Create stablecoin
invoices from your backend and fulfill orders with signed webhooks.

Use this package only on your server. It accepts secret keys and must not be
bundled into browser code.

## Installation

```bash
python -m pip install invoq
```

Requires Python 3.9 or newer.

## Get your keys

1. Sign in to the invoq dashboard and create a project.
2. On the API keys page, create a secret key. Test keys start with `sk_test_`,
   live keys with `sk_live_`.
3. In your project's webhooks settings, save your webhook URL and store the
   webhook secret (`whsec_...`) for that mode.

Add both to your server environment:

```bash
INVOQ_SECRET_KEY=sk_test_...
INVOQ_WEBHOOK_SECRET=whsec_...
```

Start with test keys. Switch to the live key and live webhook secret when you go
to production.

## Create a client

```python
import os

from invoq import Invoq

invoq = Invoq(os.environ["INVOQ_SECRET_KEY"])
```

Production API default:

```text
https://api.invoq.money
```

Override the API origin and request timeout during development:

```python
invoq = Invoq(
    os.environ["INVOQ_SECRET_KEY"],
    api_origin="http://localhost:8787",
    timeout_ms=10_000,
)
```

`api_origin` must be an absolute `http` or `https` origin with no path, query,
hash, username, or password. The SDK appends `/v1/...` API paths. Requests time
out after 10 seconds by default (`timeout_ms`).

## Invoices

Create an invoice:

```python
invoice = invoq.invoices.create({
    "amount": "149",
    "currency": "USD",
    "description": "SaaS boilerplate",
    "reference_id": "order_1234",
    "return_url": "https://merchant.example/thanks",
})
```

Notes:

- Use a server-side amount. Do not trust client-supplied amounts.
- `amount` is a decimal USD string from `"0.01"` to `"999.99"` with up to 2
  decimal places, such as `"129"` or `"129.99"`.
- Use `reference_id` to map `invoice.paid` webhooks back to your order. It also
  makes creation retry-safe: creating again with the same `reference_id` and the
  same invoice terms returns the existing invoice instead of a duplicate, while
  different terms fail with a `409 reference_id_conflict` API error.
- Omit `return_url` to use the project's default return URL. Pass `None` to send
  JSON `null` and create the invoice without a return URL. On `reference_id`
  retries, pass `return_url` explicitly when you need to assert a specific value.
- `description` and `reference_id` must be strings when present.

Get an invoice:

```python
invoice = invoq.invoices.get("inv_123")
```

`invoices.get()` returns the public invoice shape used by the hosted checkout
endpoint. It includes checkout-facing fields such as `amount_paid`,
`amount_due`, `payment_status`, `project`, `deposit_address`,
`monitoring_ends_at`, and `direct_onchain_rails`, but does not include
`reference_id`. Use the create response or the `invoice.paid` webhook when you
need your merchant `reference_id`.

Create a test payment:

```python
paid_invoice = invoq.invoices.create_test_payment("inv_123", {
    "amount": "149",
    "reference_id": "test_payment_001",
})
```

`create_test_payment` only works on invoices created with a `sk_test_` key. When
payments reach the invoice amount, the invoice becomes `paid` and invoq sends a
real signed `invoice.paid` webhook to your test webhook URL. Partial amounts are
allowed and produce `partially_paid`.

Amounts in responses are normalized to 4 decimal places: create with `"129"` and
the invoice returns `amount: "129.0000"`. Compare amounts numerically, not as
strings. `amount_due` is derived as `max(amount - amount_paid, 0)` and uses the
same 18-decimal scale as `amount_paid`.

The SDK returns the response `data` object directly.

## Hosted checkout page

Every invoice also has a hosted checkout page at:

```text
https://pay.invoq.money/<invoice id>
```

Share the link or redirect to it when an in-page checkout modal is not a fit.

## Webhooks

Pass the raw request body to `verify_webhook`. Do not parse JSON and re-dump it
before verification.

```python
import os

from invoq import is_invoice_paid, verify_webhook

event = verify_webhook(
    raw_body,
    {"invoq-signature": signature_header},
    os.environ["INVOQ_WEBHOOK_SECRET"],
)

if is_invoice_paid(event):
    order_id = event["data"]["invoice"]["reference_id"]

    if order_id is None:
        raise ValueError("Missing invoice reference_id for fulfillment.")

    fulfill_order(order_id)
```

Use `invoice.paid` webhooks to fulfill orders on your server. When
`is_invoice_paid(event)` is true, the invoice is ready for automatic
fulfillment; use the invoice `reference_id` to find and fulfill your order. The
helper accepts paid-equivalent invoice statuses (`paid`, `settling`, or
`settled`) and rejects `review_required`.

Important:

- Pass the raw request body as a string or bytes.
- Pass a header mapping with the `invoq-signature` header.
- Do not parse JSON and re-dump it before verification.
- `verify_webhook` does not require `Invoq(...)` or your invoq API secret key.
- Use your webhook secret, not `INVOQ_SECRET_KEY`.
- Fulfill idempotently. Failed webhook deliveries are retried.
- Respond with a 2xx quickly. Other statuses count as failed deliveries.

Webhook verification failures raise `InvoqSignatureVerificationError`. The
signature header is:

```text
invoq-signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">
```

## Errors

```python
from invoq import InvoqApiError, InvoqError

try:
    invoq.invoices.create({"amount": "0.001", "currency": "USD"})
except InvoqApiError as error:
    print(error.status)
    print(error.code)
    print(error.fields)
except InvoqError:
    raise
```

Non-2xx API responses raise `InvoqApiError`, which has `status`, `code`,
`fields`, `meta`, and the raw `payload`. Connection failures, timeouts, response
parse failures, and invalid SDK inputs raise `InvoqError`.
