Metadata-Version: 2.4
Name: prostackng
Version: 1.0.0
Summary: Official Python SDK for ProStack NG — Africa's unified API gateway
Author-email: ProStack NG Technologies Ltd <contact@prostackng.com.ng>
License: MIT
Project-URL: Homepage, https://prostackng.com.ng
Project-URL: Documentation, https://prostackng.com.ng/docs
Project-URL: Repository, https://github.com/PereTheDataAnalyst/prostack-api
Project-URL: Bug Tracker, https://github.com/PereTheDataAnalyst/prostack-api/issues
Keywords: prostack,nigeria,api,sms,bvn,nin,payments,identity,kyc,vtu,fintech,africa
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
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: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Internet :: WWW/HTTP
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.27.0
Requires-Dist: pydantic>=2.0.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
Requires-Dist: pytest-cov>=5.0; extra == "dev"
Requires-Dist: respx>=0.21; extra == "dev"
Requires-Dist: mypy>=1.9; extra == "dev"
Requires-Dist: ruff>=0.4; extra == "dev"
Dynamic: license-file

# prostackng (Python)

Official Python SDK for [ProStack NG](https://prostackng.com.ng) — Africa's unified API gateway.

**One API key. Five services. Billed in Naira.**

[![PyPI version](https://badge.fury.io/py/prostackng.svg)](https://pypi.org/project/prostackng/)
[![Python](https://img.shields.io/pypi/pyversions/prostackng)](https://pypi.org/project/prostackng/)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)

---

## Installation

```bash
pip install prostackng
```

---

## Quick Start

```python
from prostackng import ProStack

client = ProStack(api_key="psk_live_...")

# Send an SMS
sms = client.notifications.send_sms(
    to="08012345678",
    message="Your OTP is 4821. Valid for 5 minutes.",
)
print(sms.status)  # "sent"

# Verify a BVN
bvn = client.identity.verify_bvn(
    bvn="12345678901",
    first_name="John",
    last_name="Doe",
)
if bvn.status == "verified":
    print(f"Verified: {bvn.first_name} {bvn.last_name}")

# Initiate a payment
payment = client.payments.initiate(amount=5000, email="user@app.com")
# Redirect user to:
print(payment.authorization_url)
```

Get your API key at [prostack-api-dashboard.vercel.app](https://prostack-api-dashboard.vercel.app).

---

## Async Support (FastAPI, aiohttp)

```python
from prostackng import AsyncProStack

client = AsyncProStack(api_key="psk_live_...")

# All methods are awaitable
sms = await client.notifications.send_sms(to="08012345678", message="Hello")
bvn = await client.identity.verify_bvn(bvn="12345678901")
payment = await client.payments.initiate(amount=5000, email="user@app.com")
```

**FastAPI example:**

```python
from fastapi import FastAPI
from prostackng import AsyncProStack

app = FastAPI()
prostack = AsyncProStack(api_key="psk_live_...")

@app.post("/send-otp")
async def send_otp(phone: str, otp: str):
    result = await prostack.notifications.send_sms(
        to=phone,
        message=f"Your OTP is {otp}. Valid for 5 minutes.",
    )
    return {"status": result.status, "cost": result.cost}
```

---

## Services

### Notifications

```python
# Single SMS
result = client.notifications.send_sms(
    to="08012345678",
    message="Hello!",
    sender_id="MyApp",    # optional
)

# Email
result = client.notifications.send_email(
    to="user@example.com",
    subject="Welcome to MyApp",
    body="Thanks for signing up.",
    html="<p>Thanks for signing up.</p>",  # optional
)

# Bulk SMS (up to 10,000 recipients — async processing)
job = client.notifications.send_bulk_sms(
    recipients=[
        {"to": "08012345678", "message": "Hi John!"},
        {"to": "08098765432", "message": "Hi Jane!"},
    ],
    callback_url="https://yourapp.com/webhooks/bulk",  # optional
)
print(f"Job {job.job_id}: {job.total} recipients queued")

# Poll job status
status = client.notifications.get_bulk_job_status(job.job_id)
print(f"{status.sent}/{status.total} sent")
```

### Payments

```python
# Initiate
payment = client.payments.initiate(
    amount=5000,               # ₦5,000 — kobo conversion handled automatically
    email="user@example.com",
    provider="paystack",       # or "flutterwave"
    reference="MY-REF-001",    # optional
    metadata={"order_id": "99"},  # optional
)
# Redirect to payment.authorization_url

# Verify
result = client.payments.verify("MY-REF-001")
if result.status == "success":
    pass  # fulfill order

# List transactions
transactions = client.payments.list(limit=50)
```

### Identity / KYC

> BVN and NIN verification require a **LIVE API key**.

```python
# BVN
result = client.identity.verify_bvn(
    bvn="12345678901",
    first_name="John",         # optional cross-validation
    last_name="Doe",
    date_of_birth="1990-01-15",  # YYYY-MM-DD
)

# NIN
result = client.identity.verify_nin(nin="12345678901", first_name="Jane")

# Phone
result = client.identity.verify_phone(phone="08012345678", network="mtn")

print(result.status)       # "verified"
print(result.first_name)   # "John"
print(result.cost)         # 100.0
```

### VTU / Utility Bills

```python
# Airtime
client.vtu.buy_airtime(phone="08012345678", amount=500, network="mtn")

# Data — list plans first
plans = client.vtu.list_data_plans("airtel")
client.vtu.buy_data(phone="08012345678", plan_id=plans[0].id, network="airtel")

# Electricity
result = client.vtu.pay_electricity(
    meter_number="12345678901",
    disco="phed",
    meter_type="prepaid",
    amount=5000,
)
print(result.token)  # "1234-5678-9012-3456" (prepaid token)

# Cable TV
client.vtu.pay_cable_tv(decoder_number="1234567890", provider="dstv", package="CONFAM")
```

### Reports

```python
# Generate (async — poll for completion)
report = client.reports.generate(
    title="April 2026 Sales",
    output_format="pdf",      # "pdf" | "excel" | "both"
    data=[
        {"date": "2026-04-01", "product": "Widget A", "amount": 15000},
        {"date": "2026-04-02", "product": "Widget B", "amount": 7500},
    ],
)

# Poll until complete
import time
while report.status in ("pending", "processing"):
    time.sleep(3)
    report = client.reports.get_by_id(report.id)

print(report.output_url)  # download URL
```

### Webhooks

```python
wh = client.webhooks.create(
    url="https://yourapp.com/webhooks/prostack",
    events=["payment.success", "identity.verified", "vtu.success"],
)
webhooks = client.webhooks.list()
client.webhooks.delete(wh.id)
```

---

## Webhook Verification

```python
from prostackng import ProStack, verify_webhook, parse_webhook_payload

# Flask
from flask import Flask, request
app = Flask(__name__)

@app.route("/webhooks/prostack", methods=["POST"])
def prostack_webhook():
    is_valid = verify_webhook(
        payload=request.get_data(),       # raw bytes — do NOT parse first
        signature=request.headers.get("X-ProStack-Signature", ""),
        secret=os.environ["PROSTACK_WEBHOOK_SECRET"],
    )
    if not is_valid:
        return "Invalid signature", 400

    event = parse_webhook_payload(request.get_data())

    if event["event"] == "payment.success":
        payment_id = event["data"]["id"]
        # fulfill order...

    return "OK", 200


# Django
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
def prostack_webhook(request):
    is_valid = verify_webhook(
        payload=request.body,
        signature=request.headers.get("X-Prostack-Signature", ""),
        secret=settings.PROSTACK_WEBHOOK_SECRET,
    )
    if not is_valid:
        return HttpResponse(status=400)
    event = parse_webhook_payload(request.body)
    return HttpResponse(status=200)
```

---

## Error Handling

```python
from prostackng import (
    ProStack,
    ProStackError,
    AuthenticationError,
    ValidationError,
    InsufficientBalanceError,
    RateLimitError,
    NetworkError,
    ServerError,
)
import time

try:
    result = client.identity.verify_bvn(bvn="12345678901")

except InsufficientBalanceError as e:
    print(f"Top up needed. Have ₦{e.balance}, need ₦{e.required}")

except RateLimitError as e:
    print(f"Rate limited. Retry in {e.retry_after}s")
    time.sleep(e.retry_after)

except AuthenticationError:
    print("Invalid API key — check your ProStack dashboard")

except ValidationError as e:
    print(f"Validation failed: {e.fields}")

except NetworkError as e:
    print(f"Connection failed: {e}")

except ProStackError as e:
    print(f"API error {e.status_code}: {e.message}")
```

The SDK automatically retries `429` and `5xx` errors with exponential backoff. Auth errors (401/403) and validation errors (400/422) are **not** retried.

---

## Context Managers

```python
# Sync — auto-closes connection pool
with ProStack(api_key="psk_live_...") as client:
    result = client.vtu.buy_airtime(phone="080...", amount=500, network="mtn")

# Async — auto-closes async connection pool
async with AsyncProStack(api_key="psk_live_...") as client:
    result = await client.payments.initiate(amount=5000, email="u@e.com")
```

---

## Requirements

- Python 3.8+
- `httpx >= 0.27`
- `pydantic >= 2.0`

---

## License

MIT © [ProStack NG Technologies Ltd](https://prostackng.com.ng)

Built in Port Harcourt, Nigeria 🇳🇬
