Skip to main content

A smart, strategy-based, multi-currency Paystack SDK for Python.

Project description

# SmartPaystack 

A smart, framework-agnostic, strategy-based Paystack integration for Python. 

Stop writing manual math logic to calculate Paystack fees. Just declare your strategy (`ABSORB`, `PASS`, `SPLIT`) and let the package do the rest. Works seamlessly with **FastAPI, Flask, Django, Tornado, or plain Python scripts.**

## ✨ Features
* **Zero-Math API:** No more Kobo/Cents conversions. Pass native amounts (e.g., `5000` NGN).
* **Smart Fee Strategies:** Easily absorb fees, pass them to the customer, or split them.
* **Multi-Currency Routing:** Automatically applies the correct fee caps and percentages for NGN, GHS, ZAR, KES, and USD.
* **Framework Agnostic Webhooks:** Built-in HMAC SHA512 verifier that works with any web framework.
* **Fully Typed:** Sweet IDE auto-completion.

---

## 📦 Installation

```bash
pip install smartpaystack

🚀 Quickstart

1. Initialization

You can either pass your secret key directly or set it as an environment variable (PAYSTACK_SECRET_KEY).

import os
from smartpaystack import SmartPaystack

# Option A: Uses the PAYSTACK_SECRET_KEY environment variable
os.environ["PAYSTACK_SECRET_KEY"] = "sk_live_xxxxxx"
client = SmartPaystack()

# Option B: Pass it explicitly
client = SmartPaystack(secret_key="sk_live_xxxxxx")

2. Collecting Money (Charges)

Stop worrying about fee math. Tell the client how much you want, and who pays the fee.

from smartpaystack import ChargeStrategy, Currency

# Scenario A: You want exactly ₦50,000. Customer pays the Paystack fee.
response = client.create_charge(
    email="customer@email.com", 
    amount=50000, 
    currency=Currency.NGN,
    charge_strategy=ChargeStrategy.PASS 
)
print(response["authorization_url"])

# Scenario B: You absorb the fee for a Ghana Cedi transaction.
response = client.create_charge(
    email="ghana@email.com",
    amount=1000,
    currency=Currency.GHS,
    charge_strategy=ChargeStrategy.ABSORB
)

# Scenario C: You split the Paystack fee 50/50 with the customer.
# If the fee is ₦150, the customer is charged ₦10,075 and you receive ₦9,925.
response = client.create_charge(
    email="split@email.com",
    amount=10000,
    currency=Currency.NGN,
    charge_strategy=ChargeStrategy.SPLIT,
    split_ratio=0.5  # The percentage of the fee the customer pays (0.5 = 50%)
)
print(response["authorization_url"])

3. Passing Custom Metadata

You can easily attach your own custom data (like order IDs or user IDs) to a charge. The package will safely merge your custom dictionary with its own internal fee calculations so you can access both later in your webhook!

response = client.create_charge(
    email="buyer@email.com",
    amount=15000,
    charge_strategy=ChargeStrategy.PASS,
    metadata={
        "custom_order_id": "ORD-88291",
        "cart_items": 3
    }
)

When this transaction succeeds, your webhook will receive your custom fields alongside smartpaystack_strategy, merchant_expected, and customer_amount.

4. Sending Money (Transfers)

Sending money is a two-step process: create a recipient, then initiate the transfer.

# 1. Resolve the account (Optional but recommended)
account = client.resolve_account_number(account_number="0123456789", bank_code="033")
print(f"Resolved Name: {account['account_name']}")

# 2. Create the recipient (You can pass metadata here, too!)
recipient = client.create_transfer_recipient(
    name=account["account_name"],
    account_number="0123456789",
    bank_code="033",
    metadata={"internal_worker_id": "W-990"}
)
recipient_code = recipient["recipient_code"]

# 3. Send the money (e.g., Send ₦10,500)
transfer = client.initiate_transfer(
    amount=10500,
    recipient_code=recipient_code,
    reason="Monthly Payout"
)
print(f"Transfer Status: {transfer['status']}")

5. Recurring Subscriptions 📅

Easily manage billing cycles. Create a plan once, then subscribe your customers to it.

from smartpaystack import Interval

# 1. Create a Plan (e.g., A Pro tier that costs ₦10,000 / month)
plan = client.create_plan(
    name="Pro Tier Monthly",
    amount=10000,
    interval=Interval.MONTHLY
)
plan_code = plan["plan_code"]

# 2. Subscribe a customer to the plan
# Note: If the customer already has an active authorization (saved card), 
# you can pass the authorization_code to charge them immediately.
subscription = client.create_subscription(
    customer_email="user@email.com",
    plan_code=plan_code
)
print(f"Subscription active: {subscription['status']}")

# 3. Disable a subscription (Requires the sub code and email token from Paystack)
client.disable_subscription(
    subscription_code="SUB_vsy1egv220",
    email_token="e7x1bejv"
)

🛡️ Error Handling

When building fintech applications, you must handle failures gracefully. smartpaystack provides specific exceptions so you can catch exactly what went wrong.

from smartpaystack import SmartPaystack
from smartpaystack.exceptions import PaystackAPIError, PaystackError

client = SmartPaystack()

try:
    account = client.resolve_account_number("invalid_number", "033")
except PaystackAPIError as e:
    # Raised when Paystack returns a 400/500 response, or network fails
    print(f"Paystack API failed: {str(e)}")
    # Example Output: Paystack API failed: Could not resolve account name.
except PaystackError as e:
    # A generic fallback for any other package-related error
    print(f"An unexpected error occurred: {str(e)}")

Available Exceptions (from smartpaystack.exceptions):

  • PaystackError: The base class for all package exceptions.
  • PaystackAPIError: Raised when the HTTP request to Paystack fails or returns an error message.
  • WebhookVerificationError: Raised when the HMAC signature on an incoming webhook is invalid or missing.

📡 Verifying Webhooks

Paystack sends webhooks to your server when events happen (like a successful charge or transfer). smartpaystack provides a generic WebhookVerifier that works with any framework.

Example: FastAPI

from fastapi import FastAPI, Request, Header, HTTPException
from smartpaystack import WebhookVerifier
from smartpaystack.exceptions import WebhookVerificationError

app = FastAPI()
verifier = WebhookVerifier(secret_key="sk_live_xxxxxx")

@app.post("/paystack/webhook")
async def paystack_webhook(request: Request, x_paystack_signature: str = Header(None)):
    raw_body = await request.body()
    
    try:
        # Verifies the HMAC SHA512 signature and parses the JSON
        event_data = verifier.verify_and_parse(raw_body, x_paystack_signature)
    except WebhookVerificationError as e:
        raise HTTPException(status_code=400, detail=str(e))

    # Handle the event
    if event_data["event"] == "charge.success":
        data = event_data["data"]
        # Retrieve the math breakdown and your custom metadata!
        merchant_keeps = data["metadata"]["merchant_expected"]
        order_id = data["metadata"].get("custom_order_id")
        
        print(f"Payment successful for Order {order_id}! Expected payout: {merchant_keeps}")
        
    return {"status": "success"}

Example: Flask

from flask import Flask, request, jsonify
from smartpaystack import WebhookVerifier
from smartpaystack.exceptions import WebhookVerificationError

app = Flask(__name__)
verifier = WebhookVerifier(secret_key="sk_live_xxxxxx")

@app.route("/paystack/webhook", methods=["POST"])
def paystack_webhook():
    signature = request.headers.get("x-paystack-signature")
    raw_body = request.get_data()
    
    try:
        event_data = verifier.verify_and_parse(raw_body, signature)
    except WebhookVerificationError as e:
        return jsonify({"error": str(e)}), 400

    if event_data["event"] == "transfer.success":
        print("Transfer successful!")
        
    return jsonify({"status": "success"}), 200

Example: Django

In your views.py, use @csrf_exempt since Paystack (an external service) cannot send a CSRF token.

from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from django.conf import settings
from smartpaystack import WebhookVerifier
from smartpaystack.exceptions import WebhookVerificationError

# Initialize the verifier (ideally load this from environment or settings)
verifier = WebhookVerifier(secret_key=getattr(settings, "PAYSTACK_SECRET_KEY", "sk_live_xxxxxx"))

@csrf_exempt
@require_POST
def paystack_webhook(request):
    signature = request.headers.get("x-paystack-signature", "")
    raw_body = request.body # Django provides the raw bytes here
    
    try:
        event_data = verifier.verify_and_parse(raw_body, signature)
    except WebhookVerificationError as e:
        return JsonResponse({"error": str(e)}, status=400)

    # Handle the event
    if event_data["event"] == "charge.success":
        print(f"Payment successful for amount: {event_data['data']['amount']}")
        
    return JsonResponse({"status": "success"}, status=200)

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

smartpaystack-0.1.6.tar.gz (12.1 kB view details)

Uploaded Source

Built Distribution

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

smartpaystack-0.1.6-py3-none-any.whl (9.7 kB view details)

Uploaded Python 3

File details

Details for the file smartpaystack-0.1.6.tar.gz.

File metadata

  • Download URL: smartpaystack-0.1.6.tar.gz
  • Upload date:
  • Size: 12.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.0

File hashes

Hashes for smartpaystack-0.1.6.tar.gz
Algorithm Hash digest
SHA256 e63dd6952fcdc7f57f85c477f93e6f01caab84b7f62c6fafa6fd0cf9a7efd3d1
MD5 187ae76e0d1dae4d6da6393618a5427a
BLAKE2b-256 dc2695932ffe4f64bb78a151cdb929847830077c03511bb61742a07021b00348

See more details on using hashes here.

File details

Details for the file smartpaystack-0.1.6-py3-none-any.whl.

File metadata

  • Download URL: smartpaystack-0.1.6-py3-none-any.whl
  • Upload date:
  • Size: 9.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.0

File hashes

Hashes for smartpaystack-0.1.6-py3-none-any.whl
Algorithm Hash digest
SHA256 831618681021419be4f812024f64ddfc14f9868f1faa77d4c24d79d0e6d117b3
MD5 841a3dba79e977e8d88c900fafd373d0
BLAKE2b-256 5131bb62129a06079cc34b05ffb2bb037ed7623f6f594e69e53c3a4e4e8bc363

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