Skip to main content

Unified Python SDK for Pakistani payment gateways (EasyPaisa, JazzCash, HBL, ABL, UBL, etc.)

Project description

raqm-core

Unified Python SDK for Pakistani payment gateways.

One async, type-safe interface across multiple processors — EasyPaisa, JazzCash, HBL, ABL, UBL, and more.

⚠️ Work in progress. Currently only EasyPaisa is fully implemented. JazzCash is in progress. Contributions welcome!

⚠️ Breaking change in v0.2.4 — methods now raise PaymentError on business-level failures instead of returning a result with a non-success response code. Wrap calls in try/except to handle errors gracefully.


Supported Gateways

Gateway Status
EasyPaisa ✅ Live
JazzCash 🚧 In progress
HBL 📅 Planned
ABL 📅 Planned
UBL 📅 Planned
📅 Your gateway here

Features

  • Async-first — built on httpx.AsyncClient for non-blocking I/O
  • Type-safe — all responses are validated through Pydantic models
  • Consistent API — same patterns across all gateways
  • Mock-friendly — inject your own httpx.AsyncClient for testing
  • Minimal dependencies — just httpx and pydantic

Installation

pip install raqm-core

Or with uv:

uv add raqm-core

Requires Python 3.9+.


Quick Start

import asyncio
from raqm_core import EasyPaisa


async def main():
    ep = EasyPaisa(
        store_id="43",
        username="your_username",
        password="your_password",
        sandbox=True,
    )

    result = await ep.pay_via_ma(
        order_id="order_123",
        amount="1000.00",
        email="customer@example.com",
        mobile_number="03451234567",
    )

    print(f"Status: {result.responseCode}{result.responseDesc}")
    print(f"Transaction ID: {result.transactionId}")


asyncio.run(main())

Usage

EasyPaisa

Initialisation

from raqm_core import EasyPaisa

ep = EasyPaisa(
    store_id="43",
    username="your_username",
    password="your_password",
    sandbox=True,           # set False for production
)

You can optionally inject your own httpx.AsyncClient — useful for testing with httpx.MockTransport:

import httpx

client = httpx.AsyncClient(...)
ep = EasyPaisa(store_id="43", username="...", password="...", sandbox=True, client=client)

Mobile Account (MA) Payment

result = await ep.pay_via_ma(
    order_id="order_123",
    amount="500.00",
    email="customer@example.com",
    mobile_number="03451234567",
)

# EasyPaisaMAResponse fields:
result.orderId           # str
result.storeId           # int
result.transactionId     # str
result.transactionDateTime  # str (dd/MM/yyyy hh:mm AM/PM)
result.responseCode      # EasypaisaResponseCode (enum)
result.responseDesc      # str

Over-the-Counter (OTC) Payment

result = await ep.pay_via_otc(
    order_id="order_456",
    amount="1500.00",
    email="customer@example.com",
    msisdn="03451234567",
    token_expiry="01/01/2025 11:59 PM",
)

# EasyPaisaOTCResponse fields (extends base):
result.paymentToken              # str
result.paymentTokenExpiryDateTime  # str

Transaction Inquiry

result = await ep.inquire_transaction_status(
    order_id="order_123",
    account_number="123456789",
)

# EasyPaisaInquireTransactionResponse fields:
result.transactionStatus   # str (e.g. "COMPLETED")
result.transactionAmount   # str
result.accountNum          # str
result.storeName           # str
result.msisdn              # str
result.paymentMode         # str ("MA", "OTC", "CC")

Error Handling

All gateway methods raise typed exceptions on failure:

RaqmCoreError          # Base exception for the SDK
├── NetworkError       # HTTP/transport errors (connection, timeout, 4xx/5xx)
└── PaymentError       # Business-level failures (non-success response code)
                      #   .response — the full gateway response object
from raqm_core.exceptions.exceptions import NetworkError, PaymentError

try:
    result = await ep.pay_via_ma(...)
except NetworkError as e:
    print(f"Network issue: {e}")
except PaymentError as e:
    print(f"Payment failed: {e}")
    print(f"Response code: {e.response.responseCode}")

PaymentError carries the gateway's response object in exc.response, so you can inspect the raw error details even when the SDK raises.


Architecture

Each payment gateway follows a consistent 3-layer structure:

src/
├── <gateway>.py              # Client class — public API
├── headers/
│   └── <gateway>.py          # Auth / signing helpers
└── schemas/
    └── <gateway>.py          # Pydantic request/response models

Current structure:

