Ir al contenido

Registrar ventas y recibir pagos

La venta que entra por la API sigue el mismo camino de la pantalla de ventas: recibe el número de la empresa, cuenta en la cuota del plan, arma las cuotas, registra en finanzas y descuenta el stock de los productos del catálogo. Esta guía registra una venta en cuotas y después da de baja una cuota.

PermisoPara qué
sales:createRegistrar la venta
sales:updateCobrar, anular cobros, reprogramar, cancelar y crear una versión nueva
sales:readLeer la venta después
finance:createSolo cuando la llamada pide el registro en finanzas: track_in_finance: true o una cuenta en payment_plan.account_id
finance:readPara elegir la cuenta en payment_plan.account_id, y para ver la cuenta y los movimientos de la venta en la respuesta

Registrar un cobro pide solo el permiso de ventas, aun con la venta en finanzas: el movimiento es consecuencia de la venta, como en la pantalla.

El acceso del miembro a finanzas también cuenta. Enviar el campo track_in_finance, con true o con false, y elegir la cuenta en payment_plan.account_id exigen que el miembro que la clave representa tenga acceso a finanzas en el panel; sin él, la respuesta es 403 member_permission_denied.

Toda venta lleva un payment_plan, y siempre tiene los seis campos, aunque algunos sean null:

CampoQué es
modepaid_full, entry_and_rest, unpaid o installments
payment_methodpix, credit_card, debit_card, cash, boleto, transfer, other o null
account_idLa cuenta que recibe, o null para la cuenta predeterminada de la empresa
paid_atFecha del pago al contado o del anticipo (YYYY-MM-DD), o null
entry_amount_centsMonto del anticipo, en el modo entry_and_rest; null en los demás
installmentsLas cuotas, cada una con amount_cents y due_date; null en paid_full

Los cuatro modos son los de la pantalla:

  • paid_full: pagado al contado. La venta nace pagada.
  • entry_and_rest: un anticipo pagado ahora y el resto en cuotas. El anticipo tiene que ser mayor que cero y menor que el total, y las cuotas suman lo que falta.
  • installments: en cuotas, sin nada pagado todavía. Las cuotas suman el total.
  • unpaid: a cobrar, fiado. Exige client_id, y las cuotas suman el total.

La suma de las cuotas tiene que cerrar con el monto. Una diferencia de hasta 1 centavo se ajusta en la última cuota; más que eso responde 422. El total se calcula a partir de los ítems, el descuento, el flete y los costos adicionales, como lo calcula el formulario; usted no envía el total.

Registrar la venta y cobrar la primera cuota

Sección titulada «Registrar la venta y cobrar la primera cuota»
Ventana de terminal
# 1. Venta de R$ 1.500,00 en 3 cuotas con boleto
# Genere la clave una vez y guárdela: en un reintento, repita con el mismo valor.
SALE_KEY=$(uuidgen)
curl -X POST "https://api.fatureihoje.com/public/v1/sales" \
-H "Authorization: Bearer $FH_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $SALE_KEY" \
-d '{
"client_id": "0d9a7c5e-3f1b-4d2a-8c6e-4a2b0c8e6f1d",
"items": [
{ "type": "custom", "name": "Instalación de 4 cámaras", "quantity": 1, "unit_price_cents": 150000 }
],
"payment_plan": {
"mode": "installments",
"payment_method": "boleto",
"account_id": null,
"paid_at": null,
"entry_amount_cents": null,
"installments": [
{ "amount_cents": 50000, "due_date": "2026-10-10" },
{ "amount_cents": 50000, "due_date": "2026-11-10" },
{ "amount_cents": 50000, "due_date": "2026-12-10" }
]
}
}'
# 2. Se pagó la primera cuota. Use el id de la venta y el id de la cuota
# que vinieron en la respuesta de arriba.
SALE_ID="3c5e7a9b-1d3f-4b5d-8f1a-3c5e7a9b1d3f"
INSTALLMENT_ID="inst-1"
# Genere la clave una vez y guárdela: en un reintento, repita con el mismo valor.
PAYMENT_KEY=$(uuidgen)
curl -X POST "https://api.fatureihoje.com/public/v1/sales/$SALE_ID/payments" \
-H "Authorization: Bearer $FH_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $PAYMENT_KEY" \
-d "{
\"amount_cents\": 50000,
\"paid_at\": \"2026-10-09\",
\"payment_method\": \"pix\",
\"installment_id\": \"$INSTALLMENT_ID\"
}"

