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
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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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.