A Typed Python client for the Pesapal API with sync/async support, order submission, IPN management, and optional CLI tools.
Project description
pesapal-client
A Typed Python client for the Pesapal API with sync/async support, order submission, IPN management, and optional CLI tools.
Features
- Typed models for requests and responses using Pydantic
- Sync and async API support
- Order submission and payment status tracking
- IPN (Instant Payment Notification) management
- Subscription management
- Automatic authentication and token refresh
- Custom exceptions for error handling
- Utility functions for JWT and JSON parsing
- Optional CLI tools for quick integration
Installation
pip install pesapal-client
Quick Start
from pesapal_client.client import PesapalClientV3
client = PesapalClientV3(
consumer_key="your_consumer_key",
consumer_secret="your_consumer_secret",
is_sandbox=True,
)
Register IPN(Instant Payment Notification)
The IPN is the url to which pesapal will notify you incase there are any status updates on a recently created payment order. The request method should set via the ipn_nofication_type which can either be a GET or POST
Scenario: Registering an IPN for payment callbacks
from pesapal_client.v3.schemas import IPNRegistrationRequest
ipn_request = IPNRegistrationRequest(
url="https://yourdomain.com/pesapal/ipn",
ipn_notification_type="POST"
)
response = client.ipn.register_ipn(ipn_request)
print(response.ipn_id, response.ipn_status_description)
Get Registered IPNs
Pesapal allows registration of multiple IPNs and if incase you need to list them. Here is how you do it.
Scenario: Retrieving all registered IPNs
registered_ipns = client.ipn.get_registered_ipns()
for ipn in registered_ipns:
print(ipn.url, ipn.created_date)
Initiate a Payment / Submit an Order
Scenario: Customer checks out with a single payment
from uuid import UUID
from pesapal_client.v3.schemas import InitiatePaymentOrderRequest, BillingAddress
payment_request = InitiatePaymentOrderRequest(
id="ORDER-001",
currency="UGX", # ISO 4217 code
amount=50000.0,
description="Order #001 - Online Purchase",
callback_url="https://yourdomain.com/payment/callback",
notification_id=UUID("YOUR_REGISTERED_IPN_ID"),
billing_address=BillingAddress(
email_address="customer@example.com",
first_name="John",
last_name="Doe",
phone_number="+256700000000",
country_code="UG",
line_1="123 Kampala Road"
)
)
response = client.one_time_payment.initiate_payment_order(payment_request)
print("Redirect user to:", response.redirect_url)
Checking Payment Status
Scenario: Checking payment status after customer completes checkout
from pesapal_client.v3.shcemas import PaymentOrderStatusCode
status = client.one_time_payment.get_payment_order_status(
payment_order_tracking_id="TRACKING_ID_FROM_INITIATE"
)
if status.status_code == PaymentOrderStatusCode.COMPLETED:
print("✅ Payment successful. Fulfill the order.")
elif status.status_code == PaymentOrderStatusCode.FAILED:
print("❌ Payment failed. Ask customer to retry.")
elif status.status_code == PaymentOrderStatusCode.REVERSED:
print("Payment has been refunded")
else:
print("ℹ️ Payment is still processing...")
Subscriptions
Subscriptions allow recurring payments
from pesapal_client.v3.schemas import InitiateSubscriptionRequest, SubscriptionDetails
subscription_request = InitiateSubscriptionRequest(
id="SUB-001",
currency="USD",
amount=10.0,
description="Monthly Membership",
callback_url="https://yourdomain.com/subscription/callback",
notification_id=UUID("YOUR_REGISTERED_IPN_ID"),
billing_address=BillingAddress(
email_address="subscriber@example.com",
first_name="Alice",
last_name="Smith",
phone_number="+14155552671",
country_code="US",
),
account_number="ACC12345",
subscription_details=SubscriptionDetails(
start_date="01-01-2025",
end_date="01-01-2026",
frequency="MONTHLY"
)
)
response = client.subscription.initiate_subscription(subscription_request)
print("Redirect user to:", response.redirect_url)
If the subscription details(they are optional) are left out, then the customer shall be asked to choose the periods when redirected to the payment form page(redirect url)
Refunds
Customer requests for a refund
from pesapal_client.v3.schemas import RefundRequest
refund_request = RefundRequest(
confirmation_code="CONFIRMATION_CODE_FROM_SUCCESSFUL_TXN",
amount="1000.00",
username="customer_identity i.e name or email or phone number",
remarks="Customer requested refund"
)
response = client.one_time_payment.initiate_refund(refund_request)
if response.status == 200:
print("✅ Refund initiated:", response.message)
else:
print("❌ Refund failed:", response.message)
Exception Handling
All API calls may raise a PesapalException if Pesapal returns an error (even if the HTTP status code is 200 OK but the body indicates a failure).
You should wrap your calls in a try/except block to handle these gracefully.
Remember to also watch for RequestError exceptions for non Pesapal errors i.e Connection failure, Read Timeout
Example: Handling Errors During Payment Initiation
from pesapal_client.exceptions import PesapalException, RequestError
from pesapal_client.v3.schemas import InitiatePaymentOrderRequest, BillingAddress
from uuid import UUID
try:
payment_request = InitiatePaymentOrderRequest(
id="ORDER-ERR-001",
currency="USD",
amount=50.0,
description="Test Order with Error",
callback_url="https://yourdomain.com/payment/callback",
notification_id=UUID("YOUR_REGISTERED_IPN_ID"),
billing_address=BillingAddress(
email_address="invalid-email", # <-- This will trigger validation error
first_name="Jane",
last_name="Doe"
)
)
response = client.one_time_payment.initiate_payment_order(payment_request)
print("Redirect user to:", response.redirect_url)
except RequestError as e:
# Failed to communicate to Pesapal API
print("Pesapal API Error: ", str(e))
except PesapalException as e:
# Pesapal API-specific error
print("❌ Pesapal API Error:", str(e))
except Exception as e:
# Any other unexpected error
print("⚠️ Unexpected Error:", str(e))
License
This project is licensed under the MIT License - see the LICENSE file for details.
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pesapal_client-0.1.5.tar.gz.
File metadata
- Download URL: pesapal_client-0.1.5.tar.gz
- Upload date:
- Size: 11.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.7.1 CPython/3.11.13 Linux/6.11.0-1018-azure
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
53159699c48af91416a018791685b21f4de4315d034f764b38503a8d0c1f37fa
|
|
| MD5 |
fb694eea4ac39e8e71ec5e0f0fee4e83
|
|
| BLAKE2b-256 |
57d622a8710dbb1c7169d6e8a1ffa054fa75caec26d1dc077cc116b51e5c3089
|
File details
Details for the file pesapal_client-0.1.5-py3-none-any.whl.
File metadata
- Download URL: pesapal_client-0.1.5-py3-none-any.whl
- Upload date:
- Size: 9.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.7.1 CPython/3.11.13 Linux/6.11.0-1018-azure
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fea256a70f6e347463fde0ca98fea6fc21f6f815ac352aea43748cf43760691e
|
|
| MD5 |
1efc9fe3f94eacc4f01f6bf5e7946c2e
|
|
| BLAKE2b-256 |
524dbc4eafa69586540ccdaad1172701bb3febeaf79c8ba2e2bcbeb4a322fa77
|