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.6.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.6-py3-none-any.whl (10.0 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: raqm_core-0.2.6.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.6.tar.gz
Algorithm Hash digest
SHA256 5c3bcb741e6eee16d8c2c7fdb2f7e597e24bd19525dea442d9894aa94415b9ae
MD5 198f353136f774e9b6978a9d3d25e492
BLAKE2b-256 a106caa506025de69fa9f3f5cb9473f81e839b12a91806bb56cd0ff687add696

See more details on using hashes here.

File details

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

File metadata

  • Download URL: raqm_core-0.2.6-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.6-py3-none-any.whl
Algorithm Hash digest
SHA256 41b6e6abb8e80af8947676e616a9ee15f4a1efe9871f1913f5ce60a3715dcce4
MD5 4a6bc55db014c6beaf47ddb1a499fe2c
BLAKE2b-256 37ea144b7d16ec5cd880324d2af15909798792065afea63b473c2c47d2ce0a6b

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