Metadata-Version: 2.4
Name: edahab
Version: 1.1.1
Summary: Python client for the eDahab payment API
Author: eDahab Python client contributors
License: MIT
Keywords: edahab,payment,api,somalia
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# edahab — Python client

Lightweight Python client for the official [eDahab API](https://docs.edahab.net/api-introduction). Uses only the standard library (no required third-party packages).

- [Install](#install)
- [Official API docs](#official-api-docs)
- [Configuration (set credentials once)](#configuration-set-credentials-once)
- [Module functions](#module-functions)
- [`EdahabClient`](#edahabclient)
- [Result types](#result-types)
- [Status codes](#status-codes)
- [Errors (exceptions)](#errors-exceptions)
- [Constants](#constants)
- [Package version](#package-version)

## Install

```bash
pip install edahab
```

## Official API docs

| Topic | URL |
|--------|-----|
| Request format & hash | [API introduction](https://docs.edahab.net/api-introduction) |
| Customer payment (push/pop-up) | [Purchase / Issueinvoice](https://docs.edahab.net/purchase) |
| Merchant credit payment | [Credit payment / agentPayment](https://docs.edahab.net/credit-invoice) |
| Verify invoice | [CheckInvoiceStatus](https://docs.edahab.net/verify-invoice) |
| Numeric status codes | [Status codes](https://docs.edahab.net/status) |

---

## Configuration (set credentials once)

### `configure`

Save `api_key` and `api_secret` for the process. After this, `pay`, `verify_invoice`, and `send_credit` can omit credentials.

**Parameters (summary):** `api_key`, `api_secret`, optional `base_url`, `timeout`, `open_url` (tests / custom HTTP).

**Returns:** `EdahabClient` (the shared default client).

```python
from edahab import configure, pay

configure("your-api-key", "your-api-secret")

result = pay(customer="65", amount=1, agent="5555")
```

Custom base URL and timeout:

```python
from edahab import configure

configure(
    "your-api-key",
    "your-api-secret",
    base_url="https://edahab.net/api/api/",
    timeout=60.0,
)
```

### `configure_from_env`

Same as `configure`, but reads credentials from environment variables (default names below).

**Parameters (summary):** optional `api_key_env`, `api_secret_env`, `base_url`, `timeout`, `open_url`.

**Returns:** `EdahabClient`.

```bash
export EDAHAB_API_KEY="your-api-key"
export EDAHAB_API_SECRET="your-api-secret"
```

```python
from edahab import configure_from_env, pay

configure_from_env()
pay(customer="65", amount=1, agent="5555")
```

Custom env var names:

```python
configure_from_env(api_key_env="MY_EDAHAB_KEY", api_secret_env="MY_EDAHAB_SECRET")
```

### `get_client`

Return the client created by `configure` or `configure_from_env`.

**Raises:** `RuntimeError` if nothing was configured yet.

```python
from edahab import configure, get_client

configure("your-api-key", "your-api-secret")

client = get_client()
same = client.pay(customer="65", amount=1, agent="5555")
```

### `clear_configuration`

Remove the shared default client (useful in tests).

```python
from edahab import clear_configuration

clear_configuration()
```

---

## Module functions

### `pay`

Create a purchase invoice (`Issueinvoice`). Customer confirms on their phone (push/pop-up).

- With **configured** credentials:

```python
from edahab import configure, pay, is_invoice_paid

configure("your-api-key", "your-api-secret")
invoice = pay(customer="65", amount=1, agent="5555", currency="USD")
print(is_invoice_paid(invoice.invoice_status), invoice.invoice_id)
```

- **One-call** with explicit credentials (no `configure`):

```python
from edahab import pay

invoice = pay(
    "your-api-key",
    "your-api-secret",
    customer="65",
    amount=1,
    agent="5555",
)
```

### `verify_invoice`

Double-check invoice status (`CheckInvoiceStatus`). Works with configured client or explicit `api_key` / `api_secret` (same rules as `pay`).

```python
from edahab import configure, pay, verify_invoice, is_invoice_paid

configure("your-api-key", "your-api-secret")
issued = pay(customer="65", amount=1, agent="5555")

if issued.invoice_id:
    confirmed = verify_invoice(invoice_id=issued.invoice_id)
    print(confirmed.invoice_status, is_invoice_paid(confirmed.invoice_status))
```

With explicit credentials:

```python
verify_invoice(
    "your-api-key",
    "your-api-secret",
    invoice_id="INV123456789",
)
```

### `send_credit`

Send money from your merchant balance (`agentPayment`). Uses configured client or pass keys like `pay`.

```python
from edahab import configure, send_credit

configure("your-api-key", "your-api-secret")
credit = send_credit(
    to="252612345678",
    amount=100,
    reference="order-999",
    currency="SLSH",
)
print(credit.transaction_status, credit.transaction_message)
```

### `is_invoice_paid`

Return `True` only when `InvoiceStatus == "Paid"` (per [purchase docs](https://docs.edahab.net/purchase)).

```python
from edahab import is_invoice_paid

assert is_invoice_paid("Paid") is True
assert is_invoice_paid("Unpaid") is False
```

### `generate_request_hash`

Build the `hash` query string: SHA-256 hex of **(JSON body string + api_secret)**. Use the **exact** same body string you POST.

```python
from edahab import generate_request_hash, serialize_body

body = serialize_body({"apiKey": "123", "invoiceId": "ABC"})
h = generate_request_hash(body, "secretapikey")
```

### `serialize_body`

Turn a `dict` into compact JSON with sorted keys (stable for signing and posting).

```python
from edahab import serialize_body

s = serialize_body({"apiKey": "x", "amount": 1})
```

### `describe_status_code`

Map an invoice `StatusCode` integer (or `None`) to a short description string.

```python
from edahab import describe_status_code, StatusCode

print(describe_status_code(0))
print(describe_status_code(StatusCode.INSUFFICIENT_CUSTOMER_BALANCE))
print(describe_status_code(None))  # None
```

---

## `EdahabClient`

Low-level control: one instance holds your key pair and options.

### Constructor `EdahabClient(api_key, api_secret, *, base_url=..., timeout=..., open_url=...)`

```python
from edahab import EdahabClient, DEFAULT_BASE_URL

client = EdahabClient(
    "your-api-key",
    "your-api-secret",
    base_url=DEFAULT_BASE_URL,
    timeout=30.0,
)
```

### `EdahabClient.from_env`

Build a client from environment variables (same defaults as `configure_from_env`).

```python
client = EdahabClient.from_env()
```

### `client.api_key` (property)

Read-only API key used in JSON bodies.

```python
print(client.api_key)
```

### `issue_invoice`

Official parameter names: `edahab_number`, `amount`, `agent_code`, optional `currency`, `raise_on_api_error`.

```python
result = client.issue_invoice("65", 1, "5555", currency="USD")
```

Allow non-success `StatusCode` without raising:

```python
result = client.issue_invoice(
    "65", 1, "5555", raise_on_api_error=False
)
print(result.status_code, result.validation_errors)
```

### `pay`

Alias for `issue_invoice` with keyword names: `customer`, `amount`, `agent`, `currency`, `raise_on_api_error`.

```python
result = client.pay(customer="65", amount=1, agent="5555")
```

### `agent_payment`

Credit payment: `phone_number`, `transaction_amount`, `transaction_id`, optional `currency`.

```python
out = client.agent_payment(
    "252612345678",
    100,
    "order-999",
    currency="SLSH",
)
```

### `send_credit`

Alias: `to`, `amount`, `reference`, `currency`.

```python
out = client.send_credit(to="252612345678", amount=100, reference="order-999")
```

### `check_invoice_status`

`invoice_id`, optional `raise_on_api_error`.

```python
status = client.check_invoice_status("INV123456789")
```

### `verify`

Alias for `check_invoice_status`.

```python
status = client.verify("INV123456789")
```

---

## Result types

Dataclasses returned by the client. Each includes **`raw`** (`dict`) with the full API payload.

### `IssueInvoiceResult`

Fields: `invoice_status`, `transaction_id`, `invoice_id`, `status_code`, `request_id`, `status_description`, `validation_errors`, `raw`.

### `CheckInvoiceStatusResult`

Same fields as `IssueInvoiceResult` (verify endpoint matches purchase response shape in the docs).

### `AgentPaymentResult`

Fields: `transaction_status`, `transaction_message`, `phone_number`, `transaction_id`, `currency`, `raw`.

```python
from edahab import EdahabClient

client = EdahabClient("k", "s")
r = client.pay(customer="65", amount=1, agent="5555")
print(r.invoice_id, r.raw)
```

---

## Status codes

### `StatusCode` (enum)

`SUCCESS`, `API_ERROR`, `INVALID_JSON`, `VALIDATION_ERROR`, `INVALID_API_CREDENTIALS`, `INSUFFICIENT_CUSTOMER_BALANCE`, `INVOICE_NOT_FOUND`, `INVALID` (int values 0–7; see [status docs](https://docs.edahab.net/status)).

```python
from edahab import StatusCode

assert StatusCode.SUCCESS == 0
```

### `STATUS_DESCRIPTIONS`

`dict[int, str]` — human-readable lines for each code. Also used internally by `describe_status_code`.

```python
from edahab import STATUS_DESCRIPTIONS, StatusCode

print(STATUS_DESCRIPTIONS[StatusCode.SUCCESS.value])
```

---

## Errors (exceptions)

| Class | When |
|--------|------|
| `EdahabError` | Base type for all package errors |
| `EdahabHTTPError` | HTTP/network failure; may set `status_code`, `url`, `body` |
| `EdahabDecodeError` | Response is not JSON or not a JSON object |
| `EdahabCredentialError` | API `StatusCode` 4 (bad key/hash) |
| `EdahabValidationError` | API `StatusCode` 3; check `validation_errors` |
| `EdahabAPIStatusError` | Other non-zero invoice `StatusCode`; check `raw_response` |

```python
from edahab import (
    EdahabClient,
    EdahabCredentialError,
    EdahabValidationError,
    EdahabAPIStatusError,
    EdahabHTTPError,
)

client = EdahabClient("your-api-key", "your-api-secret")
try:
    client.issue_invoice("65", 1, "5555")
except EdahabCredentialError as e:
    print("Fix key/secret or hash", e)
except EdahabValidationError as e:
    print(e.validation_errors)
except EdahabAPIStatusError as e:
    print(e.status_code, e.raw_response)
except EdahabHTTPError as e:
    print(e.status_code, e.body)
```

---

## Constants

### `DEFAULT_BASE_URL`

`"https://edahab.net/api/api/"` — default POST base for all endpoints.

```python
from edahab import DEFAULT_BASE_URL
print(DEFAULT_BASE_URL)
```

### Default environment variable names

Used by `configure_from_env` and `EdahabClient.from_env`:

- `EDAHAB_API_KEY`
- `EDAHAB_API_SECRET`

---

## Package version

```python
import edahab

print(edahab.__version__)
```

---

## Quick reference — everything exported from `edahab`

`configure`, `configure_from_env`, `get_client`, `clear_configuration`, `pay`, `verify_invoice`, `send_credit`, `is_invoice_paid`, `generate_request_hash`, `serialize_body`, `describe_status_code`, `EdahabClient`, `DEFAULT_BASE_URL`, `StatusCode`, `STATUS_DESCRIPTIONS`, `IssueInvoiceResult`, `CheckInvoiceStatusResult`, `AgentPaymentResult`, `EdahabError`, `EdahabHTTPError`, `EdahabDecodeError`, `EdahabCredentialError`, `EdahabValidationError`, `EdahabAPIStatusError`.
