# Python SDK

Official server-side SDK for integrating ZevPay Checkout in Python applications. Provides full REST API coverage with httpx HTTP client.

## Installation

```bash
pip install zevpay
```

Requires Python 3.8 or later.

## Quick start

```python
from zevpay import ZevPay

client = ZevPay("sk_test_your_secret_key")

# Initialize a checkout session
session = client.checkout.initialize(
    amount=500000,  # ₦5,000 in kobo
    email="customer@example.com",
    reference="ORDER-123",
    callback_url="https://yoursite.com/callback",
)

print(session["checkout_url"])
```

## Configuration

```python
client = ZevPay(
    "sk_live_xxx",
    base_url="https://api.zevpaycheckout.com",  # default
    timeout=30,      # 30s request timeout (default)
    max_retries=2,   # retries on 5xx errors (default)
)
```

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `base_url` | str | `https://api.zevpaycheckout.com` | API base URL |
| `timeout` | int | `30` | Request timeout in seconds |
| `max_retries` | int | `2` | Max retries on server errors |

::: warning Secret keys only
The SDK only accepts secret keys (`sk_live_*` or `sk_test_*`). Public keys cannot be used server-side.
:::

## Checkout sessions

### Initialize a session

```python
session = client.checkout.initialize(
    amount=500000,
    email="customer@example.com",
    currency="NGN",
    reference="ORDER-123",
    callback_url="https://yoursite.com/callback",
    metadata={"order_id": "123"},
    payment_methods=["bank_transfer", "payid"],
)

# Redirect your customer to:
print(session["checkout_url"])
```

### Verify a payment

```python
result = client.checkout.verify(session["session_id"])

if result["status"] == "completed":
    print(f"Payment confirmed at: {result['paid_at']}")
```

### Get session details

```python
details = client.checkout.get("ses_abc123")
```

## Transfers

### Bank transfer

```python
transfer = client.transfers.create(
    type="bank_transfer",
    amount=1000000,  # ₦10,000
    account_number="0123456789",
    bank_code="044",
    account_name="John Doe",
    narration="Payout",
    reference="TXN-123",
)
```

### PayID transfer

```python
transfer = client.transfers.create(
    type="payid",
    amount=500000,
    pay_id="johndoe",
    narration="Payment",
)
```

### List, verify, resolve

```python
# List transfers
transfers = client.transfers.list(
    page=1,
    page_size=20,
    status="completed",
)

# Verify a transfer
result = client.transfers.verify("TXN-123")

# List banks
banks = client.transfers.list_banks()

# Resolve bank account
account = client.transfers.resolve_account(
    account_number="0123456789",
    bank_code="044",
)
print(account["account_name"])  # 'JOHN DOE'

# Calculate fees
charges = client.transfers.calculate_charges(amount=1000000)

# Get wallet balance
balance = client.transfers.get_balance()
```

## Invoices

```python
# Create
invoice = client.invoices.create(
    customer_name="Jane Doe",
    customer_email="jane@example.com",
    due_date="2026-04-01",
    line_items=[
        {"description": "Web Design", "quantity": 1, "unit_price": 5000000},
        {"description": "Hosting (1yr)", "quantity": 1, "unit_price": 1200000},
    ],
    tax_rate=7.5,
)

# Send (draft → sent)
client.invoices.send(invoice["public_id"])

# List
invoices = client.invoices.list(status="sent")

# Cancel
client.invoices.cancel(invoice["public_id"])
```

## Static PayIDs

```python
# Create
payid = client.static_payid.create(
    pay_id="mystore",
    name="My Store",
    description="Accept payments to my store",
)

# Deactivate / Reactivate
client.static_payid.deactivate(payid["id"])
client.static_payid.reactivate(payid["id"])
```

## Dynamic PayIDs

```python
# Create a time-limited payment link
dpayid = client.dynamic_payid.create(
    amount=1000000,
    name="Donation Drive",
    expires_in_minutes=60,
)

print(dpayid["full_pay_id"])  # e.g. 'donation-drive-xyz.dpay'

# Deactivate
client.dynamic_payid.deactivate(dpayid["id"])
```

## Virtual accounts

```python
# Create
va = client.virtual_accounts.create(
    amount=1000000,
    validity_minutes=60,
)

print(va["account_number"])  # Customer pays to this account

# List
accounts = client.virtual_accounts.list(status="pending")
```

