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
PaymentErroron 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.AsyncClientfor 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.AsyncClientfor testing - Minimal dependencies — just
httpxandpydantic
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
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 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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5c3bcb741e6eee16d8c2c7fdb2f7e597e24bd19525dea442d9894aa94415b9ae
|
|
| MD5 |
198f353136f774e9b6978a9d3d25e492
|
|
| BLAKE2b-256 |
a106caa506025de69fa9f3f5cb9473f81e839b12a91806bb56cd0ff687add696
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
41b6e6abb8e80af8947676e616a9ee15f4a1efe9871f1913f5ce60a3715dcce4
|
|
| MD5 |
4a6bc55db014c6beaf47ddb1a499fe2c
|
|
| BLAKE2b-256 |
37ea144b7d16ec5cd880324d2af15909798792065afea63b473c2c47d2ce0a6b
|