Metadata-Version: 2.4
Name: ecitizen-pesaflow-gateway
Version: 0.1.1
Summary: Kenya eCitizen / PesaFlow payment gateway SDK and CLI for Python - Django, Flask, FastAPI, or plain Python. Signed M-Pesa STK Push checkout, HMAC webhook verification, zero dependencies, Python 3.7-3.13.
Author: Jose Modi
License: MIT
Project-URL: Homepage, https://github.com/JoseModi97/ecitizen-pesaflow-gateway-python
Project-URL: Source, https://github.com/JoseModi97/ecitizen-pesaflow-gateway-python
Project-URL: Repository, https://github.com/JoseModi97/ecitizen-pesaflow-gateway-python
Project-URL: Bug Tracker, https://github.com/JoseModi97/ecitizen-pesaflow-gateway-python/issues
Project-URL: Issues, https://github.com/JoseModi97/ecitizen-pesaflow-gateway-python/issues
Project-URL: Documentation, https://github.com/JoseModi97/ecitizen-pesaflow-gateway-python#readme
Project-URL: Node.js / npm sibling package, https://www.npmjs.com/package/ecitizen-pesaflow-gateway
Keywords: ecitizen,e-citizen,pesaflow,pesaflow-gateway,ecitizen-gateway,kenya,kenya-payment-gateway,kenyan-payments,government-payments,payment,payment-gateway,payment-sdk,payments,checkout,mpesa,m-pesa,stk-push,mpesa-stk-push,mobile-money,safaricom,django,django-payment-gateway,flask,flask-payment-gateway,fastapi,fastapi-payment-gateway,webhook,webhook-verification,ipn,hmac,hmac-sha256,fintech,kenya-fintech,python-sdk
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Natural Language :: English
Classifier: Operating System :: OS Independent
Classifier: Environment :: Web Environment
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.7
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: Programming Language :: Python :: 3.13
Classifier: Framework :: Django
Classifier: Framework :: Flask
Classifier: Framework :: FastAPI
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Office/Business :: Financial :: Point-Of-Sale
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Requires-Python: >=3.7
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dotenv
Requires-Dist: python-dotenv>=0.19; extra == "dotenv"
Provides-Extra: django
Requires-Dist: django>=3.2; extra == "django"
Provides-Extra: flask
Requires-Dist: flask>=2.0; extra == "flask"
Provides-Extra: fastapi
Requires-Dist: fastapi>=0.95; extra == "fastapi"
Requires-Dist: python-multipart>=0.0.5; extra == "fastapi"
Provides-Extra: test
Requires-Dist: pytest>=7.0; extra == "test"
Requires-Dist: django>=3.2; extra == "test"
Requires-Dist: flask>=2.0; extra == "test"
Requires-Dist: fastapi>=0.95; extra == "test"
Requires-Dist: python-multipart>=0.0.5; extra == "test"
Requires-Dist: httpx>=0.23; extra == "test"
Dynamic: license-file

# ecitizen-pesaflow-gateway (Python)

> **Kenya eCitizen / PesaFlow payment gateway SDK for Python** — signed M-Pesa STK Push checkout, HMAC-SHA256 webhook/IPN verification, and ready-made adapters for **Django**, **Flask**, and **FastAPI**. Zero dependencies, Python 3.7–3.13.

