Metadata-Version: 2.4
Name: avepay-mcf
Version: 0.1.0
Summary: SDK officiel AvePay MCF — certification fiscale (DGI Burkina Faso / SECEF)
License: MIT
Requires-Python: >=3.9
Requires-Dist: httpx>=0.27
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == 'dev'
Description-Content-Type: text/markdown

# avepay (Python)

SDK officiel Python pour l'API **AvePay MCF** — certification fiscale DGI Burkina Faso / SECEF.

- Client **sync** (`AvePay`) et **async** (`AsyncAvePay`), même API.
- Payload **friendly** (snake_case, calculs auto) avec échappatoire **brute** (clés camelCase).
- Python 3.9+, basé sur `httpx`.

## Installation

```bash
pip install avepay-mcf
```

## Quickstart

```python
from avepay.mcf import AvePay, AvePayError

avepay = AvePay(api_key="ak_live_…", isf="1")
mcf = avepay.mcf("EL02000015-1")

receipt = mcf.certify(
    number="FV-2026-0001",
    type="FV",                                    # FV | FT | EV | ET (défaut FV)
    operator={"id": 1, "name": "Awa"},
    customer={"type": "PP", "name": "Client Comptant"},
    items=[
        {"name": "Riz 25kg", "tax_group": "B", "unit_price": 15000, "quantity": 1},
    ],
    payments=[{"method": "cash", "amount": 15000}],
)

print(receipt.sig)        # signature SECEF (codeSecef)
print(receipt.qr)         # contenu QR
print(receipt.total_ttc)  # 15000
print(receipt.counters)   # Counters(tc=…, fvc=…, frc=…)
```

`certify()`/`credit_note()` acceptent un dict positionnel **ou** des kwargs (les kwargs priment).

### Mapping friendly → brut

- `number` → `invoiceNumber`, `type` → `invoiceType`, `operator={"id","name"}` → `operatorId`/`operatorName`.
- items : `amount_ttc` calculé (`unit_price * quantity`), `tax_rate` déduit du `tax_group` (A = 0 %, B = 18 %). Groupe inconnu (C..N) → fournir `tax_rate`.
- payments : alias `cash`→`E`, `transfer`→`V`, `card`→`C`, `mobile`→`M`, `cheque`→`B`, `other`→`A` (codes bruts acceptés).
- Toute clé **brute** (ex. `invoiceType`, `clientType`) prime sur l'alias friendly.

## Avoir (credit note)

```python
avoir = mcf.credit_note(
    number="FA-2026-0001",
    from_=receipt,                  # dérive credit_note_ref = f"{nim}-{fvc}"
    # ou: credit_note_ref="EL02000015-1-291",
    credit_note_nature="COR",       # COR | RAN | RAM | RRR (défaut COR)
    operator={"id": 1, "name": "Awa"},
    customer={"type": "PP"},
    items=[{"name": "Riz 25kg", "tax_group": "B", "unit_price": 15000}],
    payments=[{"method": "cash", "amount": 15000}],
)
```

## Info / cancel-pending / me

```python
info = mcf.info()             # mode, compteurs, marchand
mcf.cancel_pending()          # annule une transaction 38h pendante
me = avepay.me()              # org + NIM scopés + état bridges
```

## Erreurs

```python
try:
    mcf.certify(...)
except AvePayError as e:
    print(e.code, e.http_status, e.message, e.request_id)
    if e.is_bridge_offline:
        ...  # MCF hors ligne — réessayer plus tard
```

Retry réseau/5xx automatique avec backoff + `Idempotency-Key` réutilisée entre tentatives.

## Async

```python
import asyncio
from avepay.mcf import AsyncAvePay

async def main():
    async with AsyncAvePay(api_key="ak_live_…", isf="1") as avepay:
        receipt = await avepay.mcf("EL02000015-1").certify(number="FV-1", ...)
        print(receipt.sig)

asyncio.run(main())
```

## Webhooks

```python
event = avepay.webhooks.verify(raw_body, request.headers.get("X-Avepay-Signature"))
# event.type: "cert.issued" | "mcf.connected" | "mcf.disconnected"
```

Le secret est passé à `verify(body, sig, secret)` ou au client (`AvePay(webhook_secret=…)`).

## URL de l'API
Priorité : **`base_url=` explicite > variable d'env `AVEPAY_BASE_URL` > défaut `https://api-mcf-orchestrator.toolsite.io`**. Au changement de domaine, il suffit de définir `AVEPAY_BASE_URL` (pas de nouvelle version).

## Développement

```bash
python -m venv .venv && . .venv/bin/activate
pip install -e ".[dev]"
pytest          # tests contre tests/fixtures/ (httpx MockTransport)
```
