# Monedero, recargas, códigos y facturas

El dinero en Zaroz son dos cosas: **facturas** (lo que debes) y un **monedero** (crédito que tienes). Cómo una compra se convierte en factura está en [Suscripciones y facturación](/es/concepts/billing). Esta página es la parte del dinero, llamada a llamada. Los ejemplos usan el ayudante de [Usar la API de Zaroz](/es/guides/api/overview). Todos los importes son **céntimos** enteros en la moneda de la factura.

## Leer el monedero

El monedero es de la organización, y todos sus miembros leen el mismo saldo. El tuyo es el de tu organización personal.

**curl**

```bash
curl -sS "$ZAROZ_API/me/wallet" -H "$AUTH"
curl -sS "$ZAROZ_API/organizations/$ORG/wallet" -H "$AUTH"
curl -sS "$ZAROZ_API/me/wallet/entries?page=1&page_size=25" -H "$AUTH"      # el libro, lo más nuevo primero
```

```json
{
  "balances": [
    { "currency": "EUR", "amount_cents": 3000, "real_cents": 2500, "promo_cents": 500,
      "legacy_cents": 0, "spendable_cents": 2500 }
  ]
}
```

**TypeScript**

```ts
import { call } from "./zaroz";

const wallet = await call("GET", "/me/wallet");
for (const b of wallet.body.balances) {
  console.log(b.currency, "gastable", b.spendable_cents / 100, "promo", b.promo_cents / 100);
}
const ledger = await call("GET", "/me/wallet/entries?page_size=25");
for (const e of ledger.body.data) console.log(e.created_at, e.reason, e.amount_cents);
```

**Python**

```python
from zaroz import call

_, wallet = call("GET", "/me/wallet")
for b in wallet["balances"]:
    print(b["currency"], "gastable", b["spendable_cents"] / 100, "promo", b["promo_cents"] / 100)
_, ledger = call("GET", "/me/wallet/entries?page_size=25")
for e in ledger["data"]:
    print(e["created_at"], e["reason"], e["amount_cents"])
```

Solo se devuelven los saldos distintos de cero, así que un monedero que nunca se recargó responde una lista `balances` vacía. Trata una moneda ausente como cero.

### Qué significan las cifras

| Campo | Significado |
|---|---|
| `spendable_cents` | **Lo que un pago con monedero puede usar ahora mismo.** Úsalo para decidir si una factura se puede pagar, no `amount_cents` |
| `real_cents` | Dinero equivalente a efectivo: recargas, reembolsos liquidados al monedero |
| `promo_cents` | **Crédito promocional.** Nunca es efectivo y no se puede retirar. No se gasta como efectivo en un pago: se aplica automáticamente como línea de descuento al emitir una factura (y al pagar con el monedero una factura sin pagar) |
| `legacy_cents` | Dinero antiguo aún sin clasificar; un pago con monedero no puede gastarlo |
| `amount_cents` | Todo lo anterior sumado. Solo para conciliar |

El libro no tiene columna de saldo acumulado: cada entrada es un movimiento con signo (positivo acredita, negativo debita) con un `reason` como `top_up_pay_pal`, `redeemed_code`, `invoice_autopay`, `invoice_refund` o `promo_grant`.

## Añadir fondos

Primero pregunta qué proveedores pueden aceptar una recarga ahora y muestra solo esos:

**curl**

```bash
curl -sS "$ZAROZ_API/me/wallet/top-ups/availability" -H "$AUTH"
# {"paypal": true, "nowpayments": false}

curl -sS -X POST "$ZAROZ_API/me/wallet/top-ups" -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"amount_cents": 2500, "provider": "paypal"}'
# {"id": "...", "status": "waiting", "checkout_url": "https://...", "expires_at": "..."}

curl -sS "$ZAROZ_API/me/wallet/top-ups/$TOPUP" -H "$AUTH"      # consulta hasta "credited"
```

**TypeScript**

```ts
const topup = await call("POST", "/me/wallet/top-ups", { amount_cents: 2500, provider: "paypal" });
console.log("Envía al cliente a", topup.body.checkout_url);

// no se acredita nada hasta que el proveedor confirma; consulta
for (;;) {
  const t = await call("GET", `/me/wallet/top-ups/${topup.body.id}`);
  if (["credited", "expired", "failed", "review_required", "reversed"].includes(t.body.status)) {
    console.log(t.body.status);
    break;
  }
  await new Promise((r) => setTimeout(r, 5000));
}
```

