Skip to main content

Official Python SDK for Paylode Services Limited — CBN Licensed PSSP

Project description

paylode-python

Official Python SDK for Paylode Services Limited — CBN Licensed Payment Solution Service Provider (PSSP).

PyPI version Python versions License: MIT

Requirements

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

Installation

pip install paylode-python

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

Quick start

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)
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
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.

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

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

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

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

# 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

# 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.

# 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"},
)
client.subaccount.fetch("SUB_abc123")
client.subaccount.list(page=1, per_page=50)
client.subaccount.update("SUB_abc123", percentage_charge=75)

client.settlement

# 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.

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)
# 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

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

KYC tier limits

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

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

# 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)

# 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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

paylode_python-1.0.0.tar.gz (17.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

paylode_python-1.0.0-py3-none-any.whl (14.5 kB view details)

Uploaded Python 3

File details

Details for the file paylode_python-1.0.0.tar.gz.

File metadata

  • Download URL: paylode_python-1.0.0.tar.gz
  • Upload date:
  • Size: 17.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for paylode_python-1.0.0.tar.gz
Algorithm Hash digest
SHA256 334f1bd9221d9ca79274ff787fd752f9ad119b5b3d10af7906ccbf4526518768
MD5 c426f66e27dbfeb9d84573896b054349
BLAKE2b-256 442353303950475778a17c44e0539ae92b9029ba861202491983307fea21f0f8

See more details on using hashes here.

File details

Details for the file paylode_python-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: paylode_python-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 14.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for paylode_python-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 14716343567300ceb312ca78800cb55c334ea329bb87615ee76f2f1318510275
MD5 be7e63e3fcc7148a36c94090d0734f81
BLAKE2b-256 d75e3c69332f0195184ac393fb89cea0e8e68ab6aea70e66713170a223967190

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page