Skip to main content

bursapay-sdk · Python

Official Python SDK for the BursaPay Developer Gateway API.

pip install bursapay-sdk

Version: 0.2.0 · Python 3.8–3.12 · Sync & async · PyPI


Quick start

from bursapay import BursaPay

# Production
bp = BursaPay("sk_live_xxxx")

# Sandbox / test
bp = BursaPay("sk_test_xxxx")

# Local dev server
bp = BursaPay("sk_test_xxxx", base_url="http://localhost:8000/api/v1")

Payments

# Initialize — returns an authorization URL to redirect your customer to
payment = bp.payments.initialize(
    amount=5000,          # NGN, not kobo
    email="customer@example.com",
    currency="NGN",       # NGN | USD | GBP | KES
    metadata={"order_id": "ORD-123"},
)
print(payment["authorization_url"])

# Verify after redirect back
result = bp.payments.verify("BP-XXXX")
print(result["status"])   # "success" | "failed" | "pending"

# Retrieve a single payment
payment = bp.payments.retrieve("BP-XXXX")

# List with optional filters
page = bp.payments.list(status="success", page_size=20)

# Charge a saved authorization code (recurring)
bp.payments.charge(
    authorization_code="AUTH_xxx",
    email="customer@example.com",
    amount=5000,
)

# Charge saved card without redirect (card-on-file / one-click)
bp.payments.charge_saved_card(
    customer_reference="cust_abc123",
    authorization_code="AUTH_xxx",
    amount=5000,
)

# Schedule a future charge (must be >60 s in the future)
bp.payments.initialize(
    amount=5000,
    email="customer@example.com",
    charge_at="2025-12-31T09:00:00Z",
)

# Cancel a scheduled payment
bp.payments.cancel_schedule("BP-XXXX")

# Split payment
bp.payments.initialize(
    amount=10000,
    email="buyer@example.com",
    splits=[
        {"subaccount_code": "ACCT_abc123", "share": 0.8},
        {"subaccount_code": "ACCT_def456", "share": 0.2},
    ],
)

# Bulk initialize
bp.payments.bulk_initialize([
    {"amount": 1000, "email": "a@b.com", "reference": "BP-001"},
    {"amount": 2000, "email": "c@d.com", "reference": "BP-002"},
])

Customers

customer = bp.customers.create(email="jane@example.com", name="Jane Doe")
print(customer["customer_reference"])   # "cust_xxxx"

bp.customers.list()
bp.customers.list(q="jane")             # search by email, name, or phone
bp.customers.retrieve("cust_xxxx")
bp.customers.update("cust_xxxx", name="Jane Smith")
bp.customers.payments("cust_xxxx")      # payment history
bp.customers.delete("cust_xxxx")

Transfers

transfer = bp.transfers.initiate(
    amount=10000,
    bank_code="044",          # Access Bank
    account_number="0123456789",
    account_name="John Doe",
    narration="Vendor payout",
)
bp.transfers.retrieve(transfer["reference"])
bp.transfers.list(status="completed")

# Bulk payout
bp.transfers.bulk([
    {"amount": 5000, "bank_code": "058", "account_number": "0987654321", "account_name": "Vendor A"},
    {"amount": 8000, "bank_code": "011", "account_number": "1122334455", "account_name": "Vendor B"},
])

Webhooks

# Register an endpoint
wh = bp.webhooks.create(
    url="https://myapp.com/hooks/bursapay/",
    events=["payment.success", "payment.failed", "transfer.success"],
)
print(wh["secret"])   # store this to verify incoming payloads

bp.webhooks.list()
bp.webhooks.update(wh["id"], is_active=False)
bp.webhooks.delete(wh["id"])

# Inspect delivery logs
bp.webhooks.logs(wh["id"], status="failed")
bp.webhooks.log_detail(log_id=42)
bp.webhooks.retry(log_id=42)

# All valid event types
bp.webhooks.events()

# Send a test event
bp.webhooks.send_test(wh["id"], event="payment.success")

# Dead-letter queue — deliveries that exhausted all retries
dlq = bp.webhooks.dead_letters()
for entry in dlq["results"]:
    if not entry["replayed"]:
        bp.webhooks.replay_dead_letter(entry["id"])

Verifying incoming webhook signatures

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

@csrf_exempt
def bursapay_webhook(request):
    is_valid = BursaPay.verify_webhook_signature(
        request.body,
        request.headers.get("X-BursaPay-Signature", ""),
        "your_webhook_secret",
    )
    if not is_valid:
        return HttpResponse(status=401)

    event = json.loads(request.body)
    if event["event"] == "payment.success":
        ref = event["data"]["reference"]
        # fulfil order...
    return HttpResponse(status=200)

