<div align="center">

# 🥑 abacapython

**SDK Python não-oficial para o gateway de pagamentos [AbacatePay](https://abacatepay.com)**

[![PyPI](https://img.shields.io/pypi/v/abacapython.svg)](https://pypi.org/project/abacapython/)
[![Python](https://img.shields.io/pypi/pyversions/abacapython.svg)](https://pypi.org/project/abacapython/)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![CI](https://github.com/nicollasrezende/abacapython/actions/workflows/ci.yml/badge.svg)](https://github.com/nicollasrezende/abacapython/actions/workflows/ci.yml)
[![Code style: ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)

</div>

> 📚 **Projeto feito para fins de estudo.**
> Sim, já existe um SDK Python oficial/anterior da AbacatePay — descobrimos depois 😅
> Esta biblioteca foi construída como exercício de engenharia: explorar design de SDK, sync+async com `httpx`, validação com Pydantic v2, verificação de webhook, packaging moderno, CI/CD com OIDC Trusted Publishing, MkDocs, etc. Se servir de referência para alguém estudando os mesmos temas, ótimo.
>
> ⚠️ **Biblioteca não-oficial, mantida pela comunidade, sem fins comerciais.**
> Todos os créditos da plataforma de pagamentos, dos produtos e da API REST são da [AbacatePay](https://abacatepay.com). Documentação oficial da API: https://docs.abacatepay.com.

---

## ✨ Features

- 🐍 **Python 3.10+** com type hints completos (compatível com `mypy --strict`)
- ⚡ **Sync e async** na mesma interface (`AbacaPay` e `AsyncAbacaPay`, ambos sobre `httpx`)
- 🧱 **Modelos Pydantic v2** com validação, alias automático (camelCase ↔ snake_case) e tolerância a campos novos
- 🛡️ **Hierarquia de erros tipada** por status HTTP (`AuthenticationError`, `NotFoundError`, `RateLimitError`, …)
- 🔁 **Retries com backoff exponencial + jitter** em `429` e `5xx`
- 🔐 **Verificação de webhook** (HMAC-SHA256, comparação constant-time)
- 📦 **Cobre 100% dos endpoints documentados**: Checkouts, Payment Links, Customers, Checkout Transparente (PIX), Products, Coupons, Webhooks, Subscriptions, Payouts, PIX Transfers, Store e TrustMRR
- 🧪 **Testado** com `pytest` + `respx`

## 📦 Instalação

```bash
pip install abacapython
```

Ou com `uv`:

```bash
uv add abacapython
```

## 🚀 Quickstart

### Síncrono

```python
from abacapython import AbacaPay

client = AbacaPay(api_key="abc_dev_...")  # ou via env: ABACATEPAY_API_KEY

# 1. Crie um produto
produto = client.products.create({
    "external_id": "plano-mensal",
    "name": "Plano Mensal",
    "price": 9990,           # R$ 99,90 em centavos
    "currency": "BRL",
})

# 2. Crie um checkout hospedado
checkout = client.checkouts.create({
    "items": [{"id": produto.id, "quantity": 1}],
    "methods": ["PIX", "CARD"],
    "completion_url": "https://meusite.com/obrigado",
})

print(checkout.url)  # redirecione o cliente para esta URL
```

### Assíncrono

```python
import asyncio
from abacapython import AsyncAbacaPay

async def main() -> None:
    async with AsyncAbacaPay(api_key="abc_dev_...") as client:
        loja = await client.store.get()
        print(loja.name)

asyncio.run(main())
```

### PIX Checkout Transparente (QR Code no seu site)

```python
pix = client.pix_qrcodes.create({
    "amount": 4990,                    # R$ 49,90
    "description": "Pedido #1234",
    "expires_in": 3600,                # 1 hora
    "customer": {"email": "alice@example.com", "tax_id": "12345678900"},
})

# Mostre estes para o cliente:
print(pix.br_code)         # "copia-e-cola"
print(pix.br_code_base64)  # imagem PNG do QR Code

# Mais tarde:
status = client.pix_qrcodes.check(id=pix.id)
if status.status == "PAID":
    ...
```

### Assinaturas (recorrência)

```python
from abacapython import CreateProductParams, ProductCycle

plano = client.products.create(CreateProductParams(
    external_id="premium",
    name="Premium Mensal",
    price=4990,
    currency="BRL",
    cycle=ProductCycle.MONTHLY,        # produto recorrente
))

sub = client.subscriptions.create({
    "items": [{"id": plano.id, "quantity": 1}],
    "completion_url": "https://meusite.com/bem-vindo",
})

print(sub.url)
```

### Webhooks

```python
import os
from flask import Flask, request, abort
from abacapython import webhooks

app = Flask(__name__)
SECRET = os.environ["ABACATEPAY_WEBHOOK_SECRET"]

@app.post("/abacatepay/webhook")
def receive():
    signature = webhooks.extract_signature(request.headers)
    try:
        event = webhooks.parse_event(request.data, signature, SECRET)
    except webhooks.WebhookSignatureError:
        abort(401)

    if event.event == "checkout.completed":
        # Marque pedido como pago...
        pass
    elif event.event == "transparent.completed":
        # PIX confirmado...
        pass
    return "", 204
```

### Tratamento de erros

```python
from abacapython.exceptions import (
    AuthenticationError, NotFoundError, RateLimitError, APIResponseError,
)

try:
    client.customers.get(id="cust_inexistente")
except NotFoundError:
    ...
except AuthenticationError:
    ...
except RateLimitError:
    ...
except APIResponseError as exc:
    # Fallback genérico
    print(exc.status_code, exc.message)
```

## ⚙️ Configuração

| Parâmetro          | Default                                | Descrição                                                                    |
| ------------------ | -------------------------------------- | ---------------------------------------------------------------------------- |
| `api_key`          | `os.environ["ABACATEPAY_API_KEY"]`     | Bearer Token da AbacatePay.                                                  |
| `base_url`         | `https://api.abacatepay.com/v2`        | Pode ser sobrescrito por `ABACATEPAY_BASE_URL`.                              |
| `timeout`          | `30.0`                                 | Timeout (s) para cada requisição.                                            |
| `max_retries`      | `2`                                    | Tentativas extras para `429`, `5xx`, timeouts e erros de conexão.            |
| `backoff_factor`   | `0.5`                                  | Fator base do backoff exponencial entre retries.                             |
| `backoff_max`      | `8.0`                                  | Limite (s) para qualquer espera entre retries.                               |
| `http_client`      | `None`                                 | Instância customizada de `httpx.Client` / `httpx.AsyncClient`.               |

## 🗺️ Mapa de endpoints

| Recurso na API           | Acesso no SDK                |
| ------------------------ | ---------------------------- |
| `/customers/*`           | `client.customers`           |
| `/products/*`            | `client.products`            |
| `/checkouts/*`           | `client.checkouts`           |
| `/payment-links/*`       | `client.payment_links`       |
| `/transparents/*` (PIX)  | `client.pix_qrcodes`         |
| `/coupons/*`             | `client.coupons`             |
| `/webhooks/*`            | `client.webhooks`            |
| `/subscriptions/*`       | `client.subscriptions`       |
| `/payouts/*`             | `client.payouts`             |
| `/pix/*` (transferência) | `client.pix_transfers`       |
| `/store/*`               | `client.store`               |
| `/trustMRR/*` (público)  | `client.trust_mrr`           |

## 🛣️ Compatibilidade

| Python | Status     |
| ------ | ---------- |
| 3.10   | ✅ testado |
| 3.11   | ✅ testado |
| 3.12   | ✅ testado |
| 3.13   | ✅ testado |

## 🤝 Como contribuir

Pull Requests são muito bem-vindos! Veja [CONTRIBUTING.md](CONTRIBUTING.md) para detalhes.

Em resumo:

```bash
git clone https://github.com/nicollasrezende/abacapython
cd abacapython
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev,docs]"
ruff check . && ruff format --check .
mypy
pytest
```

## 📜 Licença

[MIT](LICENSE). Faça bom proveito.

## 📖 Por que existe (TL;DR sincero)

Eu queria praticar:

- Design de SDK no estilo Stripe/Twilio (resources, modelos tipados, sync+async)
- `httpx` com retries + backoff exponencial + jitter
- Pydantic v2 com alias (`camelCase` ↔ `snake_case`) e validações declarativas
- Verificação HMAC de webhook com comparação constant-time
- Packaging moderno (`hatchling`, `pyproject.toml`, `py.typed`)
- CI/CD com matrix multi-Python e PyPI via **OIDC Trusted Publishing** (sem token no repo)
- MkDocs Material com `mkdocstrings` gerando referência da API
- Workflow open-source: CONTRIBUTING, CODE_OF_CONDUCT, SECURITY, issue/PR templates

A `abacapython` foi o pretexto para fazer tudo isso. **Já existe um SDK oficial da AbacatePay para Python**, então não use esta lib em produção — use a deles. Se este código te ajudou a aprender algum dos tópicos acima, missão cumprida 🥑

## 🙏 Créditos

- **[AbacatePay](https://abacatepay.com)** — gateway de pagamentos, API e produto. Esta biblioteca apenas consome a API REST pública deles, documentada em https://docs.abacatepay.com.
- Comunidade Python brasileira por inspirar projetos open-source de qualidade.

---

<sub>Esta biblioteca **não é afiliada à AbacatePay**. AbacatePay é marca registrada de seus respectivos titulares.</sub>
