Metadata-Version: 2.4
Name: paylode-python
Version: 1.0.0
Summary: Official Python SDK for Paylode Services Limited — CBN Licensed PSSP
Home-page: https://github.com/Gokeakinboro/Payment-Gateway
Author: Paylode Services Limited
Author-email: dev@paylodeservices.com
Project-URL: Documentation, https://docs.paylodeservices.com
Project-URL: Bug Tracker, https://github.com/Gokeakinboro/Payment-Gateway/issues
Keywords: paylode payments nigeria fintech cbn pssp payment-gateway
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: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Office/Business :: Financial
Requires-Python: >=3.7
Description-Content-Type: text/markdown
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: keywords
Dynamic: project-url
Dynamic: requires-python
Dynamic: summary

# paylode-python

Official Python SDK for [Paylode Services Limited](https://paylodeservices.com) — CBN Licensed Payment Solution Service Provider (PSSP).

[![PyPI version](https://img.shields.io/pypi/v/paylode-python)](https://pypi.org/project/paylode-python/)
[![Python versions](https://img.shields.io/pypi/pyversions/paylode-python)](https://pypi.org/project/paylode-python/)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)

## Requirements

- Python 3.7+
- A Paylode secret key (`sk_live_...` or `sk_test_...`) — obtain from your merchant dashboard

## Installation

```bash
pip install paylode-python
```

Zero external dependencies — uses Python standard library only (`urllib`, `hmac`, `hashlib`).

## Quick start

```python
from paylode import Paylode

client = Paylode("sk_live_xxxxxxxxxxxxxxxxxxxx")

# 1. Initialize a payment — redirect your customer to authorization_url
txn = client.transaction.initialize(
    email="customer@example.com",
    amount=500_000,           # ₦5,000 in kobo (1 kobo = 0.01 naira)
    callback_url="https://yoursite.com/payment/complete",
    channels=["card", "bank_transfer"],
    metadata={"order_id": "ORD-9812", "customer_name": "Ada Obi"},
)
redirect_url = txn["data"]["authorization_url"]

# 2. Verify server-side when the customer returns — ALWAYS do this
result = client.transaction.verify("ORD-9812")
if result["data"]["status"] == "success":
    fulfill_order(result["data"]["metadata"]["order_id"])
```

---

## API reference

### `Paylode(secret_key, *, sandbox=None)`

| Parameter    | Type           | Description                                               |
|--------------|----------------|-----------------------------------------------------------|
| `secret_key` | str            | Your `sk_live_...` or `sk_test_...` key (required)        |
| `sandbox`    | bool, optional | Force sandbox mode (auto-detected from key prefix)        |

```python
client = Paylode("sk_live_xxxxxxxxxxxxxxxxxxxx")
print(client.sandbox)   # False — auto-detected from live key
print(client.version)   # '1.0.0'
print(repr(client))     # Paylode(mode='live', version='1.0.0')
```

---

### `client.transaction`

#### `.initialize(*, email, amount, ...)` → `dict`

Initialize a new payment. Redirect your customer to `data.authorization_url`.

| Parameter      | Type       | Required | Description                                                   |
|----------------|------------|----------|---------------------------------------------------------------|
| `email`        | str        | Yes      | Customer email address                                        |
| `amount`       | int        | Yes      | Amount in **kobo** (minimum ₦100 = `10_000` kobo)           |
| `reference`    | str        | No       | Unique transaction reference (auto-generated if omitted)      |
| `currency`     | str        | No       | `'NGN'` (default)                                             |
| `callback_url` | str        | No       | URL to redirect customer after payment                        |
| `channels`     | list[str]  | No       | `['card', 'bank_transfer', 'ussd', 'direct_debit']`          |
| `metadata`     | dict       | No       | Arbitrary key-value pairs — returned in webhooks              |

```python
txn = client.transaction.initialize(
    email="customer@example.com",
    amount=250_000,                     # ₦2,500
    reference="ORD-2026-00142",         # optional, must be unique
    callback_url="https://myshop.com/confirm",
    channels=["card", "bank_transfer"],
    metadata={"order_id": "ORD-9812", "plan": "premium"},
)

redirect_url = txn["data"]["authorization_url"]  # send customer here
reference    = txn["data"]["reference"]           # store this
```

#### `.verify(reference)` → `dict`

Verify a transaction. **Always call this server-side before fulfilling any order.**

```python
result = client.transaction.verify("ORD-2026-00142")

if result["data"]["status"] == "success":
    amount_paid = result["data"]["amount"]   # in kobo
    fees        = result["data"]["fees"]     # platform fees
    order_id    = result["data"]["metadata"]["order_id"]
    fulfill_order(order_id)
```

#### `.list(...)` → `dict`

```python
transactions = client.transaction.list(
    page=1,
    per_page=50,
    status="success",       # 'success' | 'failed' | 'pending'
    from_date="2026-01-01",
    to_date="2026-01-31",
)
for txn in transactions["data"]:
    print(txn["reference"], txn["amount"])
```

#### `.fetch(transaction_id)` → `dict`

```python
txn = client.transaction.fetch("txn_01JXYZ123456")
```

#### `.refund(reference, amount=None, reason='')` → `dict`

```python
# Full refund
client.transaction.refund("ORD-2026-00142")

# Partial refund — ₦2,000
client.transaction.refund(
    "ORD-2026-00142",
    amount=200_000,
    reason="Item out of stock",
)
```

---

### `client.customer`

```python
# Create
customer = client.customer.create(
    email="ada@example.com",
    first_name="Ada",
    last_name="Obi",
    phone="+2348012345678",
    metadata={"plan": "gold"},
)

# Fetch by email or customer code
c = client.customer.fetch("ada@example.com")
c = client.customer.fetch("CUS_abc123")

# List
customers = client.customer.list(page=1, per_page=50)

# Update
client.customer.update("CUS_abc123", phone="+2349011112222")
```

---

### `client.subaccount` — for aggregators

Subaccounts represent merchants under an aggregator. Paylode uses them to automatically split settlement at the point of transaction.

```python
# Register a merchant under your aggregator account
sub = client.subaccount.create(
    business_name="Shoprite Nigeria",
    settlement_bank="GTB",
    account_number="0123456789",
    percentage_charge=70,       # merchant receives 70% of each transaction
    description="Shoprite Lagos Island branch",
)
subaccount_code = sub["data"]["subaccount_code"]   # SUB_xxxx

# Include when initializing a split transaction
txn = client.transaction.initialize(
    email="buyer@example.com",
    amount=100_000,
    metadata={"order_id": "ORD-9812"},
)
```

```python
client.subaccount.fetch("SUB_abc123")
client.subaccount.list(page=1, per_page=50)
client.subaccount.update("SUB_abc123", percentage_charge=75)
```

---

### `client.settlement`

```python
# List settlements
settlements = client.settlement.list(
    page=1,
    per_page=50,
    from_date="2026-01-01",
    to_date="2026-01-31",
)

# Fetch one by ID
s = client.settlement.fetch("STL_01JX123456")
```

---

### `Paylode.verify_webhook(raw_body, signature, secret)` → `bool`

Verify a webhook signature. Call at the top of every webhook handler.

```python
import hashlib, hmac

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

@csrf_exempt
def paylode_webhook(request):
    sig    = request.META.get("HTTP_X_PAYLODE_SIGNATURE", "")
    secret = settings.PAYLODE_WEBHOOK_SECRET

    if not Paylode.verify_webhook(request.body, sig, secret):
        return HttpResponse(status=401)

    import json
    event = json.loads(request.body)

    if event["event"] == "payment.success":
        fulfill_order(event["data"]["metadata"]["order_id"])
    elif event["event"] == "refund.processed":
        update_order_status(event["data"]["reference"], "refunded")

    return HttpResponse(status=200)
```

```python
# Flask example
from flask import Flask, request, abort

@app.route('/webhook/paylode', methods=['POST'])
def paylode_webhook():
    if not Paylode.verify_webhook(
        request.get_data(),
        request.headers.get("X-Paylode-Signature", ""),
        app.config["PAYLODE_WEBHOOK_SECRET"],
    ):
        abort(401)

    event = request.get_json(force=True)
    if event["event"] == "payment.success":
        fulfill_order(event["data"]["metadata"]["order_id"])
    return "", 200
```

---

### Static helpers

```python
Paylode.generate_ref("ORD")      # 'ORD-M6X2K1-A3F9B2C1'
Paylode.kobo_to_naira(500_000)   # 5000.0
Paylode.naira_to_kobo(5000)      # 500000
```

---

### KYC tier limits

```python
limits = client.kyc_limits

limits["tier_1"]["single_txn"]  # 5_000_000 kobo (₦50,000)
limits["tier_1"]["daily"]        # 30_000_000 kobo (₦300,000)
limits["tier_2"]["single_txn"]  # 100_000_000 kobo (₦1,000,000)
limits["tier_3"]["single_txn"]  # 500_000_000 kobo (₦5,000,000)
```

---

## Error handling

```python
from paylode.exceptions import (
    PaylodeValidationError,   # Invalid params — client-side
    PaylodeAPIError,          # Non-2xx API response
    PaylodeAuthError,         # 401 — invalid or missing key
    PaylodeNetworkError,      # Could not reach Paylode API
    PaylodeError,             # Base class — catches all of the above
)

try:
    txn = client.transaction.initialize(email="x", amount=100)
except PaylodeValidationError as e:
    print(e.message)   # 'amount must be an integer in kobo, minimum ₦100'
    print(e.field)     # 'amount'
except PaylodeAuthError:
    print("Invalid API key")
except PaylodeAPIError as e:
    print(e.status_code)   # e.g. 422
    print(e.error_code)    # e.g. 'DUPLICATE_REFERENCE'
    print(e.raw)           # full API response dict
except PaylodeNetworkError:
    print("Could not reach Paylode API — retry later")
```

---

## Sandbox / test mode

```python
# sk_test_ key auto-sets sandbox=True
client = Paylode("sk_test_xxxxxxxxxxxxxxxxxxxx")
print(client.sandbox)   # True
```

Test cards for the checkout page:

| Card number            | Result             |
|------------------------|--------------------|
| `4084 0841 1111 1111`  | Success            |
| `4084 0841 1111 1112`  | Insufficient funds |
| `4084 0841 1111 1113`  | Declined           |

---

## Complete integration example (Django)

```python
# views.py
import json
from django.http import JsonResponse, HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from paylode import Paylode
from paylode.exceptions import PaylodeError

client = Paylode(settings.PAYLODE_SECRET_KEY)


@require_POST
def checkout(request):
    body = json.loads(request.body)
    try:
        txn = client.transaction.initialize(
            email=body["email"],
            amount=body["amount"],
            callback_url=request.build_absolute_uri("/confirm/"),
            metadata={"order_id": body["order_id"]},
        )
        return JsonResponse({"authorization_url": txn["data"]["authorization_url"]})
    except PaylodeError as e:
        return JsonResponse({"error": e.message}, status=400)


def confirm(request):
    result = client.transaction.verify(request.GET["reference"])
    if result["data"]["status"] == "success":
        fulfill_order(result["data"]["metadata"]["order_id"])
        return redirect("/order/complete/")
    return redirect("/order/failed/")


@csrf_exempt
@require_POST
def webhook(request):
    if not Paylode.verify_webhook(
        request.body,
        request.META.get("HTTP_X_PAYLODE_SIGNATURE", ""),
        settings.PAYLODE_WEBHOOK_SECRET,
    ):
        return HttpResponse(status=401)

    event = json.loads(request.body)
    if event["event"] == "payment.success":
        fulfill_order(event["data"]["metadata"]["order_id"])
    return HttpResponse(status=200)
```

---

Paylode Services Limited · CBN/PAY/2024/001847 · [docs.paylodeservices.com](https://docs.paylodeservices.com)