Wallet

bp.wallets.balance()
bp.wallets.ledger()                          # all entry types
bp.wallets.ledger(entry_type="credit")       # filter: credit | debit | fee | settlement | withdrawal | refund
bp.wallets.settlements()
bp.wallets.settlement("STL-xxxx")

Virtual Accounts

# Default bank
va = bp.virtual_accounts.create("cust_xxxx")

# Choose a specific bank: wema-bank | access-bank | titan-paystack | sterling-bank
va = bp.virtual_accounts.create("cust_xxxx", preferred_bank="wema-bank")
print(va["account_number"], va["bank_name"])

bp.virtual_accounts.list()
bp.virtual_accounts.retrieve(va["id"])

Subscriptions

plan = bp.subscriptions.create_plan(
    name="Pro Monthly",
    amount=5000,
    interval="monthly",
)

sub = bp.subscriptions.enroll(
    customer_reference="cust_xxxx",
    plan_id=plan["id"],
    authorization_code="AUTH_xxx",
)

bp.subscriptions.pause(sub["id"])
bp.subscriptions.resume(sub["id"])
bp.subscriptions.cancel(sub["id"])

Invoices

inv = bp.invoices.create(
    customer_reference="cust_xxxx",
    line_items=[
        {"description": "Web design", "quantity": 1, "unit_price": 150000},
        {"description": "Hosting (annual)", "quantity": 1, "unit_price": 30000},
    ],
    due_date="2025-12-31",
)

bp.invoices.update(inv["reference"], status="sent")   # triggers payment link creation
bp.invoices.list(status="overdue")
bp.invoices.delete(inv["reference"])

Payment Links

link = bp.payment_links.create(
    title="Pay for Invoice #42",
    amount=180000,
    expires_at="2025-12-31T23:59:59Z",
)
print(link["url"])

bp.payment_links.analytics(link["link_code"])

Refunds

refund = bp.refunds.create("BP-XXXX", amount=2500, reason="Customer request")
bp.refunds.retrieve(refund["refund_reference"])

Disputes

# List all disputes
disputes = bp.disputes.list()
disputes = bp.disputes.list(per_page=10, cursor="...")

# Get a single dispute
dispute = bp.disputes.retrieve("DIS-XXXX")

# Submit evidence / update status
bp.disputes.update_evidence(
    "DIS-XXXX",
    evidence={
        "delivery_proof": "https://cdn.example.com/proof.pdf",
        "notes": "Item delivered on 2025-01-15",
    },
    status="under_review",
)
# NOTE: "won" and "lost" statuses are set by Paystack webhook events only —
#       the API will reject attempts to set them directly.

Reconciliation

# Requires a live secret key (sk_live_*)
# Rate-limited to 10 requests per hour per developer
result = bp.reconciliation.run("2025-07-31")
print(result["matched"])        # count of matching records
print(result["discrepancies"])  # list of mismatches to investigate

Async support

import asyncio
from bursapay import BursaPay

async def main():
    async with BursaPay("sk_test_xxxx").async_client() as bp:
        payment = await bp.payments.initialize(amount=5000, email="a@b.com")
        print(payment["authorization_url"])

asyncio.run(main())

Error handling

from bursapay import BursaPay
from bursapay.exceptions import (
    AuthenticationError,
    NotFoundError,
    ValidationError,
    RateLimitError,
    ServerError,
    BursaPayError,
)

try:
    bp.payments.initialize(amount=5000, email="x@y.com", currency="XYZ")
except ValidationError as e:
    print(e.error_code)       # "invalid_currency"
    print(e.field_errors)     # field-level errors dict
    print(e.status_code)      # 400
except AuthenticationError:
    print("Check your API key")
except NotFoundError as e:
    print(f"Not found: {e.message}")
except RateLimitError:
    print("Rate limited — back off and retry")
except ServerError as e:
    print(f"BursaPay server error: {e.status_code}")
except BursaPayError as e:
    print(f"Unexpected error: {e}")

Context manager

with BursaPay("sk_test_xxxx") as bp:
    result = bp.payments.verify("BP-XXXX")
# connection pool is closed automatically

Environment variables (recommended)

import os
from bursapay import BursaPay

bp = BursaPay(
    os.environ["BURSAPAY_SECRET_KEY"],
    base_url=os.environ.get("BURSAPAY_BASE_URL"),   # omit for production default
)


License

MIT

