Skip to content

Latest commit

 

History

20 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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. EasyPaisa and JazzCash (MWALLET) are implemented. 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 ✅ Live (MWALLET)
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/
├── conftest.py               # Shared fixtures & mock helpers
├── 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. Add shared helpers to tests/conftest.py.

conftest.py — add a success body constant and a factory:

# tests/conftest.py
from raqm_core.hbl import HBL

HBL_SUCCESS_BODY = {
    "orderId": "abc123",
    "responseCode": "0000",
    "responseDesc": "SUCCESS",
}

def make_hbl(response_body: dict) -> HBL:
    return HBL(
        api_key="test",
        sandbox=True,
        client=make_client(response_body),
    )

test file — import from conftest:

# tests/test_hbl.py
import pytest
from raqm_core.exceptions.exceptions import PaymentError
from tests.conftest import HBL_SUCCESS_BODY, make_client, make_hbl


class TestPay:
    @pytest.mark.asyncio
    async def test_success(self):
        hbl = make_hbl(HBL_SUCCESS_BODY)
        result = await hbl.pay(...)
        assert result.responseCode == "0000"

    @pytest.mark.asyncio
    async def test_network_error(self):
        hbl = HBL(
            api_key="test",
            sandbox=True,
            client=make_client({"error": "timeout"}, status_code=500),
        )
        with pytest.raises(NetworkError):
            await hbl.pay(...)

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 (MWALLET)
  • 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

About

A python library made for easy integration with Pakistani banking

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages