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.

Created by Fidel Chukwunyere PyPI version License: MIT

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

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)

👤 Author


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.7.tar.gz (12.4 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.7-py3-none-any.whl (9.8 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: smartpaystack-0.1.7.tar.gz
  • Upload date:
  • Size: 12.4 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.7.tar.gz
Algorithm Hash digest
SHA256 de142c4b3b2233f756d5b92b21d8c5b64135593a5b87dac32e09401d1b10d646
MD5 cd3100cc1314926114146d7a9970ba90
BLAKE2b-256 012129dc597697fc69f0b969156b6ae4d9c142ee5fe2e5e0f85942879c964101

See more details on using hashes here.

File details

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

File metadata

  • Download URL: smartpaystack-0.1.7-py3-none-any.whl
  • Upload date:
  • Size: 9.8 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.7-py3-none-any.whl
Algorithm Hash digest
SHA256 146ed39f346cd2f62675a3d5b63beb646ab1ecb93db167ad87f96af19de350cb
MD5 bc0ce9fb939d6a17e586c88f6922638e
BLAKE2b-256 4959c76a54661462115728b095662f5c9d1f3353980317649a396787c0cc539d

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