from bursapay import BursaPay

# Production
bp = BursaPay("sk_live_xxxx")

# Sandbox / test
bp = BursaPay("sk_test_xxxx")

# Local dev server
bp = BursaPay("sk_test_xxxx", base_url="http://localhost:8000/api/v1")

Payments

# Initialize — returns an authorization URL to redirect your customer to
payment = bp.payments.initialize(
    amount=5000,          # NGN, not kobo
    email="customer@example.com",
    currency="NGN",       # NGN | USD | GBP | KES
    metadata={"order_id": "ORD-123"},
)
print(payment["authorization_url"])

# Verify after Paystack redirects back
result = bp.payments.verify("BP-XXXX")
print(result["status"])   # "success" | "failed" | "pending"

# Retrieve a payment
payment = bp.payments.retrieve("BP-XXXX")

# List with optional filters
page = bp.payments.list(status="success", page_size=20)

# Charge a saved authorization code (recurring)
bp.payments.charge(
    authorization_code="AUTH_xxx",
    email="customer@example.com",
    amount=5000,
)

# Schedule a future charge (must be >60 s in the future)
bp.payments.initialize(
    amount=5000,
    email="customer@example.com",
    charge_at="2025-12-31T09:00:00Z",
)

# Cancel a scheduled payment
bp.payments.cancel_schedule("BP-XXXX")

# Split payment
bp.payments.initialize(
    amount=10000,
    email="buyer@example.com",
    splits=[
        {"subaccount_code": "ACCT_abc123", "share": 0.8},
        {"subaccount_code": "ACCT_def456", "share": 0.2},
    ],
)

# Bulk initialize
bp.payments.bulk_initialize([
    {"amount": 1000, "email": "a@b.com", "reference": "BP-001"},
    {"amount": 2000, "email": "c@d.com", "reference": "BP-002"},
])

Customers

customer = bp.customers.create(email="jane@example.com", name="Jane Doe")
print(customer["customer_reference"])   # "CUS-xxxx"

bp.customers.list()
bp.customers.retrieve("CUS-xxxx")
bp.customers.update("CUS-xxxx", name="Jane Smith")
bp.customers.payments("CUS-xxxx")
bp.customers.delete("CUS-xxxx")

Transfers

transfer = bp.transfers.initiate(
    amount=10000,
    bank_code="044",          # Access Bank
    account_number="0123456789",
    account_name="John Doe",
    narration="Vendor payout",
)
bp.transfers.retrieve(transfer["reference"])
bp.transfers.list(status="completed")

# Bulk payout
bp.transfers.bulk([
    {"amount": 5000, "bank_code": "058", "account_number": "0987654321", "account_name": "Vendor A"},
    {"amount": 8000, "bank_code": "011", "account_number": "1122334455", "account_name": "Vendor B"},
])

Webhooks

# Register an endpoint
wh = bp.webhooks.create(
    url="https://myapp.com/hooks/bursapay/",
    events=["payment.success", "payment.failed", "transfer.success"],
)
print(wh["secret"])   # store this to verify incoming payloads

bp.webhooks.list()
bp.webhooks.update(wh["id"], is_active=False)
bp.webhooks.delete(wh["id"])

# Inspect delivery logs
bp.webhooks.logs(wh["id"], status="failed")
bp.webhooks.retry(log_id=42)

# All valid event types
bp.webhooks.events()

Verifying incoming webhook signatures

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

@csrf_exempt
def bursapay_webhook(request):
    is_valid = BursaPay.verify_webhook_signature(
        request.body,
        request.headers.get("X-BursaPay-Signature", ""),
        "your_webhook_secret",
    )
    if not is_valid:
        return HttpResponse(status=401)

    event = json.loads(request.body)
    if event["event"] == "payment.success":
        ref = event["data"]["reference"]
        # fulfil order...
    return HttpResponse(status=200)

Wallet

bp.wallets.balance()
bp.wallets.ledger()
bp.wallets.settlements()
bp.wallets.settlement("STL-xxxx")

Virtual Accounts

va = bp.virtual_accounts.create("CUS-xxxx")
print(va["account_number"], va["bank_name"])

bp.virtual_accounts.list()
bp.virtual_accounts.retrieve(va["id"])

Subscriptions

plan = bp.subscriptions.create_plan(
    name="Pro Monthly",
    amount=5000,
    interval="monthly",
)

sub = bp.subscriptions.enroll(
    customer_reference="CUS-xxxx",
    plan_id=plan["id"],
    authorization_code="AUTH_xxx",
)

bp.subscriptions.pause(sub["id"])
bp.subscriptions.resume(sub["id"])
bp.subscriptions.cancel(sub["id"])

Invoices

inv = bp.invoices.create(
    customer_reference="CUS-xxxx",
    line_items=[
        {"description": "Web design", "quantity": 1, "unit_price": 150000},
        {"description": "Hosting (annual)", "quantity": 1, "unit_price": 30000},
    ],
    due_date="2025-12-31",
)

bp.invoices.update(inv["reference"], status="sent")   # triggers payment link creation
bp.invoices.list(status="overdue")
bp.invoices.delete(inv["reference"])

Payment Links

link = bp.payment_links.create(
    title="Pay for Invoice #42",
    amount=180000,
    expires_at="2025-12-31T23:59:59Z",
)
print(link["url"])

bp.payment_links.analytics(link["link_code"])

Refunds

refund = bp.refunds.create("BP-XXXX", amount=2500, reason="Customer request")
bp.refunds.retrieve(refund["refund_reference"])

Async support

import asyncio
from bursapay import BursaPay

async def main():
    async with BursaPay("sk_test_xxxx").async_client() as bp:
        payment = await bp.payments.initialize(amount=5000, email="a@b.com")
        print(payment["authorization_url"])

asyncio.run(main())

Error handling

from bursapay import BursaPay
from bursapay.exceptions import (
    AuthenticationError,
    NotFoundError,
    ValidationError,
    RateLimitError,
    ServerError,
    BursaPayError,
)

try:
    bp.payments.initialize(amount=5000, email="x@y.com", currency="XYZ")
except ValidationError as e:
    print(e.error_code)       # "invalid_currency"
    print(e.field_errors)     # field-level errors dict
except AuthenticationError:
    print("Check your API key")
except RateLimitError:
    print("Slow down — rate limited")
except ServerError as e:
    print(f"BursaPay server error: {e.status_code}")
except BursaPayError as e:
    print(f"Unexpected error: {e}")

Context manager

with BursaPay("sk_test_xxxx") as bp:
    result = bp.payments.verify("BP-XXXX")
# connection pool is closed automatically

Environment variables (recommended)

import os
from bursapay import BursaPay

bp = BursaPay(
    os.environ["BURSAPAY_SECRET_KEY"],
    base_url=os.environ.get("BURSAPAY_BASE_URL"),   # omit for production default
)

License

MIT

Download files

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

Source Distribution

bursapay_sdk-0.1.1.tar.gz (26.4 kB view details)

Uploaded Source

Built Distribution

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

bursapay_sdk-0.1.1-py3-none-any.whl (29.0 kB view details)

Uploaded Python 3

File details

Details for the file bursapay_sdk-0.1.1.tar.gz.

File metadata

  • Download URL: bursapay_sdk-0.1.1.tar.gz
  • Upload date:
  • Size: 26.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: Hatch/1.17.1 {"ci":null,"cpu":"AMD64","implementation":{"name":"CPython","version":"3.12.4"},"installer":{"name":"hatch","version":"1.17.1"},"openssl_version":"OpenSSL 3.0.13 30 Jan 2024","python":"3.12.4","system":{"name":"Windows","release":"11"}} HTTPX2/2.9.1

File hashes

Hashes for bursapay_sdk-0.1.1.tar.gz
Algorithm Hash digest
SHA256 66e9065dc72bb4ac928128245e7defa515e52de40be8f07c69559ace69ec8c32
MD5 8d87f90bdf42ac8a6d970a7ae77ed8f8
BLAKE2b-256 2d8c106928400aae78c49a91edb7c62f647ea7db5accc81c29061fcc4e2f5973

See more details on using hashes here.

File details

Details for the file bursapay_sdk-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: bursapay_sdk-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 29.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: Hatch/1.17.1 {"ci":null,"cpu":"AMD64","implementation":{"name":"CPython","version":"3.12.4"},"installer":{"name":"hatch","version":"1.17.1"},"openssl_version":"OpenSSL 3.0.13 30 Jan 2024","python":"3.12.4","system":{"name":"Windows","release":"11"}} HTTPX2/2.9.1

File hashes

Hashes for bursapay_sdk-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 396e677b55a5f04ec5bf3cf3780c7a3c3b43be4e9b2670aee9c8dec1ad056598
MD5 a82e299e12f3c42bb7ab510e91e2dfd8
BLAKE2b-256 a6378a1617b3310af1ae1ac5c31d9d017fb2a021a9ef4b3f3d33ebf8173521b0

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 Sentry Error logging StatusPage Status page