[![PyPI version](https://img.shields.io/pypi/v/ecitizen-pesaflow-gateway.svg?style=flat-square&cacheSeconds=300)](https://pypi.org/project/ecitizen-pesaflow-gateway/)
[![PyPI downloads](https://img.shields.io/pypi/dm/ecitizen-pesaflow-gateway.svg?style=flat-square&cacheSeconds=300)](https://pypi.org/project/ecitizen-pesaflow-gateway/)
[![CI](https://github.com/JoseModi97/ecitizen-pesaflow-gateway-python/actions/workflows/ci.yml/badge.svg)](https://github.com/JoseModi97/ecitizen-pesaflow-gateway-python/actions)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
[![Python Versions](https://img.shields.io/badge/python-3.7%2B-blue.svg?style=flat-square)](https://www.python.org/)
[![Django | Flask | FastAPI](https://img.shields.io/badge/frameworks-Django%20%7C%20Flask%20%7C%20FastAPI-informational.svg?style=flat-square)](#compatibility)

A beginner-friendly Kenya eCitizen / PesaFlow payment gateway SDK and CLI for **Django**, **Flask**, **FastAPI**, and **plain Python**. Build signed checkout payloads, render an instant payment button, and verify webhook callbacks with plain-English fields.

**Keywords:** eCitizen payment gateway Python, PesaFlow SDK, Kenya payment gateway Python, M-Pesa STK Push Python, Django payment gateway, Flask payment gateway, FastAPI payment gateway, HMAC webhook verification, Safaricom M-Pesa integration, Kenyan government payments API.

This is the Python sibling of [`ecitizen-pesaflow-gateway`](https://www.npmjs.com/package/ecitizen-pesaflow-gateway) on npm ([source](https://github.com/JoseModi97/ecitizen-pesaflow-gateway)). Both packages implement the exact same HMAC-SHA256 signing/verification algorithm and are tested against shared cross-language golden test vectors, so a payload signed by one is verifiable by the other.

- **Forward & Backward Compatible**: Pure standard library, no C extensions. Tested on **Python 3.7 through 3.13**.
- **Zero Runtime Dependencies**: The `EcitizenGateway`/`EcitizenClient` core (signing, verification, HTTP submission) uses only `hmac`, `hashlib`, `base64`, and `urllib` from the standard library - installing it never conflicts with your Django/Flask/FastAPI project's own dependency pins.
- **Interactive CLI Setup**: Run `ecitizen-pesaflow init` to interactively configure your credentials, write `.env` entries, and scaffold a framework-specific payment view in seconds.
- **Headless Payment CLI**: Sign and submit payments straight from the terminal with `ecitizen-pesaflow pay` / `status` - no browser, no HTML, no scaffolding required.
- **Instant Payment Button**: Render a ready-to-use, HMAC-signed payment form in one call (`client.pay_button(...)`) or get the raw payload (`client.checkout(...)`) for a custom UI.
- **Safaricom M-Pesa STK Push**: Built-in Kenyan phone normalization (`PhoneHelper`) so `0712345678` / `0112345678` / `+254712345678` all become `2547XXXXXXXX` / `2541XXXXXXXX`.
- **Timing-Safe Cryptographic Verification**: Validate server-to-server IPN notifications with `hmac.compare_digest`.
- **Pre-Built Adapters**: Webhook views for Django, Flask, and FastAPI - or call `client.verify(payload)` directly from any other framework (Bottle, Pyramid, Sanic, Tornado, plain WSGI/ASGI).

---

## Compatibility

- **Python**: >= 3.7 (tested on 3.7, 3.8, 3.9, 3.10, 3.11, 3.12, 3.13 in CI).
- **Web Frameworks**: Django, Flask, FastAPI out of the box via dedicated adapters; any other Python framework by calling `client.verify(payload_dict)` directly.

---

## Installation

```bash
pip install ecitizen-pesaflow-gateway
```

Optional `.env` auto-loading for the CLI:

```bash
pip install "ecitizen-pesaflow-gateway[dotenv]"
```

---

## Interactive CLI Setup

```bash
ecitizen-pesaflow init
```

The wizard will:
1. Prompt for your eCitizen credentials (**API Client ID**, **API Key**, **Merchant Secret**, **Service ID**).
2. Ask for your target framework (**Django**, **Flask**, **FastAPI**, or **Standalone**).
3. Write your `.env` file.
4. Scaffold a ready-to-run payment view for your selected framework.

Non-interactive / CI:
```bash
ecitizen-pesaflow init --client-id "MY_ID" --api-key "MY_KEY" --secret "MY_SEC" --service-id "MY_SVC" --framework flask --yes
```

Verify your environment and HMAC algorithm against a known-good test vector:
```bash
ecitizen-pesaflow test
```

Sign and submit a payment directly from the terminal:
```bash
ecitizen-pesaflow pay --amount 500 --reference INV-0001 --description "School fees" \
  --name "Jane Doe" --id-number 12345678 --phone 0712345678
```
Add `--dry-run` to print the signed payload without sending it. Pass `--yes`/`-y` to skip the confirmation prompt in scripts/CI.

Check settlement status:
```bash
ecitizen-pesaflow status --reference INV-0001 --status-url https://your-status-endpoint
```

Run `ecitizen-pesaflow help` for the full flag reference.

---

## Quickstart: Flask

```python
from flask import Flask
from ecitizen_pesaflow_gateway import EcitizenClient, PhoneHelper
from ecitizen_pesaflow_gateway.adapters.flask_adapter import create_flask_webhook_view

app = Flask(__name__)
client = EcitizenClient()  # reads ECITIZEN_* from the environment


@app.get("/payment/pay")
def pay():
    return client.pay_button({
        "amount": 1500,
        "reference": "INV-0001",
        "description": "Land Rates Clearance",
        "name": "John Doe",
        "idNumber": "28374619",
        "phone": PhoneHelper.normalize("0712345678"),
        "sendStkPush": True,
        "notifyUrl": "https://yourdomain.com/payment/notify",
    }, "Proceed to eCitizen", {"class": "btn btn-success btn-lg"})


def on_success(result):
    print(f"Payment confirmed for {result.reference}, amount {result.amount_paid}")
    # Order.query.filter_by(reference=result.reference).update({"status": "paid"})


app.add_url_rule(
    "/payment/notify",
    view_func=create_flask_webhook_view(client, on_success=on_success),
    methods=["POST"],
)
```

## Quickstart: FastAPI

eCitizen POSTs webhooks as `application/x-www-form-urlencoded`, and FastAPI/Starlette's `request.form()` needs `python-multipart` to parse that - install with the `fastapi` extra:
```bash
pip install "ecitizen-pesaflow-gateway[fastapi]"
```

```python
from fastapi import FastAPI
from ecitizen_pesaflow_gateway import EcitizenClient, PhoneHelper
from ecitizen_pesaflow_gateway.adapters.fastapi_adapter import create_fastapi_webhook_route

app = FastAPI()
client = EcitizenClient()


async def on_success(result):
    print("Confirmed:", result.reference, result.amount_paid)


app.include_router(create_fastapi_webhook_route(client, on_success=on_success), prefix="/payment")
```

## Quickstart: Django

```python
# views.py
from django.http import HttpResponse
from ecitizen_pesaflow_gateway import EcitizenClient, PhoneHelper
from ecitizen_pesaflow_gateway.adapters.django_adapter import create_django_webhook_view

client = EcitizenClient()


def pay(request):
    return HttpResponse(client.pay_button({
        "amount": 500,
        "reference": "INV-0001",
        "description": "School fees",
        "name": "Jane Doe",
        "idNumber": "12345678",
        "phone": PhoneHelper.normalize("0712345678"),
        "sendStkPush": True,
        "notifyUrl": request.build_absolute_uri("/payment/notify"),
    }))


notify = create_django_webhook_view(client, on_success=lambda r: print("paid", r.reference))
```

```python
# urls.py
from django.urls import path
from . import views

urlpatterns = [
    path("payment/pay", views.pay),
    path("payment/notify", views.notify),
]
```

## Standalone Usage (any other framework, or none)

```python
from ecitizen_pesaflow_gateway import EcitizenClient, PhoneHelper

client = EcitizenClient({
    "apiClientID": "YOUR_API_CLIENT_ID",
    "apiKey": "YOUR_API_KEY",
    "secret": "YOUR_SECRET",
    "serviceID": "YOUR_SERVICE_ID",
})

# 1. Generate a signed checkout payload
checkout = client.checkout({
    "amount": 500,
    "reference": "INV-0001",
    "description": "School fees",
    "name": "Jane Doe",
    "idNumber": "12345678",
    "phone": "0712345678",  # normalized automatically to 254712345678
})

# 2. Verify an inbound webhook notification
result = client.verify(webhook_payload)  # e.g. request.POST.dict(), request.form, etc.
if result.success:
    print(f"Payment confirmed for {result.reference} ({result.amount_paid} KES)")
else:
    print(f"Verification failed: {result.description}")

# 3. Or skip the browser/HTML entirely and prompt the payment directly
submission = client.initiate_payment({
    "amount": 500,
    "reference": "INV-0002",
    "description": "School fees",
    "name": "Jane Doe",
    "idNumber": "12345678",
    "phone": "0712345678",
    "sendStkPush": True,
})
print(submission.http_status, submission.response_body)

# 4. Poll settlement status (requires statusUrl / ECITIZEN_STATUS_URL)
status = client.check_payment_status("INV-0002")
print(status.http_status, status.response_body)
```

---

## API Reference Overview

| Method / Utility | Description |
|---|---|
| `client.checkout(...)` | Generates a signed checkout payload and endpoint URL |
| `client.pay_button(...)` | Generates a self-contained HTML form and payment button |
| `client.initiate_payment(...)` | Directly submits payment without browser/HTML |
| `client.check_payment_status(...)` | Polls payment settlement status |
| `client.verify(payload)` | Verifies IPN webhook HMAC signature |
| `client.is_paid(payload)` | Returns a boolean verification result |
| `PhoneHelper.normalize(phone)` | Normalizes Kenyan phone numbers (`07...`, `01...` -> `254...`) |
| `ecitizen-pesaflow init` | Interactive project setup wizard |

---

## Security & Best Practices

1. **Keep Secrets Private**: Store your API key and merchant secret in `.env` or your platform's secret manager. Never commit secrets to git.
2. **CSRF Exemption**: eCitizen servers POST webhook notifications from outside your application's domain. `create_django_webhook_view` is already `@csrf_exempt`; if you build your own view, exempt the notification route explicitly.
3. **Timing-Safe Comparison**: Webhook signatures are checked using `hmac.compare_digest` to prevent side-channel timing attacks.
4. **Phone Formatting**: Always use `PhoneHelper.normalize()` so phone numbers match Safaricom / Airtel STK push formats (`2547XXXXXXXX` or `2541XXXXXXXX`).

---

## Development

```bash
pip install -e ".[test]"
pytest -q
```

`tox.ini` runs the suite across every supported Python version if you have multiple interpreters installed locally; CI runs the same matrix (3.7-3.13) plus a zero-dependency install check on every push.

---

## Related Projects

- [`ecitizen-pesaflow-gateway`](https://www.npmjs.com/package/ecitizen-pesaflow-gateway) on npm — the Node.js / TypeScript sibling ([source](https://github.com/JoseModi97/ecitizen-pesaflow-gateway)) for Express, Fastify, Next.js, and NestJS. Both packages share the same signing algorithm and cross-language golden test vectors.

---

## License

MIT License. See [LICENSE](LICENSE) for details.