src/
├── easypaisa.py               # EasyPaisa client
├── jazzcash.py                # JazzCash client
├── exceptions/
│   └── exceptions.py          # Custom exception hierarchy
├── headers/
│   ├── easypaisa.py           # Basic Auth header
│   └── jazzcash.py            # SHA-256 secure hash
└── schemas/
    ├── easypaisa.py           # EasyPaisa Pydantic models
    └── jazzcash.py            # JazzCash Pydantic models

tests/
├── test_easypaisa.py         # EasyPaisa integration tests
├── test_jazzcash.py          # JazzCash integration tests
└── headers/
    ├── test_easypaisa.py     # Auth header unit tests
    └── test_jazzcash.py      # Secure hash unit tests

Adding a New Gateway

Want to add support for a new processor? Follow this checklist.

1. Research the API

Understand the gateway's:

  • Authentication mechanism (Basic Auth, HMAC, API key, etc.)
  • Endpoints and request/response formats
  • Error/response codes

2. Create header helpers

src/headers/<gateway>.py — authentication or signing logic.

# src/headers/hbl.py
def generate_signature(api_key: str, payload: dict) -> str:
    ...

Write unit tests in tests/headers/test_<gateway>.py.

3. Create Pydantic schemas

src/schemas/<gateway>.py — response models and enums.

# src/schemas/hbl.py
from pydantic import BaseModel, Field


class HBLResponse(BaseModel):
    orderId: str = Field(...)
    responseCode: str = Field(...)
    responseDesc: str = Field(...)

4. Create the client class

src/<gateway>.py — async client with a _post() helper and public methods.

# src/hbl.py
import httpx
from .headers.hbl import generate_signature
from .schemas.hbl import HBLResponse


class HBL:
    def __init__(self, api_key: str, sandbox: bool, client: httpx.AsyncClient | None = None):
        self._client = client or httpx.AsyncClient()
        ...

    async def _post(self, endpoint: str, payload: dict) -> dict:
        ...

    async def pay(self, order_id: str, amount: str, ...) -> HBLResponse:
        ...

5. Write integration tests

tests/test_<gateway>.py — use httpx.MockTransport to mock responses.

# tests/test_hbl.py
import httpx
import pytest
from raqm_core import HBL


@pytest.mark.asyncio
async def test_pay_success():
    def handler(request: httpx.Request) -> httpx.Response:
        return httpx.Response(status_code=200, json={...})

    client = httpx.AsyncClient(transport=httpx.MockTransport(handler))
    hbl = HBL(api_key="test", sandbox=True, client=client)
    result = await hbl.pay(...)
    assert result.responseCode == "0000"

6. Update this README

Add the new gateway to the Supported Gateways table with the appropriate status badge.


Roadmap

  • EasyPaisa (MA, OTC, inquiry)
  • JazzCash client + schemas
  • HBL payment gateway
  • ABL payment gateway
  • UBL payment gateway
  • Standardised error handling across gateways
  • Request/response logging middleware
  • CI/CD + automated testing

Development

# Clone the repo
git clone https://github.com/your-username/raqm-core.git
cd raqm-core

# Create a virtual environment
uv venv
source .venv/bin/activate

# Install dependencies
uv sync

# Run tests
pytest

Contributing

Contributions are welcome! See Adding a New Gateway for the detailed guide.

Please open an issue first to discuss your proposed changes.


License

MIT

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

raqm_core-0.2.5.tar.gz (12.3 kB view details)

Uploaded Source

Built Distribution

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

raqm_core-0.2.5-py3-none-any.whl (10.0 kB view details)

Uploaded Python 3

File details

Details for the file raqm_core-0.2.5.tar.gz.

File metadata

  • Download URL: raqm_core-0.2.5.tar.gz
  • Upload date:
  • Size: 12.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for raqm_core-0.2.5.tar.gz
Algorithm Hash digest
SHA256 1b3b76a09791bfa110debebbf7b0e10d9efdbf95cf50dc9f9d0bbfd0f7be5e84
MD5 0d56db5003004468dc36696361afb0b0
BLAKE2b-256 68e47e5475296de95b848a4b3e97e3a7f7888d04c1328cc4c17e53d30ac3b8a1

See more details on using hashes here.

File details

Details for the file raqm_core-0.2.5-py3-none-any.whl.

File metadata

  • Download URL: raqm_core-0.2.5-py3-none-any.whl
  • Upload date:
  • Size: 10.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for raqm_core-0.2.5-py3-none-any.whl
Algorithm Hash digest
SHA256 b3dd4dc4484b3daa0ab1f56dd7e6479019ba63e92278010b2a2a437d5b5465b4
MD5 67336407144da2ab139f857a10dd7c87
BLAKE2b-256 0a139ac9c881be92f21a35493d9718357ee39a72be97f2fce22e4270fd2d32da

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