**Python**

```python
import time
from zaroz import call

_, topup = call("POST", "/me/wallet/top-ups", {"amount_cents": 2500, "provider": "paypal"})
print("Envía al cliente a", topup["checkout_url"])
while True:  # no se acredita nada hasta que el proveedor confirma
    _, t = call("GET", f"/me/wallet/top-ups/{topup['id']}")
    if t["status"] in ("credited", "expired", "failed", "review_required", "reversed"):
        print(t["status"])
        break
    time.sleep(5)
```

- `provider` es `paypal` o `nowpayments` (criptomonedas).
- `amount_cents` debe ser uno de los importes fijos en EUR: `2500`, `5000`, `10000`, `25000`, `100000`. Cualquier otro se rechaza con `invalid-field-value`.
- **La llamada no acredita nada.** El monedero se acredita una vez, cuando el proveedor confirma el pago. Solo `credited` significa que llegó el dinero; `expired`, `failed`, `review_required` y `reversed` son terminales.
- Reintentar una recarga tras un fallo de tiempo inicia un segundo pago independiente. Ninguno mueve dinero hasta que se paga.
- Un proveedor sin configurar o que no responde da `503` y no se cobró nada.
- Un token de organización necesita `organizations.wallet.top_up` para esto.

## Canjear un código de monedero

Un código de monedero (un regalo o sorteo) acredita un monedero, una sola vez. `preview` no necesita cuenta, de modo que un enlace de reclamación puede mostrar lo que vale el código antes de iniciar sesión.

**curl**

```bash
curl -sS "$ZAROZ_API/public/wallet/codes/ABCD-EFGH/preview"
# {"amount_cents": 1000, "currency": "EUR", "expires_at": null}

curl -sS -X POST "$ZAROZ_API/me/wallet/codes/redeem" -H "$AUTH" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" -d '{"code": "ABCD-EFGH"}'
# {"amount_cents": 1000, "currency": "EUR", "entry_id": "..."}
```

**TypeScript**

```ts
import { randomUUID } from "node:crypto";

const redeemed = await call("POST", "/me/wallet/codes/redeem", { code: "ABCD-EFGH" }, {
  "Idempotency-Key": randomUUID(),
});
console.log(redeemed.body.amount_cents, redeemed.body.currency);
```

**Python**

```python
import uuid
from zaroz import call

status, redeemed = call("POST", "/me/wallet/codes/redeem", {"code": "ABCD-EFGH"},
                        headers={"Idempotency-Key": str(uuid.uuid4())})
print(redeemed)
```

Sea cual sea el motivo del fallo (no existe, ya se usó, se revocó, caducó o se alcanzó un límite por persona), la respuesta es la misma `invalid-field-value`. Es deliberado: impide sondear qué códigos existen. Envía siempre un `Idempotency-Key`, para que un reintento tras un fallo de tiempo devuelva el primer crédito en lugar de un rechazo.

## Facturas

**curl**

```bash
curl -sS "$ZAROZ_API/billing/invoices?page=1&page_size=25" -H "$AUTH"            # todas las organizaciones que ves
curl -sS "$ZAROZ_API/organizations/$ORG/invoices?status=pending" -H "$AUTH"
curl -sS "$ZAROZ_API/billing/invoices/$INVOICE" -H "$AUTH"                       # con sus líneas
curl -sS "$ZAROZ_API/billing/invoices/open-summary" -H "$AUTH"                   # cuántas, vencidas, total adeudado
curl -sS "$ZAROZ_API/subscriptions/$SUBSCRIPTION/invoices" -H "$AUTH"
```

**TypeScript**

```ts
const open = await call("GET", "/billing/invoices/open-summary");
console.log(`${open.body.count} abiertas, ${open.body.overdue_count} vencidas, total ${open.body.total_cents}`);

const invoice = await call("GET", `/billing/invoices/${INVOICE}`);
for (const line of invoice.body.lines) console.log(line.description, line.amount_cents, line.project_name);
```

**Python**

