Official Python Client Library for WeBirr Payment Gateway APIs
Project description
Official Python Client Library for WeBirr Payment Gateway APIs
This Client Library provides convenient access to WeBirr Payment Gateway APIs from Python Applications.
Install
$ pip install webirr
For local development from this repository:
$ pip install -e .
Usage
The library needs to be configured with a merchant Id & API key. You can get it by contacting webirr.com
You can use this library for production or test environments. you will need to set is_test_env=True for test, and False for production apps when creating objects of class WeBirrClient
Examples assume the WeBirr TestEnv and read credentials from environment variables:
export WEBIRR_TEST_ENV_MERCHANT_ID="YOUR_TEST_MERCHANT_ID"
export WEBIRR_TEST_ENV_API_KEY="YOUR_TEST_API_KEY"
Create the client with merchant ID, API key, and environment once. The client automatically sets Bill.merchant_id before sending bill create/update requests, so application code and examples should not set merchant_id on the bill object.
Example
The examples below create the client, call the API, check error, handle the success branch, and print error_code on failure.
Creating a new Bill / Updating an existing Bill on WeBirr Servers
import os
from datetime import datetime
from webirr import Bill, WeBirrClient
def main():
api_key = os.getenv("WEBIRR_TEST_ENV_API_KEY", "")
merchant_id = os.getenv("WEBIRR_TEST_ENV_MERCHANT_ID", "")
# api_key = "YOUR_API_KEY"
# merchant_id = "YOUR_MERCHANT_ID"
api = WeBirrClient(merchant_id, api_key, True)
bill = Bill()
bill.amount = "270.90"
bill.customer_code = "cc01" # it can be email address or phone number if you dont have customer code
bill.customer_name = "Elias Haileselassie"
bill.customer_phone = "0911000000" # optional; used for SMS notification when enabled for the merchant
bill.time = "2021-07-22 22:14" # your bill time, always in this format
bill.description = "hotel booking"
bill.bill_reference = "python/example/" + datetime.now().strftime("%Y%m%d%H%M%S") # your unique reference number
print("\nCreating Bill...")
res = api.create_bill(bill)
if not res.error:
# success
payment_code = res.res # returns paymentcode such as 429 723 975
print(f"\nPayment Code = {payment_code}") # we may want to save payment code in local db.
else:
# fail
print(f"\nerror: {res.error}")
print(f"\nerrorCode: {res.error_code}") # can be used to handle specific business error such as ERROR_INVALID_INPUT_DUP_REF
# Update existing bill if it is not paid
bill.amount = "278.00"
bill.customer_name = "Elias python"
# bill.bill_reference = "WE CAN NOT CHANGE THIS"
print("\nUpdating Bill...")
res = api.update_bill(bill)
if not res.error:
# success
print("\nbill is updated succesfully") # res.res will be 'OK' no need to check here!
else:
# fail
print(f"\nerror: {res.error}")
print(f"\nerrorCode: {res.error_code}") # can be used to handle specific business error such as ERROR_INVALID_INPUT
main()
Getting a Bill and Listing Bills
import os
from webirr import WeBirrClient
def main():
api_key = os.getenv("WEBIRR_TEST_ENV_API_KEY", "")
merchant_id = os.getenv("WEBIRR_TEST_ENV_MERCHANT_ID", "")
api = WeBirrClient(merchant_id, api_key, True)
bill_reference = "YOUR_BILL_REFERENCE" # BILL_REFERENCE_YOU_SAVED_AFTER_CREATING_A_NEW_BILL
payment_code = "YOUR_PAYMENT_CODE" # PAYMENT_CODE_YOU_SAVED_AFTER_CREATING_A_NEW_BILL
print("\nGetting bill by reference...")
res = api.get_bill_by_reference(bill_reference)
if not res.error:
# success
print("\nBill found by reference.")
print(f"\nBill Reference: {res.res.bill_reference}")
print(f"\nPayment Code: {res.res.wbc_code}")
print(f"\nAmount: {res.res.amount}")
print(f"\nPayment Status: {res.res.payment_status}")
print(f"\nUpdate Timestamp: {res.res.update_time_stamp}")
else:
# fail
print(f"\nError: {res.error}")
print(f"\nError Code: {res.error_code}")
print("\nGetting bill by payment code...")
res = api.get_bill_by_payment_code(payment_code)
if not res.error:
# success
print("\nBill found by payment code.")
print(f"\nBill Reference: {res.res.bill_reference}")
print(f"\nPayment Code: {res.res.wbc_code}")
else:
# fail
print(f"\nError: {res.error}")
print(f"\nError Code: {res.error_code}")
print("\nListing bills...")
payment_status = -1 # -1 all, 0 pending, 1 unconfirmed payment, 2 paid.
last_time_stamp = "20251231" # Date-only cursor; use "20251231235959" when you need time precision.
limit = 10
res = api.get_bills(payment_status, last_time_stamp, limit)
if not res.error:
# success
print(f"\nBills returned: {len(res.res)}")
for bill in res.res:
print("\n-----------------------------")
print(f"\nBill Reference: {bill.bill_reference}")
print(f"\nPayment Code: {bill.wbc_code}")
print(f"\nAmount: {bill.amount}")
print(f"\nPayment Status: {bill.payment_status}")
print(f"\nUpdate Timestamp: {bill.update_time_stamp}")
else:
# fail
print(f"\nError: {res.error}")
print(f"\nError Code: {res.error_code}")
main()
Timestamp cursors can be date-only (yyyyMMdd) or include time (yyyyMMddHHmmss). Use empty string only when you intentionally want all history from the beginning.
Getting Payment status of an existing Bill from WeBirr Servers
import os
from webirr import WeBirrClient
def main():
api_key = os.getenv("WEBIRR_TEST_ENV_API_KEY", "")
merchant_id = os.getenv("WEBIRR_TEST_ENV_MERCHANT_ID", "")
# api_key = "YOUR_API_KEY"
# merchant_id = "YOUR_MERCHANT_ID"
api = WeBirrClient(merchant_id, api_key, True)
payment_code = "PAYMENT_CODE_YOU_SAVED_AFTER_CREATING_A_NEW_BILL"
print("\nGetting Payment Status...")
res = api.get_payment_status(payment_code)
if not res.error:
# success
if res.res and res.res.is_paid:
payment = res.res.data
print("\nbill is paid")
print("\nbill payment detail")
print(f"\nBank: {payment.bank_id}")
print(f"\nBank Reference Number: {payment.payment_reference}")
print(f"\nAmount Paid: {payment.amount}")
print(f"\nPayment Date: {payment.payment_date}")
else:
print("\nbill is pending payment")
else:
# fail
print(f"\nerror: {res.error}")
print(f"\nerrorCode: {res.error_code}") # can be used to handle specific business error such as ERROR_INVALID_INPUT
main()
Sample object returned from getPaymentStatus()
sample = {
"error": None,
"res": {
"status": 2,
"data": {
"status": 2,
"id": 111219507,
"bankID": "cbe_mobile",
"paymentReference": "TX70e78862148f4c249606",
"paymentDate": "2025-02-26 22:17:19",
"confirmed": True,
"confirmedTime": "2025-02-26 22:17:19",
"amount": "278",
"wbcCode": "149 233 514",
"updateTimeStamp": "2025022622171981338",
},
},
"errorCode": None,
}
Use payment_date as the payment time field. time remains available as a deprecated backward-compatible alias.
Deleting an existing Bill from WeBirr Servers (if it is not paid)
import os
from webirr import WeBirrClient
def main():
api_key = os.getenv("WEBIRR_TEST_ENV_API_KEY", "")
merchant_id = os.getenv("WEBIRR_TEST_ENV_MERCHANT_ID", "")
# api_key = "YOUR_API_KEY"
# merchant_id = "YOUR_MERCHANT_ID"
api = WeBirrClient(merchant_id, api_key, True)
payment_code = "PAYMENT_CODE_YOU_SAVED_AFTER_CREATING_A_NEW_BILL"
print("\nDeleting Bill...")
res = api.delete_bill(payment_code)
if not res.error:
# success
print("\nbill is deleted succesfully") # res.res will be 'OK' no need to check here!
else:
# fail
print(f"\nerror: {res.error}")
print(f"\nerrorCode: {res.error_code}") # can be used to handle specific business error such as ERROR_INVALID_INPUT
main()
Getting list of Payments and process them with Bulk Polling Consumer
import os
import time
from webirr import WeBirrClient
class PaymentProcessor:
def __init__(self):
api_key = os.getenv("WEBIRR_TEST_ENV_API_KEY", "")
merchant_id = os.getenv("WEBIRR_TEST_ENV_MERCHANT_ID", "")
self.api = WeBirrClient(merchant_id, api_key, True)
self.last_time_stamp = "20251231" # Example cursor. Save the last processed payment updateTimeStamp in your database.
def run_once(self):
print("\nRetrieving Payments...")
self.fetch_and_process_payments()
def run_forever(self):
while True:
self.run_once()
print("\nSleeping for 5 seconds...")
time.sleep(5)
def fetch_and_process_payments(self):
limit = 100 # Number of records to retrieve depending on your processing requirement & capacity
response = self.api.get_payments(self.last_time_stamp, limit)
if not response.error:
# success
if len(response.res) == 0:
print("\nNo new payments found.")
for payment in response.res:
self.process_payment(payment)
print("\n-----------------------------")
if len(response.res) > 0:
self.last_time_stamp = response.res[-1].update_time_stamp
print(f"\nLast Timestamp: {self.last_time_stamp}") # save updateTimeStamp to your database for the next get_payments() call
else:
# fail
print(f"\nerror: {response.error}")
print(f"\nerrorCode: {response.error_code}") # can be used to handle specific business error such as ERROR_INVALID_INPUT
def process_payment(self, payment):
# Process Payment should be implemented as idempotent operation for production use cases.
# This method and logic can be shared among all payment processing consumers: 1. bulk polling, 2. webhook, 3. single payment polling.
print(f"\nPayment Status: {payment.status}")
if payment.is_paid:
print("\nPayment Status Text: Paid.")
if payment.is_reversed:
print("\nPayment Status Text: Reversed.")
print(f"\nBank: {payment.bank_id}")
print(f"\nBank Reference Number: {payment.payment_reference}")
print(f"\nAmount Paid: {payment.amount}")
print(f"\nPayment Date: {payment.payment_date}")
print(f"\nReversal/Cancel Date: {payment.canceled_time}")
print(f"\nUpdate Timestamp: {payment.update_time_stamp}")
PaymentProcessor().run_once()
Bulk polling should persist updateTimeStamp only after processing the batch successfully. Polling processors should be idempotent because duplicate/redundant reads are possible.
Webhooks - Payment processing using Webhook Callbacks
import hmac
import json
import os
from webirr import PaymentResponse
class Webhook:
# Webhook handler for processing payment updates from WeBirr.
# This endpoint should be hosted on a secure server with HTTPS enabled.
def handle_request(self, method, provided_auth_key, raw_payload):
# Validate request method is POST
if method.upper() != "POST":
return self.json_response(405, {"error": "Method Not Allowed. POST required."})
# Authenticate using authKey query string parameter.
if not self.is_authenticated(provided_auth_key):
return self.json_response(403, {"error": "Unauthorized access. Invalid authKey."})
if not raw_payload:
return self.json_response(400, {"error": "Empty request body."})
try:
payload = json.loads(raw_payload)
except json.JSONDecodeError:
return self.json_response(400, {"error": "Invalid JSON format."})
payment_data = payload.get("data", payload)
if not payment_data:
return self.json_response(400, {"error": "Invalid payment data."})
payment = PaymentResponse.from_dict(payment_data)
self.process_payment(payment)
return self.json_response(200, {"success": True, "message": "Payment received and queued for processing"})
def is_authenticated(self, provided_auth_key):
expected_auth_key = os.getenv("WEBIRR_WEBHOOK_AUTH_KEY", "YOUR_WEBHOOK_AUTH_KEY")
return bool(expected_auth_key) and hmac.compare_digest(expected_auth_key, provided_auth_key or "")
def process_payment(self, payment):
# Process Payment should be implemented as idempotent operation for production use cases.
# This method and logic can be shared among all payment processing consumers: 1. bulk polling, 2. webhook, 3. single payment polling.
print(f"\nPayment Status: {payment.status}")
if payment.is_paid:
print("\nPayment Status Text: Paid.")
if payment.is_reversed:
print("\nPayment Status Text: Reversed.")
print(f"\nBank: {payment.bank_id}")
print(f"\nBank Reference Number: {payment.payment_reference}")
print(f"\nAmount Paid: {payment.amount}")
print(f"\nPayment Date: {payment.payment_date}")
print(f"\nReversal/Cancel Date: {payment.canceled_time}")
print(f"\nUpdate Timestamp: {payment.update_time_stamp}")
def json_response(self, status_code, body):
return {"status_code": status_code, "content_type": "application/json", "body": json.dumps(body)}
# Once hosted, the webhook URL needs to be shared with WeBirr for configuration.
Gettting basic Statistics about bills created and payments received for a date range
import os
from webirr import WeBirrClient
def main():
api_key = os.getenv("WEBIRR_TEST_ENV_API_KEY", "")
merchant_id = os.getenv("WEBIRR_TEST_ENV_MERCHANT_ID", "")
# api_key = "YOUR_API_KEY"
# merchant_id = "YOUR_MERCHANT_ID"
api = WeBirrClient(merchant_id, api_key, True)
date_from = "2025-01-01" # YYYY-MM-DD
date_to = "2030-01-31" # YYYY-MM-DD
print("\nRetrieving Statistics...")
print(f"\nDate From: {date_from}")
print(f"\nDate To: {date_to}")
response = api.get_stat(date_from, date_to)
if not response.error:
# success
stat = response.res
print(f"\nNumber of Bills Created: {stat.n_bills}")
print(f"\nNumber of Paid Bills: {stat.n_bills_paid}")
print(f"\nNumber of Unpaid Bills: {stat.n_bills_unpaid}")
print(f"\nAmount of Bills: {stat.amount_bills}")
print(f"\nAmount Paid: {stat.amount_paid}")
print(f"\nAmount Unpaid: {stat.amount_unpaid}")
else:
# fail
print(f"\nError: {response.error}")
print(f"\nError Code: {response.error_code}")
main()
Examples
The examples directory includes separate workflows for the common gateway operations:
python examples/example1_create_update_bill.py
python examples/example2_payment_status_single_poll.py
python examples/example3_delete_bill.py
python examples/example4_payment_status_bulk_poll.py
python examples/example5_stat_report.py
python examples/example6_payment_status_webhook.py
python examples/example7_get_bill_and_list_bills.py
Reusable HTTP Session
For batch or mass bill workloads, pass a configured requests.Session so your application can reuse connections, configure adapters, and apply its own retry policy:
import requests
from webirr import WeBirrClient
session = requests.Session()
api = WeBirrClient(merchant_id, api_key, True, session=session)
The SDK does not silently retry bill creation. Configure retry behavior in your application so duplicate create/update processing remains under your control.
Project details
Release history Release notifications | RSS feed
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 webirr-1.0.1.tar.gz.
File metadata
- Download URL: webirr-1.0.1.tar.gz
- Upload date:
- Size: 17.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
38f009471a23c888fc3223ccf0aa9bcb1040336915767721d6a387d91027010c
|
|
| MD5 |
59cc373880eaf1c02a60ab76115554f5
|
|
| BLAKE2b-256 |
58baf40d500c8b94e103e9185fcf1afd43eec19ef8134f96f09861569e0b5af8
|
Provenance
The following attestation bundles were made for webirr-1.0.1.tar.gz:
Publisher:
publish-pypi.yml on webirr/webirr-api-python-client
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
webirr-1.0.1.tar.gz -
Subject digest:
38f009471a23c888fc3223ccf0aa9bcb1040336915767721d6a387d91027010c - Sigstore transparency entry: 1810284457
- Sigstore integration time:
-
Permalink:
webirr/webirr-api-python-client@7604d72bfbd607657db04c52460403b60e8ce7a3 -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/webirr
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@7604d72bfbd607657db04c52460403b60e8ce7a3 -
Trigger Event:
push
-
Statement type:
File details
Details for the file webirr-1.0.1-py3-none-any.whl.
File metadata
- Download URL: webirr-1.0.1-py3-none-any.whl
- Upload date:
- Size: 10.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f5e8b32bcaf4d21a0775e8030ed3644acc780644425148d088ecebf30bf79978
|
|
| MD5 |
18e757ffa4f9e8a43e109e50099deb7b
|
|
| BLAKE2b-256 |
1de951141f08ab4734eaa4cca79c111d17d8a31be7dd91b5679cf949a50d064c
|
Provenance
The following attestation bundles were made for webirr-1.0.1-py3-none-any.whl:
Publisher:
publish-pypi.yml on webirr/webirr-api-python-client
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
webirr-1.0.1-py3-none-any.whl -
Subject digest:
f5e8b32bcaf4d21a0775e8030ed3644acc780644425148d088ecebf30bf79978 - Sigstore transparency entry: 1810284488
- Sigstore integration time:
-
Permalink:
webirr/webirr-api-python-client@7604d72bfbd607657db04c52460403b60e8ce7a3 -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/webirr
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@7604d72bfbd607657db04c52460403b60e8ce7a3 -
Trigger Event:
push
-
Statement type: