# Exemplos por linguagem

> Cliente HTTP mínimo pronto para copiar em PHP, Node.js e Python, com timeout e repetição segura.

Fonte: https://app.thronecorporate.com/docs/sdks

## Não existe pacote para instalar

A API é REST com JSON: qualquer cliente HTTP serve. O Throne **não publica pacote** no npm, Packagist, PyPI ou outro repositório. Pacotes com nomes parecidos com "throne" não são nossos: não instale.

Abaixo há um cliente mínimo, pronto para copiar, em cada linguagem. Ele já:

- monta a autenticação e manda `Accept: application/json`;
- usa timeout (20 segundos por padrão; passe 60 segundos na criação de checkout de cartão);
- aceita uma `Idempotency-Key` sua nos `POST`, para você repetir com a mesma chave depois de um timeout;
- transforma erros em exceção com o código `error`, o status, o `retryable` e o `Retry-After`. Sem resposta (falha de conexão ou timeout), o status é `0`.

```php PHP
final class ThroneError extends RuntimeException
{
    public function __construct(
        public readonly int $status,          // 0 = sem resposta (conexão caiu ou timeout)
        public readonly string $error,
        string $message,
        public readonly ?bool $retryable = null,
        public readonly ?int $retryAfter = null,
    ) {
        parent::__construct("{$error}: {$message}", $status);
    }
}

final class Throne
{
    public function __construct(
        private string $pk,
        private string $sk,
        private string $base = 'https://app.thronecorporate.com/api/v1',
    ) {}

    public function get(string $path, int $timeout = 20): array
    {
        return $this->request('GET', $path, null, null, $timeout);
    }

    public function post(string $path, array $body, ?string $idempotencyKey = null, int $timeout = 20): array
    {
        return $this->request('POST', $path, $body, $idempotencyKey ?? bin2hex(random_bytes(16)), $timeout);
    }

    private function request(string $method, string $path, ?array $body, ?string $key, int $timeout): array
    {
        $headers = [
            "Authorization: Bearer {$this->pk}:{$this->sk}",
            'Accept: application/json',
            'Content-Type: application/json',
        ];
        if ($key !== null) {
            $headers[] = "Idempotency-Key: {$key}";
        }

        $retryAfter = null;
        $ch = curl_init($this->base . $path);
        curl_setopt_array($ch, [
            CURLOPT_CUSTOMREQUEST => $method,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_CONNECTTIMEOUT => 10,
            CURLOPT_TIMEOUT => $timeout,
            CURLOPT_HEADERFUNCTION => function ($ch, string $line) use (&$retryAfter): int {
                if (stripos($line, 'Retry-After:') === 0) {
                    $retryAfter = (int) trim(substr($line, 12));
                }
                return strlen($line);
            },
        ]);
        if ($body !== null) {
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
        }

        $raw = curl_exec($ch);
        if ($raw === false) {
            // Sem resposta: repita com a MESMA Idempotency-Key.
            throw new ThroneError(0, 'network', curl_error($ch));
        }
        $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $json = json_decode((string) $raw, true) ?? [];

        if ($status >= 400) {
            throw new ThroneError(
                $status,
                $json['error'] ?? 'http_' . $status,
                $json['message'] ?? '',
                $json['retryable'] ?? null,
                $retryAfter,
            );
        }

        return $json;
    }
}

$throne = new Throne(getenv('THRONE_PK'), getenv('THRONE_SK'));

// Pix
$pix = $throne->post('/transactions', [
    'payment_method' => 'pix',
    'amount_cents' => 4990,
    'customer' => ['name' => 'Maria Souza', 'document' => '12345678909', 'email' => 'maria@exemplo.com'],
]);

// Cartão: guarde a chave junto do pedido antes de chamar; 60 segundos de timeout.
$chave = bin2hex(random_bytes(16));
$checkout = $throne->post('/elements/checkout', ['amount_cents' => 4990, 'currency' => 'usd'], $chave, 60);
```
```js Node.js
import { randomUUID } from 'node:crypto';

export class ThroneError extends Error {
  constructor(status, code, message, retryable = null, retryAfter = null) {
    super(`${code}: ${message}`);
    this.status = status; // 0 = sem resposta (conexão caiu ou timeout)
    this.code = code;
    this.retryable = retryable;
    this.retryAfter = retryAfter;
  }
}

export function throneClient({ pk, sk, base = 'https://app.thronecorporate.com/api/v1' }) {
  async function request(method, path, body, idempotencyKey, timeoutMs = 20000) {
    const headers = {
      Authorization: `Bearer ${pk}:${sk}`,
      Accept: 'application/json',
      'Content-Type': 'application/json',
    };
    if (method === 'POST') headers['Idempotency-Key'] = idempotencyKey ?? randomUUID();

    let res;
    try {
      res = await fetch(base + path, {
        method,
        headers,
        body: body ? JSON.stringify(body) : undefined,
        signal: AbortSignal.timeout(timeoutMs),
      });
    } catch (cause) {
      // sem resposta: repita com a MESMA Idempotency-Key
      throw new ThroneError(0, 'network', cause.message);
    }

    const json = await res.json().catch(() => ({}));
    if (!res.ok) {
      throw new ThroneError(
        res.status,
        json.error ?? `http_${res.status}`,
        json.message ?? '',
        json.retryable ?? null,
        Number(res.headers.get('Retry-After')) || null,
      );
    }
    return json;
  }

  return {
    get: (path, timeoutMs) => request('GET', path, undefined, undefined, timeoutMs),
    post: (path, body, idempotencyKey, timeoutMs) => request('POST', path, body, idempotencyKey, timeoutMs),
  };
}

const throne = throneClient({ pk: process.env.THRONE_PK, sk: process.env.THRONE_SK });

// Cartão: guarde a chave junto do pedido antes de chamar; 60 segundos de timeout.
const chave = randomUUID();
const checkout = await throne.post('/elements/checkout', { amount_cents: 4990, currency: 'usd' }, chave, 60000);
```
```python Python
import uuid
import requests


class ThroneError(Exception):
    def __init__(self, status, code, message, retryable=None, retry_after=None):
        super().__init__(f"{code}: {message}")
        self.status = status  # 0 = sem resposta (conexão caiu ou timeout)
        self.code = code
        self.retryable = retryable
        self.retry_after = retry_after


class Throne:
    def __init__(self, pk, sk, base="https://app.thronecorporate.com/api/v1"):
        self.base = base
        self.session = requests.Session()
        self.session.headers.update({
            "Authorization": f"Bearer {pk}:{sk}",
            "Accept": "application/json",
        })

    def _request(self, method, path, body=None, idempotency_key=None, timeout=20):
        headers = {}
        if method == "POST":
            headers["Idempotency-Key"] = idempotency_key or str(uuid.uuid4())
        try:
            r = self.session.request(method, self.base + path, json=body, headers=headers, timeout=timeout)
        except requests.RequestException as e:
            # sem resposta: repita com a MESMA Idempotency-Key
            raise ThroneError(0, "network", str(e))
        try:
            data = r.json()
        except ValueError:
            data = {}
        if r.status_code >= 400:
            retry_after = r.headers.get("Retry-After")
            raise ThroneError(
                r.status_code,
                data.get("error", f"http_{r.status_code}"),
                data.get("message", ""),
                data.get("retryable"),
                int(retry_after) if retry_after and retry_after.isdigit() else None,
            )
        return data

    def get(self, path, timeout=20):
        return self._request("GET", path, timeout=timeout)

    def post(self, path, body, idempotency_key=None, timeout=20):
        return self._request("POST", path, body, idempotency_key, timeout)


throne = Throne(pk="pk_live_...", sk="sk_live_...")
saldo = throne.get("/balance")

# Cartão: guarde a chave junto do pedido antes de chamar; 60 segundos de timeout.
chave = str(uuid.uuid4())
checkout = throne.post("/elements/checkout", {"amount_cents": 4990, "currency": "usd"}, chave, timeout=60)
```

