Skip to main content

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

webirr-1.0.1.tar.gz (17.4 kB view details)

Uploaded Source

Built Distribution

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

webirr-1.0.1-py3-none-any.whl (10.5 kB view details)

Uploaded Python 3

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

Hashes for webirr-1.0.1.tar.gz
Algorithm Hash digest
SHA256 38f009471a23c888fc3223ccf0aa9bcb1040336915767721d6a387d91027010c
MD5 59cc373880eaf1c02a60ab76115554f5
BLAKE2b-256 58baf40d500c8b94e103e9185fcf1afd43eec19ef8134f96f09861569e0b5af8

See more details on using hashes here.

Provenance

The following attestation bundles were made for webirr-1.0.1.tar.gz:

Publisher: publish-pypi.yml on webirr/webirr-api-python-client

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for webirr-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 f5e8b32bcaf4d21a0775e8030ed3644acc780644425148d088ecebf30bf79978
MD5 18e757ffa4f9e8a43e109e50099deb7b
BLAKE2b-256 1de951141f08ab4734eaa4cca79c111d17d8a31be7dd91b5679cf949a50d064c

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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