```python
_, open_ = call("GET", "/billing/invoices/open-summary")
print(open_["count"], "abiertas,", open_["overdue_count"], "vencidas, total", open_["total_cents"])

_, invoice = call("GET", f"/billing/invoices/{INVOICE}")
for line in invoice["lines"]:
    print(line["description"], line["amount_cents"], line["project_name"])
```

El `status` de una factura es `pending`, `paid`, `failed`, `refunded` o `cancelled`; su `kind` es `initial`, `renewal`, `resize`, `admin_resize` o `trial_purchase`. `total_amount_cents` incluye impuestos (`tax_amount_cents`, `tax_rate`, `tax_label`). Todos los miembros de la organización pueden leer sus facturas; pagar requiere el rol Owner, Admin o Billing.

## Pagar una factura

Tres formas de liquidar una factura `pending`.

**Con el monedero.** Es inmediato y necesita que `spendable_cents` cubra la factura:

**curl**

```bash
curl -sS -X POST "$ZAROZ_API/billing/invoices/$INVOICE/pay-with-wallet" -H "$AUTH" \
  -H "Idempotency-Key: $(uuidgen)"
# {"entry_id": "..."}
```

**TypeScript**

```ts
const paid = await call("POST", `/billing/invoices/${INVOICE}/pay-with-wallet`, undefined, {
  "Idempotency-Key": randomUUID(),
});
if (paid.status === 422) console.log("saldo gastable insuficiente, o ya pagada");
```

**Python**

```python
status, paid = call("POST", f"/billing/invoices/{INVOICE}/pay-with-wallet",
                    headers={"Idempotency-Key": str(uuid.uuid4())})
if status == 422:
    print("saldo gastable insuficiente, o ya pagada")
```

Un monedero que no cubre la factura da `422` indicando el saldo y el importe necesario (no se intentó nada). Una factura ya pagada también da `422`. Un pago con tarjeta o PayPal en curso responde `409`; no se cobró nada. Pagar es la única llamada donde un reintento a ciegas podría costar dinero real dos veces, por eso importa el `Idempotency-Key`. Un token de organización necesita `organizations.wallet.spend`.

**Con tarjeta (Mollie) o PayPal.** Ambos devuelven una página alojada a la que enviar al cliente:

```bash
curl -sS -X POST "$ZAROZ_API/billing/invoices/$INVOICE/checkout/mollie" -H "$AUTH"
curl -sS -X POST "$ZAROZ_API/billing/invoices/$INVOICE/checkout/paypal" -H "$AUTH"
# {"checkout_url": "https://..."}
```

```ts
const { body } = await call("POST", `/billing/invoices/${INVOICE}/checkout/paypal`);
console.log("Abre", body.checkout_url);
```

```python
_, body = call("POST", f"/billing/invoices/{INVOICE}/checkout/paypal")
print("Abre", body["checkout_url"])
```

Cuando el cliente termina, el proveedor lo devuelve al panel; la factura pasa a `paid` cuando el proveedor lo confirma. Consulta `GET /billing/invoices/{id}` en lugar de fiarte de la redirección. Comprueba antes [qué ofrece el despliegue](/es/guides/api/overview#qué-ofrece-un-despliegue) (`mollie`, `paypal`): un proveedor sin configurar responde `501 capability-not-available`.

## Pago automático

Por defecto el monedero paga solo tus facturas de renovación cuando tiene saldo suficiente. Desactívalo si prefieres aprobar cada pago:

**curl**

```bash
curl -sS "$ZAROZ_API/me/wallet/preferences" -H "$AUTH"
# {"autopay_enabled": true, "preferred_currency": "EUR"}
curl -sS -X PATCH "$ZAROZ_API/me/wallet/preferences" -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"autopay_enabled": false}'
```

**TypeScript**

```ts
await call("PATCH", "/me/wallet/preferences", { autopay_enabled: false });
```

**Python**

```python
call("PATCH", "/me/wallet/preferences", {"autopay_enabled": False})
```

**Activar** el pago automático autoriza a la plataforma a gastar el monedero en cada renovación, por lo que requiere la autoridad de gasto (Owner o Admin), no solo el derecho a recargar.

## Si no pagas

Una factura vencida no desaparece. El [estado](/es/concepts/trust) de tu organización lo refleja y puede restringir compras nuevas; `GET /organizations/{id}/standing` te dice si estás restringido y, a los propietarios, por qué.
