Metadata-Version: 2.4
Name: dinie-sdk-sandbox
Version: 2.0.0
Summary: Official Python SDK for the Dinie V3 API
License: MIT
License-File: LICENSE
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Provides-Extra: dev
Requires-Dist: build; extra == 'dev'
Requires-Dist: mypy; extra == 'dev'
Requires-Dist: pytest; extra == 'dev'
Requires-Dist: pytest-httpx; extra == 'dev'
Requires-Dist: ruff; extra == 'dev'
Description-Content-Type: text/markdown

# dinie

Official Python SDK for the [Dinie](https://dinie.com.br) V3 API (backend-only).

A synchronous Python client for the Dinie credit-as-a-service platform: OAuth2
client-credentials auth (handled for you), automatic retries with idempotency, cursor
pagination, typed errors, and webhook verification. Snake_case throughout — the wire and
the Python surface match, so there is no casing to translate.

## Requirements

- Python >= 3.10
- A Dinie API credential (`client_id` + `client_secret`)

## Installation

```bash
# Sandbox (published as dinie-sdk-sandbox on PyPI):
pip install dinie-sdk-sandbox

# Production (once the real SDK ships):
# pip install dinie-sdk
```

The import is always `from dinie import Dinie` — the PyPI package name and the module
name are independent.

## Quickstart

```python
from dinie import Dinie
from dinie.generated.types.create_customer_request import CreateCustomerRequest

client = Dinie(client_id="dinie_ci_…", client_secret="…")

customer = client.customers.create(
    CreateCustomerRequest(
        cpf="123.456.789-09",
        cnpj="12.345.678/0001-90",
        email="ana@example.com",
        phone="+5511999999999",
    )
)
print(customer.id)      # "cust_…"
print(customer.status)  # "pending_kyc"
```

The `Dinie` client owns an in-memory OAuth2 token cache, so **construct it once and
reuse it**. Tokens are fetched and refreshed transparently — you never handle bearer
tokens manually.

## OAuth2 Client Credentials

The SDK uses the OAuth2 `client_credentials` grant. On the first request it POSTs to
`/auth/token` with your `client_id` and `client_secret`, caches the bearer token for
its lifetime, and automatically refreshes when it expires. A single-flight lock ensures
only one refresh runs at a time under concurrent access.

You can also pass credentials via environment variables instead of constructor arguments:

```bash
export DINIE_CLIENT_ID=dinie_ci_…
export DINIE_CLIENT_SECRET=…
```

Then construct the client with no arguments — it reads `DINIE_CLIENT_ID` and
`DINIE_CLIENT_SECRET` from the environment automatically:

```bash
# Instantiate with environment variables (no explicit credentials):
#   client = Dinie()
```

## Customer → Offer → Loan Flow

The full origination flow: register a customer, wait for KYC to pass (received via
webhook), then check the credit offer and create a loan.

```python
from dinie import Dinie
from dinie.generated.types.create_customer_request import CreateCustomerRequest
from dinie.generated.types.create_simulation_request import CreateSimulationRequest
from dinie.generated.types.create_loan_request import CreateLoanRequest

client = Dinie(client_id="dinie_ci_…", client_secret="…")

# 1. Register a customer
customer = client.customers.create(
    CreateCustomerRequest(
        cpf="123.456.789-09",
        cnpj="12.345.678/0001-90",
        email="ana@example.com",
        phone="+5511999999999",
    )
)

# 2. List credit offers (available once KYC is approved and status == "active")
offers_page = client.customers.credit_offers.list(customer.id)
offer = next(iter(offers_page))

# 3. Simulate a loan against the offer
simulation = client.credit_offers.create_simulation(
    offer.id,
    CreateSimulationRequest(
        installment_count=12,
        requested_amount=5000.0,
    ),
)

# 4. Create the loan from the accepted simulation
loan = client.loans.create(
    CreateLoanRequest(
        credit_offer_id=offer.id,
        simulation_id=simulation.id,
        installment_count=simulation.installment_count,
        installment_amount=simulation.installment_amount,
        first_due_date="2026-07-01",
    )
)

print(loan.id)          # "loan_…"
print(loan.status)      # "awaiting_signatures"
print(loan.signing_url) # CCB e-signature URL for the customer
```

## Webhooks

Register an endpoint and handle incoming events in your backend:

```bash
# Register your webhook URL once:
# client.webhook_endpoints.create(...)
```

Verify and parse the webhook payload in your handler:

```python
from dinie.webhooks import extract

def handle_webhook(payload: bytes, headers: dict) -> None:  # type: ignore[type-arg]
    event = extract(payload, headers)
    match event.event_type:
        case "customer.active":
            # customer KYC approved — check for credit offers
            pass
        case "credit_offer.available":
            # new credit offer is ready
            pass
        case "loan.active":
            # loan disbursed
            pass
```

## Errors

All API errors subclass `dinie.ApiError`:

```
ApiError
├── AuthError             (401)
├── PermissionDeniedError (403)
├── NotFoundError         (404)
├── ConflictError         (409)
├── ValidationError       (422)
├── RateLimitError        (429)
└── ServerError           (500+)
```

Catch errors in your application like any Python exception:

```bash
# Pattern (not executable in isolation — illustrative):
# try:
#     customer = client.customers.retrieve("cust_unknown")
# except dinie.NotFoundError:
#     print("customer not found")
```

## Development

```bash
pip install -e ".[dev]"
pytest tests/
ruff check dinie/ tests/
ruff format --check dinie/ tests/
mypy --strict dinie/
```

## Architecture

```
dinie/
  runtime/    # hand-written: HTTP, auth, retries, pagination, errors, webhooks
  generated/  # spec-driven: client, resources, types, events (emitted by sdk-generator)
```

The `runtime/` layer is hand-written and owned by humans. The `generated/` layer is
emitted by `sdk-generator --target python` from the OpenAPI spec + `sdk-config.yml`.
Never hand-edit `generated/` — regenerate instead.

## License

MIT