## Wallet

```python
# Get wallet details
wallet = client.wallet.get()

# List members
members = client.wallet.list_members()

# Add / remove members
client.wallet.add_member(pay_id="johndoe")
client.wallet.remove_member("johndoe")
```

## Webhook verification

Verify incoming webhook signatures using your webhook secret:

```python
import os
from zevpay import Webhook, ZevPayError

# In your webhook handler (e.g. Flask, Django, FastAPI)
payload = request.get_data(as_text=True)
signature = request.headers.get("x-zevpay-signature", "")
secret = os.environ["ZEVPAY_WEBHOOK_SECRET"]

try:
    event = Webhook.construct_event(payload, signature, secret)

    if event["event"] == "charge.success":
        # Handle successful checkout payment
        pass
    elif event["event"] == "transfer.success":
        # Handle successful transfer
        pass
    elif event["event"] == "transfer.failed":
        # Handle failed transfer
        pass
    elif event["event"] == "invoice.paid":
        # Handle paid invoice
        pass

    return "OK", 200
except ZevPayError:
    return "Invalid signature", 400
```

::: warning Always verify
Never process webhook events without verifying the signature. See [Verifying Signatures](/webhooks/signatures) for details.
:::

## Error handling

All API errors raise typed exceptions:

```python
from zevpay import (
    ZevPay,
    ValidationError,
    AuthenticationError,
    NotFoundError,
    ConflictError,
    RateLimitError,
    ApiError,
)

client = ZevPay("sk_test_your_secret_key")

try:
    client.transfers.create(type="bank_transfer", amount=1000000)
except ValidationError as e:
    # 400 — invalid parameters
    print(e.code)     # e.g. 'VALIDATION_ERROR'
    print(e.message)  # Human-readable message
except AuthenticationError:
    # 401 — invalid API key
    pass
except NotFoundError:
    # 404 — resource not found
    pass
except ConflictError:
    # 409 — duplicate transaction
    pass
except RateLimitError:
    # 429 — rate limit exceeded
    pass
except ApiError:
    # 500+ — server error
    pass
```

| Exception class | Status | When |
|-----------------|--------|------|
| `ValidationError` | 400 | Invalid request parameters |
| `AuthenticationError` | 401 | Invalid or missing API key |
| `ForbiddenError` | 403 | Insufficient permissions |
| `NotFoundError` | 404 | Resource not found |
| `ConflictError` | 409 | Duplicate resource |
| `RateLimitError` | 429 | Too many requests |
| `ApiError` | 500+ | Server error |

## Context manager

The client can be used as a context manager to ensure the HTTP connection is properly closed:

```python
with ZevPay("sk_test_your_secret_key") as client:
    session = client.checkout.initialize(
        amount=500000,
        email="customer@example.com",
    )
```

## Full example — FastAPI

```python
import os
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse, RedirectResponse
from zevpay import ZevPay, Webhook, ZevPayError

app = FastAPI()
zevpay = ZevPay(os.environ["ZEVPAY_SECRET_KEY"])


@app.post("/api/pay")
async def create_payment(request: Request):
    body = await request.json()

    session = zevpay.checkout.initialize(
        amount=body["amount"],
        email=body["email"],
        reference=f"ORDER-{body['order_id']}",
        callback_url=str(request.url_for("payment_callback")),
    )

    return {"checkout_url": session["checkout_url"]}


@app.get("/api/callback")
async def payment_callback(session_id: str):
    result = zevpay.checkout.verify(session_id)

    if result["status"] == "completed":
        return {"message": "Payment successful!"}

    return {"message": "Payment not completed yet."}


@app.post("/webhooks/zevpay")
async def webhook_handler(request: Request):
    payload = (await request.body()).decode()
    signature = request.headers.get("x-zevpay-signature", "")

    try:
        event = Webhook.construct_event(
            payload, signature, os.environ["ZEVPAY_WEBHOOK_SECRET"]
        )
        print(f"Webhook received: {event['event']}")
        return {"status": "ok"}
    except ZevPayError:
        return JSONResponse({"error": "Invalid signature"}, status_code=400)
```

## Resources

- [API Reference](/api/overview) — Full endpoint documentation
- [Webhook Events](/webhooks/events) — All event types
- [PyPI](https://pypi.org/project/zevpay/) — Package registry
