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"])

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

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"])

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

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

Metadata

Release files for bursapay-sdk 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for bursapay-sdk 0.1.1
File Size Uploaded Provenance Actions
bursapay_sdk-0.1.1.tar.gz 26.4 kB Without provenance Details for bursapay_sdk-0.1.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for bursapay-sdk 0.1.1
File Interpreter ABI Platform Actions
bursapay_sdk-0.1.1-py3-none-any.whl Python 3 none any Details for bursapay_sdk-0.1.1-py3-none-any.whl

Total release size: 55.3 kB

Release files / bursapay_sdk-0.1.1.tar.gz

Download URL bursapay_sdk-0.1.1.tar.gz
Size 26.4 kB
Tags Source
SHA-256 checksum
How to use checksums
66e9065dc72bb4ac928128245e7defa515e52de40be8f07c69559ace69ec8c32
BLAKE2b-256 checksum
How to use checksums
2d8c106928400aae78c49a91edb7c62f647ea7db5accc81c29061fcc4e2f5973
Upload date
Uploaded using Trusted Publishing?
What is 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

Release files / bursapay_sdk-0.1.1-py3-none-any.whl

Download URL bursapay_sdk-0.1.1-py3-none-any.whl
Size 29.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
396e677b55a5f04ec5bf3cf3780c7a3c3b43be4e9b2670aee9c8dec1ad056598
BLAKE2b-256 checksum
How to use checksums
a6378a1617b3310af1ae1ac5c31d9d017fb2a021a9ef4b3f3d33ebf8173521b0
Upload date
Uploaded using Trusted Publishing?
What is 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

Security for bursapay-sdk 0.1.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page