Zaroz
Facturación y monedero

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. Esta página es la parte del dinero, llamada a llamada. Los ejemplos usan el ayudante de Usar la API de Zaroz. 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

TerminalCode
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
JSONCode
{ "balances": [ { "currency": "EUR", "amount_cents": 3000, "real_cents": 2500, "promo_cents": 500, "legacy_cents": 0, "spendable_cents": 2500 } ] }

TypeScript

TypeScriptCode
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

Code
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

CampoSignificado
spendable_centsLo que un pago con monedero puede usar ahora mismo. Úsalo para decidir si una factura se puede pagar, no amount_cents
real_centsDinero equivalente a efectivo: recargas, reembolsos liquidados al monedero
promo_centsCré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_centsDinero antiguo aún sin clasificar; un pago con monedero no puede gastarlo
amount_centsTodo 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

TerminalCode
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

TypeScriptCode
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

Code
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

TerminalCode
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

TypeScriptCode
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

Code
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

TerminalCode
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

TypeScriptCode
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

Code
_, 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

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

TypeScript

TypeScriptCode
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

Code
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:

TerminalCode
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://..."}
TypeScriptCode
const { body } = await call("POST", `/billing/invoices/${INVOICE}/checkout/paypal`); console.log("Abre", body.checkout_url);
Code
_, 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 (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

TerminalCode
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

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

Python

Code
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 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é.

Last modified on