## Repetir com segurança

Com os clientes acima, a decisão fica assim:

| A exceção traz | O que fazer |
|---|---|
| `status` 0 (sem resposta) | Repita **com a mesma** `Idempotency-Key` e o mesmo corpo. Se a primeira chegou, você recebe a resposta original, sem duplicar nada |
| `502`, `503` ou `504` com código começando em `http_` | Página de erro do servidor, sem o JSON da API. Trate como sem resposta: repita com a mesma chave |
| `409 idempotency_key_processing` | Espere alguns segundos e repita com a mesma chave. Se continuar por mais de 2 minutos, confira no painel se algo foi criado antes de usar uma chave nova |
| `429`, ou `503` com o código da API | Espere o `retryAfter` e repita com uma chave **nova**, poucas vezes, aumentando a espera ([recuo](/docs/limites#quando-a-resposta-e-429-ou-503)) |
| `502` com o código da API e `retryable` verdadeiro ou ausente (o `502` do Pix não traz `retryable`) | Repita com uma chave **nova** |
| `502` com `retryable` falso | Não repita igual |
| `500` | Algo pode ter sido criado. Não use chave nova antes de conferir no painel. Em `POST /elements/checkout`, repita antes com a mesma chave: se nada foi criado, a chamada roda de novo |
| Qualquer outro `4xx` | Corrija o que for preciso e repita com uma chave **nova** |

Por isso os clientes aceitam uma `Idempotency-Key` sua: gere a chave antes, guarde junto do pedido e reuse na repetição. A tabela completa está em [como repetir com segurança](/docs/comecar#como-repetir-uma-chamada-com-seguranca).

## Validar webhooks

Os exemplos de validação da assinatura `X-Throne-Signature` em PHP, Node.js, Python e Go estão em [Webhooks](/docs/webhooks#validar-a-assinatura).