Los campos de la venta, del ítem y de la respuesta están en la Referencia. Tres puntos del ejemplo:

  • El ítem custom es suelto y lleva el nombre que usted envía. El ítem catalog apunta a un producto registrado (product_id, y variation_id cuando el producto tiene variaciones), y en ese caso el nombre y el precio promocional vienen del registro.
  • La venta responde con installments, cada cuota con su id. Ese id va en installment_id al cobrar, y en la reprogramación.
  • payment_status pasa de pending a partial y después a paid a medida que entran los cobros, y paid_cents suma lo que ya se cobró.

POST /public/v1/sales/{id}/payments es la ventana Cobrar de la pantalla:

  • paid_at es la fecha en que entró el dinero (YYYY-MM-DD), y es obligatorio.
  • El monto no puede pasar del saldo de la venta.
  • Con installment_id, el monto salda esa cuota, que tiene que estar abierta. Sin él, el monto entra por orden de vencimiento.
  • account_id elige la cuenta que recibió, y pide finance:read y finance:create en la clave, además del acceso del miembro a finanzas. Sin él, se usa la cuenta de la venta.

Toda escritura lleva Idempotency-Key, y en el cobro pesa más. Un cobro repetido después de una falla de red, sin la clave, entra dos veces. Con la misma clave, la repetición devuelve la respuesta de la primera y no se graba nada de nuevo. Vea Idempotencia.

Un cobro equivocado se anula con POST /public/v1/sales/{id}/payments/{payment_id}/cancel, con el motivo en reason. Para cambiar montos y vencimientos de las cuotas abiertas, use PATCH /public/v1/sales/{id}/installments, que es la ventana Reprogramar de la pantalla.

  • Finanzas. Sin el campo track_in_finance, vale la configuración de la empresa, y ese registro automático no pide permiso de finanzas. Enviar el campo, con cualquier valor, exige que el miembro tenga acceso a finanzas; true exige además finance:create en la clave, y false no. Elegir la cuenta en payment_plan.account_id exige finance:read y finance:create en la clave, además del acceso del miembro.
  • Lo que usted ve. account_id y finance_transactions solo vienen en la respuesta cuando la clave tiene finance:read y su miembro tiene acceso a finanzas. Sin eso, la venta viene sin esos dos campos, como en el panel. Las cuotas y los cobros vienen siempre.
  • Stock. decrement_stock nace true, como en la pantalla, y descuenta el stock de los ítems del catálogo sin pedir products:update. Envíe false cuando el stock se controla en otro lugar.

Una venta no se elimina, tampoco en el panel.

  • Un cambio comercial (ítems, montos, cliente, vendedor, plan del saldo) es POST /public/v1/sales/{id}/versions, con el motivo en reason. El id de la venta no cambia, y version sube. Lo ya cobrado sigue valiendo. GET /public/v1/sales/{id}/versions trae el historial.
  • Observaciones y producción cambian con un PATCH /public/v1/sales/{id}, sin versión nueva.
  • Cancelar es POST /public/v1/sales/{id}/cancel, con el motivo. Anula los cobros, devuelve el stock descontado y cancela los movimientos.

Las trabas son las de la pantalla, acción por acción: una venta cancelada, con factura activa, con la comisión ya pagada o generada por un contrato rechaza algunas acciones con 409. La tabla completa está en la Visión general.

RespuestaQué hacer
422 validation_failedCorrija el campo indicado en errors, como cuotas que no cierran con el total
409 conflictEl estado de la venta no lo permite: cancelada, con factura activa, con comisión pagada o de contrato
403 permission_missingLe falta un permiso a la clave, como finance:create con track_in_finance: true
403 member_permission_deniedEl miembro de la clave no tiene acceso a finanzas y la llamada toca finanzas
429 rate_limit_exceededEspere el Retry-After y repita. Vea Límites de uso
5xx o falla de redRepita con la misma Idempotency-Key

Para que su sistema sepa que la venta se pagó, incluso cuando el cobro se registró en el panel, suscriba un endpoint a sale.payment_registered y sale.paid. Los eventos de venta nunca llevan la cuenta ni los movimientos; esos tienen sus propios eventos financial_transaction.*. Vea el Catálogo de eventos.