Metadata-Version: 2.4
Name: afrikdp
Version: 1.0.0
Summary: Official Python client for the AfriKDP Publishing API — upload books, run checkout, and handle webhooks from your backend.
Author: AfriKDP
License: MIT
Project-URL: Homepage, https://afrikdp.com/docs.html
Project-URL: Documentation, https://afrikdp.com/docs.html
Project-URL: Repository, https://github.com/afrikdp/afrikdp-python
Keywords: afrikdp,publishing,ebooks,paystack,checkout,books,africa
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.28.0
Dynamic: license-file

# afrikdp

Official Python client for the [AfriKDP Publishing API](https://afrikdp.com/docs.html) — publishing infrastructure for Africa. Upload a book once, sell it everywhere, without building storage, checkout, or delivery yourself.

Requires **Python 3.8+**.

## Install

```bash
pip install afrikdp
```

## Quick start

```python
from afrikdp import AfriKDPClient

client = AfriKDPClient(secret_key="sk_live_...")

book = client.upload_book(
    title="Ubuntu",
    description="A short reflection on community and shared humanity.",
    price=2500,       # whole currency units — 2500 = ₦2,500. 0 = free.
    currency="NGN",
    file="./ubuntu.pdf",
    cover="./cover.jpg",
)

print(book["book_id"], book["slug"])
# → bk_8937  ubuntu-8937a1c2
```

That's it — AfriKDP stores the file, generates the book's slug, and it's ready to sell.

## Two kinds of keys

| Key | Used for | Where it belongs |
|---|---|---|
| `sk_live_...` (secret) | Uploading/updating books, webhooks, distribution | Your server only |
| `pk_live_...` (public) | Listing/fetching books, checkout, verify | Safe in a browser, but this package is meant for your backend |

```python
# Full access
client = AfriKDPClient(secret_key="sk_live_...")

# Read-only — fine if this process should never be able to upload/edit
client = AfriKDPClient(public_key="pk_live_...")
```

If you pass `secret_key`, it's used for everything (including reads). Pass only `public_key` if you deliberately want a read-only client.

## Uploading a book

```python
result = client.upload_book(
    title="Ubuntu",
    description="...",
    price=2500,
    currency="NGN",              # optional, defaults to NGN
    file="./ubuntu.pdf",         # a file path, or raw bytes
    cover="./cover.jpg",         # optional — a file path, or raw bytes
    visibility="public",         # "public" | "private" | "unlisted"
    external_book_id="my-db-id-123",  # optional — your own ID, for mapping back
)
```

## Updating a book

Editing a book that already exists on AfriKDP — only pass the fields that changed:

```python
client.update_book("bk_8937", price=3000)

# Replacing the file too:
client.update_book("bk_8937", price=3000, file="./ubuntu-v2.pdf")
```

## Listing and fetching books

```python
books = client.list_books()
one = client.get_book("bk_8937")
```

## Checkout (server-side)

Most integrations should use the [browser SDK](https://afrikdp.com/docs.html#sdk) for checkout — it opens a branded modal and handles Paystack's inline popup for you. Use this only if you're building your own checkout UI:

```python
order = client.checkout(
    book_id="bk_8937",
    buyer_email="reader@example.com",
    buyer_country="NG",  # optional
)

if order["free"]:
    # order["order_id"] is already paid — go straight to get_download_link()
    pass
else:
    # order["reference"], order["amount"], order["paystack_public_key"]
    # → hand these to Paystack's inline.js in the browser
    pass
```

After the buyer pays:

```python
result = client.verify_payment(order["reference"])
if result["status"] == "paid":
    link = client.get_download_link(result["order_id"])
    # link["download_url"] is valid for 2 minutes
```

## Distribution

```python
marketplace = client.create_marketplace(
    marketplace_name="Sarah's Bookstore",
    domain="books.sarah.com",
    visibility="private",
)

client.push_distribution(
    book_id="bk_8937",
    marketplace_ids=[marketplace["marketplace"]["id"]],
)
```

## Webhooks

```python
hook = client.register_webhook(
    url="https://yoursite.com/hooks/afrikdp",
    events=["book.created", "sale.completed"],
)
print(hook["secret"])  # shown once — store it now
```

Verifying an incoming webhook (use the **raw** request body, not a re-parsed dict) — example with Flask:

```python
from flask import Flask, request
from afrikdp import AfriKDPClient
import os

app = Flask(__name__)

@app.route("/hooks/afrikdp", methods=["POST"])
def afrikdp_webhook():
    signature = request.headers.get("X-AfriKDP-Signature", "")
    valid = AfriKDPClient.verify_webhook_signature(
        request.get_data(), signature, os.environ["AFRIKDP_WEBHOOK_SECRET"]
    )
    if not valid:
        return "Invalid signature", 401

    event = request.get_json()
    # handle event["event"], event["data"]
    return "", 200
```

## Error handling

Every method raises `AfriKDPError` on a non-2xx response:

```python
from afrikdp import AfriKDPClient, AfriKDPError

try:
    client.upload_book(title="t", description="d", price=0, file=b"...")
except AfriKDPError as err:
    print(err.status, str(err), err.body)
```

## Configuration

```python
AfriKDPClient(
    secret_key="sk_live_...",
    api_base="https://afrikdp.com/v1",  # optional override
    timeout=30,                          # optional, seconds
)
```

## License

MIT
