Zoho
Zoho Books y Zoho Invoice (nube, API v3)
Las 2 a 3 horas del comercio son: juntar los datos del paso 1, crear el Self Client y generar el código (30 minutos si todo va bien), decidir con el contador la cuenta de "Banking" y el payment_mode (30 a 60 minutos, y suele ser lo que más se demora porque hay que esperar al contador), configurar la regla de flujo de trabajo saliente (30 minutos) y acompañar las pruebas (1 hora). Las 14 a 22 horas de desarrollo son: middleware con almacenamiento cifrado del refresh token y renovación automática (3 a 4 h), búsqueda de la factura por número y armado del customerpayment (3 a 4 h), endpoint público con validación HMAC, control de timestamp y respuesta rápida (3 a 4 h), proceso de respaldo por consulta con idempotencia (2 a 3 h), manejo de centros de datos, errores 401 y 429 con reintentos (2 h), y pruebas de punta a punta más despliegue (2 a 3 h). Súmale 4 a 6 horas si el comercio maneja multimoneda, porque entra exchange_rate y las cuentas de diferencia de cambio. Súmale otras 6 a 10 horas si en vez de Self Client tenés que hacer el flujo de "Server-based Applications" con pantalla de consentimiento para conectar varios comercios. Si alguien te promete esto en 4 horas, o ya lo tiene hecho de otro cliente, o no está contando el respaldo por consulta ni la validación de firma, que es exactamente donde después se pierde plata.
Quien lo hace
Trabajo compartido, y no hay camino 100% sin programador. El dueño o el administrador de la organización hace todo lo que pasa dentro de Zoho: crear el Self Client en el Zoho API Console, generar el código con los alcances, copiar el Client ID, el Client Secret y el organization_id. Eso son clics, no código, y le lleva un par de horas. El contador define dos cosas de negocio: contra qué cuenta de "Banking" se deposita el cobro y qué payment_mode se usa (normalmente "banktransfer" u "others"), porque de eso depende que la conciliación cierre. Pero la integración en sí necesita un socio implementador o un programador, sin excepción: Zoho Books y Zoho Invoice no pueden recibir el webhook firmado de ROKI ni validar la firma HMAC por sí solos, así que alguien tiene que levantar y mantener un middleware. Si te venden que esto se resuelve solo con Zoho Flow y cero código, desconfiá: podés recibir el aviso, pero no podés verificar la firma con garantías.
Que tiene que existir antes de empezar
Donde: "Settings" > "Organization Profile"
Los alcances OAuth y la URL base cambian: ZohoBooks.* contra https://www.zohoapis.com/books/v3, o ZohoInvoice.* contra https://www.zohoapis.com/invoice/v3. Si la organización tiene Books contratado, usá los alcances de Books aunque las pantallas de facturación se parezcan a Invoice. Copiar un alcance de Books en una organización de Invoice devuelve error de autorización y se pierden horas buscando dónde está la falla.
Donde: Menú desplegable con el nombre de la organización, arriba a la derecha > "Manage Organizations"
Va como parámetro de consulta en TODAS las llamadas a la API, sin excepción. También se puede obtener con GET /organizations.
Donde: La barra de direcciones del navegador estando dentro de Zoho: books.zoho.com, books.zoho.eu, books.zoho.in, books.zoho.com.au, books.zoho.jp, books.zoho.sa, books.zohocloud.ca
Define contra qué servidor de cuentas se generan los tokens y contra qué dominio se llama la API. Es la causa número uno de integraciones rotas con Zoho.
Donde: "Settings" > "Users & Roles" (no encontré este rótulo confirmado en la documentación oficial que leí; verificá cómo aparece en tu pantalla)
El refresh token hereda los permisos del usuario que lo generó. Si generás el token con la cuenta personal de un empleado y esa cuenta se desactiva cuando renuncia, la integración muere ese mismo día y nadie sabe por qué.
Donde: Menú lateral izquierdo, módulo "Banking"
Es el campo account_id del pago. Es opcional en la API, pero si no lo mandás Zoho asigna una cuenta por defecto y el contador se entera dos meses después, cuando no cuadra la conciliación. Definilo antes de la primera prueba.
Donde: Menú lateral izquierdo, "Sales" > "Invoices" (la configuración de numeración automática existe, pero no verifiqué su rótulo exacto en la documentación oficial)
ROKI devuelve el external_reference en el webhook y el middleware lo usa para encontrar la factura. La documentación de Zoho aclara que invoice_number tiene máximo 100 caracteres y debe ser único dentro de la organización. Si el comercio reusa numeración entre series, el pago se registra contra la factura equivocada.
Donde: No es una pantalla de Zoho: es el firewall del servidor del middleware
Toda la comunicación con Zoho es saliente sobre HTTPS. No hay que abrir ningún puerto entrante en la oficina del comercio, porque Zoho es nube y no hay nada instalado ahí. El único que necesita ser alcanzable desde internet es el middleware, para que ROKI le pegue el webhook.
Los pasos
- 1. Confirmar producto, centro de datos y organization_id antes de tocar nada
Entrá a la organización y anotá tres datos en un papel: (a) si es Books o Invoice, mirando "Settings" > "Organization Profile"; (b) el dominio que aparece en el navegador (books.zoho.com, books.zoho.eu, books.zoho.in, etc.); (c) el organization_id, desde el desplegable del nombre de la organización > "Manage Organizations". Con esos tres datos armás el par de URLs que vas a usar todo el resto de la guía. Ejemplo para Estados Unidos (.com): servidor de cuentas https://accounts.zoho.com y API https://www.zohoapis.com/books/v3. Para Europa: https://accounts.zoho.eu y https://www.zohoapis.eu/books/v3. Para India: https://accounts.zoho.in y https://www.zohoapis.in/books/v3. Australia: accounts.zoho.com.au y www.zohoapis.com.au. Japón: accounts.zoho.jp y www.zohoapis.jp. Arabia Saudita: accounts.zoho.sa y www.zohoapis.sa. Canadá es la excepción rara: el servidor de cuentas es https://accounts.zohocloud.ca (no accounts.zoho.ca) mientras la API es https://www.zohoapis.ca/books/v3. - 2. Crear el cliente en el Zoho API Console (y entender qué es el Self Client)
Con el usuario administrador abierto en el navegador, entrá a https://api-console.zoho.com/ y hacé clic en "GET STARTED". Vas a ver la lista de tipos de cliente: "Client-based Applications", "Server-based Applications", "Mobile-based Applications", "Non-browser Applications" y "Self Client". Qué es el Self Client: es un cliente OAuth sin URL de redirección y sin pantalla de consentimiento. Está pensado para un programa de fondo que corre en un servidor y siempre trabaja contra LA MISMA cuenta de Zoho. Vos mismo te autorizás desde la consola y te llevás el código. Cuándo conviene: en este caso, casi siempre. Un comercio, una organización de Zoho Books, un middleware de ROKI Connect. Es el camino más corto y el que menos se rompe. Cuándo NO conviene: si ROKI Connect va a conectar muchos comercios distintos con un solo software, o si el comercio no quiere que su token dependa de un usuario en particular. Ahí tenés que registrar "Server-based Applications", cargar "Homepage URL" y "Authorized Redirect URLs", y hacer que cada comercio pase por la pantalla de consentimiento. Es más trabajo pero escala. Para el Self Client: pasá el mouse por encima de la opción "Self Client" y hacé clic en "CREATE NOW", después "CREATE" y después "OK". El Client ID y el Client Secret quedan a la vista en la pestaña "Client Secret". Copiálos a un lugar seguro; el secret es una contraseña, no lo mandes por WhatsApp. - 3. Habilitar los centros de datos que vayas a usar
Si el comercio está en un centro de datos distinto del tuyo, o si vas a atender comercios en varios, hay que habilitarlo. En el API Console, seleccioná la aplicación de la lista de aplicaciones y andá a la pestaña "Settings". Ahí habilitás el interruptor de cada centro de datos que necesites. La documentación aclara que el Client ID es común a todos los centros de datos, pero el Client Secret puede ser común o distinto por centro de datos según lo que elijas. La documentación oficial que leí describe el interruptor pero no da el rótulo textual de cada control, así que no te lo invento: se ve como una lista de centros de datos con un switch al lado. - 4. Generar el código con los alcances mínimos
Dentro del Self Client, andá a la pestaña "Generate Code". Cargá los alcances separados por coma, una descripción en "Scope Description", elegí el valor de "Time Duration" (es la validez del código, en minutos) y hacé clic en "CREATE". Alcances mínimos para Zoho Books, que es exactamente lo que ROKI Connect necesita: ZohoBooks.invoices.READ,ZohoBooks.customerpayments.CREATE El primero sirve para encontrar la factura por su número y sacarle el invoice_id y el customer_id. El segundo sirve para registrar el cobro. Nada más. No pidas ZohoBooks.fullaccess.all ni el .ALL de cada módulo: si ese token se filtra, con fullaccess te vacían la contabilidad. Opcionales, solo si los necesitás de verdad: ZohoBooks.customerpayments.READ (para verificar antes de crear y no duplicar pagos, recomendado), ZohoBooks.settings.READ (para listar organizaciones con GET /organizations), ZohoBooks.banking.READ (para leer el id de la cuenta de depósito una sola vez; después lo dejás fijo en la configuración y podés quitar el alcance). Para Zoho Invoice los nombres cambian de prefijo: ZohoInvoice.invoices.READ,ZohoInvoice.customerpayments.CREATE (la documentación de Invoice los lista escritos como ZohoInvoice.customerpayments.Create, ZohoInvoice.customerpayments.READ, etc.) El código aparece en pantalla una sola vez. Copialo YA y pasá al paso siguiente sin ir a tomar café: se vence en los minutos que elegiste y se usa una sola vez. - 5. Canjear el código por access token y refresh token
Esto lo hace quien programa, desde una terminal, contra el servidor de cuentas del centro de datos correcto. Es un POST a {Accounts_URL}/oauth/v2/token con estos parámetros: grant_type=authorization_code, client_id, client_secret y code (el código del paso anterior). En el flujo web además va redirect_uri; en Self Client no hay URL de redirección registrada, así que probá primero sin ese parámetro. Ejemplo con centro de datos .com: curl -X POST https://accounts.zoho.com/oauth/v2/token -d "grant_type=authorization_code" -d "client_id=1000.XXXX" -d "client_secret=YYYY" -d "code=1000.ZZZZ" La respuesta trae access_token, refresh_token, expires_in (3600 segundos, o sea una hora) y api_domain. Guardá el api_domain que te devuelve Zoho y usalo como base de las llamadas en vez de escribir el dominio a mano: es la forma más simple de no equivocarte de centro de datos. Si el canje falla con error de código inválido, en el 90% de los casos es una de dos: le pegaste al servidor de cuentas equivocado (generaste el código en .eu y lo canjeaste en .com), o el código ya se venció. - 6. Guardar el refresh token y renovar el access token cada hora
El access token dura una hora. El refresh token no expira mientras no lo revoquen. Renovar es un POST a {Accounts_URL}/oauth/v2/token con grant_type=refresh_token, client_id, client_secret y refresh_token. Devuelve un access_token nuevo. El refresh token se guarda cifrado en el servidor del middleware, nunca en el código fuente ni en un repositorio ni en una planilla compartida. La forma correcta de programarlo es: pedir un access token nuevo cuando falta poco para que venza, o cuando la API responde 401, reintentar una sola vez y seguir. No renueves en cada llamada. Trampa importante y silenciosa: Zoho permite un máximo de 20 refresh tokens por usuario, y cuando se pasa ese número borra automáticamente el más viejo. Si el implementador se la pasa generando tokens de prueba con la misma cuenta, un día borra sin querer el token que estaba en producción y la integración se cae sin ningún cambio de código. Usá una cuenta de servicio distinta para pruebas. - 7. Probar lectura: encontrar la factura por su número
Antes de tocar plata, probá leer. La cabecera de autorización de Zoho NO es Bearer: es Authorization: Zoho-oauthtoken <access_token>. Los ejemplos oficiales de la documentación de customerpayments lo muestran así. curl --request GET --url 'https://www.zohoapis.com/books/v3/invoices?organization_id=10234695&invoice_number=FAC-001' --header 'Authorization: Zoho-oauthtoken 1000.41d9xxxx.8fccxxxx' La API de facturas acepta filtros como invoice_number (y sus variantes invoice_number_startswith e invoice_number_contains), customer_id, status y search_text. De la respuesta te interesan invoice_id, customer_id, total, balance y status. Guardá el invoice_id: es lo que necesitás para registrar el cobro. El external_reference que le mandás a ROKI tiene que ser el invoice_number ("FAC-001"), que es lo que un humano reconoce, no el invoice_id interno. - 8. Crear el cobro en ROKI Connect
Cuando la factura se emite o se envía, el middleware llama a ROKI: POST https://aura.roki.systems/api/connect/v1/payments Authorization: Bearer sk_test_... (sk_live_... recién cuando termines de probar) { "amount": 1500.00, "external_reference": "FAC-001", "name": "Factura 001" } El amount va en lempiras con decimales, NUNCA en centavos. Este es un error clásico cuando el programador viene de otras pasarelas: si mandás 150000 pensando en centavos, le estás cobrando ciento cincuenta mil lempiras al cliente. Zoho también maneja los montos como decimales (el campo amount es double), así que el valor pasa tal cual, sin multiplicar ni dividir. El external_reference tiene que ser el invoice_number, y ROKI responde 201 con { id, checkout_url, transaction_id: null }. Guardá el id de ROKI junto al invoice_id de Zoho en una tabla del middleware: la vas a necesitar para el respaldo del paso 11. Qué dispara esta llamada: la opción limpia es una regla de flujo de trabajo saliente en Zoho. Andá a "Settings" > "Automation" > "Workflow Actions" > "Webhooks" > "+ New Webhook", cargá la URL del middleware y el método (POST viene por defecto; también acepta PUT y DELETE), y después asociá ese webhook a una regla en "Settings" > "Automation" > "Workflow Rules" > "+ New Workflow Rule" sobre el módulo de facturas. Ojo con los límites documentados: solo 1 webhook por regla de flujo de trabajo, y un máximo de 500 webhooks disparados por día. Si el comercio factura más que eso, usá consulta periódica en vez de webhook saliente. - 9. Hacerle llegar el checkout_url al cliente
Zoho no tiene un campo nativo para un link de pago externo, así que hay dos caminos honestos. El fácil: el middleware manda el checkout_url por WhatsApp o correo desde el lado de ROKI o desde el propio middleware, y Zoho ni se entera. El prolijo: crear un campo personalizado de texto en la factura y escribir ahí el checkout_url, lo que exige el alcance ZohoBooks.invoices.UPDATE, que no estaba en la lista mínima. Decidí esto antes de generar el código del paso 4, porque agregar un alcance después obliga a regenerar el código y los tokens desde cero. - 10. Registrar el cobro: el objeto customerpayment
Cuando llega el evento payment.approved, el middleware busca la factura por invoice_number usando el external_reference, y crea el pago: POST https://www.zohoapis.com/books/v3/customerpayments?organization_id=10234695 Authorization: Zoho-oauthtoken <access_token> Content-Type: application/json { "customer_id": "460000000012345", "payment_mode": "banktransfer", "amount": 1500.00, "date": "2026-08-16", "reference_number": "ROKI-<id_del_pago_en_roki>", "description": "Cobro ROKI Connect, transaccion <transaction_id>", "account_id": "460000000067890", "invoices": [ { "invoice_id": "460000000054321", "amount_applied": 1500.00 } ] } Alcance necesario: ZohoBooks.customerpayments.CREATE. Responde 201 con el objeto completo del pago. Campos obligatorios según la documentación: customer_id, payment_mode, amount, date (formato yyyy-mm-dd) y el arreglo invoices con al menos un objeto que traiga invoice_id y amount_applied. Valores permitidos de payment_mode: check, cash, creditcard, banktransfer, bankremittance, autotransaction, others. Para un cobro por link de ROKI lo razonable es banktransfer o others; que lo decida el contador, no el programador. Opcionales útiles: reference_number (máximo 100 caracteres, metele el id de ROKI para poder rastrear después), description, account_id (la cuenta de "Banking" donde cae la plata), bank_charges si ROKI descuenta comisión, y exchange_rate (por defecto 1) si la factura está en una moneda distinta a la moneda base de la organización. Regla de oro del amount_applied: la suma de los amount_applied no puede pasar el amount. Si el cliente pagó de más, Zoho lo trata como anticipo y el contador te va a llamar. Si pagó de menos, la factura queda parcialmente pagada, que es el comportamiento correcto. Para Zoho Invoice es lo mismo cambiando la base a https://www.zohoapis.com/invoice/v3/customerpayments y el alcance a ZohoInvoice.customerpayments.CREATE. - 11. Programar el respaldo por consulta, que no es opcional
Los webhooks se pierden: se cae el middleware, se vence un certificado, el proveedor de hosting reinicia. El middleware tiene que tener un proceso que cada 10 o 15 minutos recorra los pagos de ROKI creados en las últimas 48 horas que todavía figuran sin registrar y consulte GET /payments/{id} contra ROKI. Si el estado es aprobado y en Zoho no hay pago, lo crea. Para que ese proceso no duplique pagos hace falta idempotencia. Lo más simple: antes de crear, buscar en Zoho un customerpayment cuyo reference_number sea ROKI-<id>, y si existe, no hacer nada. Eso requiere el alcance ZohoBooks.customerpayments.READ. La alternativa sin ese alcance es guardar en la base del middleware el payment_id que devolvió Zoho y confiar en esa tabla, lo cual funciona hasta que alguien restaura un respaldo viejo. Cuidado con los límites de Zoho al programar la frecuencia: 100 peticiones por minuto por organización, y por día 1.000 en el plan gratuito, 2.000 en Standard, 5.000 en Professional y 10.000 en Premium, Elite y Ultimate. Pasarse devuelve HTTP 429. Un proceso mal escrito que consulte cada minuto se come la cuota diaria de un plan gratuito antes del mediodía. - 12. Probar de punta a punta con sk_test_ y recién después pasar a producción
Orden de la prueba: emitir una factura real chica en Zoho, dejar que el flujo de trabajo dispare el middleware, verificar que ROKI devolvió 201 con checkout_url, pagar desde el teléfono, y confirmar que el pago aparece en Zoho contra la factura correcta y en la cuenta de "Banking" correcta. Que el contador mire ese primer pago antes de que entren cien. Probá también el camino feo: apagá el middleware, hacé un pago, prendelo de nuevo y verificá que el respaldo por consulta lo recupera. Si eso no funciona, no estás listo para producción. Para reversas: si ROKI anula el cobro antes de liquidar, el pago en Zoho hay que eliminarlo o anularlo a mano o por API; si el reintegro es posterior, el criterio contable correcto normalmente es una nota de crédito, no borrar el pago. Definilo con el contador antes de arrancar, porque borrar pagos de períodos ya cerrados es un problema de auditoría, no de software. Recién ahí cambiás sk_test_ por sk_live_.
Como llega el webhook
Respuesta corta: ni Zoho Books ni Zoho Invoice pueden recibir el webhook de ROKI. Hay que poner un middleware en el medio, y eso no se negocia.
El detalle. Zoho Books y Zoho Invoice sí tienen webhooks, pero van en la dirección contraria a la que necesitás. En "Settings" > "Automation" > "Workflow Actions" > "Webhooks" > "+ New Webhook" configurás una URL externa y Zoho le pega cuando pasa algo adentro (se crea una factura, cambia un estado). Eso es SALIENTE: Zoho llama a tu servidor. Sirve perfecto para el paso 8, para enterarte de que hay una factura nueva y crear el cobro en ROKI. No sirve para el paso 10, porque no existe ninguna URL en Zoho que acepte un POST arbitrario tuyo y lo convierta en un pago.
Y aunque existiera, seguirías teniendo el problema de la firma. ROKI manda la cabecera ROKI-Signature con t=
Entonces, la arquitectura real que funciona: ROKI llama a una URL pública HTTPS del middleware (por ejemplo https://connect.elcomercio.hn/roki/webhook), el middleware valida la firma HMAC, verifica que el timestamp no tenga más de unos minutos para cortar reenvíos, responde 200 rápido, y recién después llama a la API de Zoho con el token OAuth para crear el customerpayment. Ese middleware es lo único que tiene que ser alcanzable desde internet. Puede vivir en un VPS de 5 dólares al mes, en una función sin servidor o en el mismo servidor donde ya corre otra cosa del comercio. Lo importante: HTTPS con certificado válido, no autofirmado.
Sobre la oficina del comercio: acá no aplica el problema clásico del ERP instalado en una PC de la oficina sin IP pública, porque Zoho es nube pura, no hay nada instalado. El comercio no tiene que abrir ni un puerto entrante en su firewall ni pedirle IP fija a su proveedor de internet. Toda la conversación con Zoho es saliente por HTTPS en el puerto 443. El único componente expuesto a internet es el middleware, y ese lo hospeda el implementador, no el comercio.
La tentación de Zoho Flow, y por qué la miro con desconfianza. Zoho Flow tiene un disparador de tipo Webhook que te genera una URL pública única, recibe JSON y después puede ejecutar una acción de Zoho Books como crear un pago. Sobre el papel, integración sin servidor propio. En la práctica hay dos agujeros. Primero, para validar la firma necesitás la cabecera ROKI-Signature y el cuerpo crudo sin modificar; Flow te entrega el payload ya interpretado como campos, y no tengo confirmado en la documentación oficial que exponga la cabecera y el cuerpo original intactos. Si no los expone, la validación de firma es imposible y estarías aceptando como buena cualquier llamada a esa URL, que es una invitación abierta a que alguien te marque facturas como pagadas. Segundo, aunque los expusiera, el cálculo hay que hacerlo en una función personalizada de Deluge con zoho.encryption.hmacsha256(clave, dato, "hex"), y eso ya es programar, solo que en un lenguaje raro y con peor depuración que cualquier servidor propio.
Si igual vas por Flow, la única forma defendible es: tratar el webhook como un mero aviso ("pasó algo con el pago X"), ignorar todo el contenido del cuerpo, y hacer que el flujo consulte GET /payments/{id} contra ROKI con la clave secreta para confirmar el monto y el estado desde la fuente. Ahí la firma deja de ser crítica porque no le creés nada al que te llamó. Es más lento y gasta una llamada extra, pero es honesto.
Lo que se rompe
- El centro de datos. Es la causa número uno de que la mitad de las integraciones de Zoho no arranquen. Un token generado en accounts.zoho.eu no sirve contra www.zohoapis.com, y el error que te devuelve no dice 'centro de datos equivocado', dice que no estás autorizado, así que el programador se pasa medio día revisando alcances que estaban bien. Regla práctica: nunca escribas el dominio a mano; usá el api_domain que Zoho te devuelve en la respuesta del token. Y ojo con Canadá, que es la excepción: el servidor de cuentas es accounts.zohocloud.ca pero la API es www.zohoapis.ca.
- La cabecera de autorización NO es Bearer. Zoho usa Authorization: Zoho-oauthtoken <access_token>. Cualquiera que venga de Stripe o de otra pasarela escribe Bearer por reflejo y se come un 401 sin explicación.
- El código del Self Client dura minutos y se usa una sola vez. Si lo generás, te vas a almorzar y volvés, tenés que generarlo de nuevo. El código de autorización del flujo web dura 2 minutos.
- El límite de 20 refresh tokens por usuario. Al pasarse, Zoho borra el más viejo automáticamente y sin avisar. El más viejo suele ser justo el que está en producción. Nunca uses la misma cuenta de Zoho para generar tokens de prueba y el token de producción.
- El token muere con el usuario. El refresh token hereda los permisos del usuario que lo generó. Si generaste el token con la cuenta del contador y el contador se va y le desactivan el usuario, la integración se cae ese día. Usá una cuenta de servicio dedicada.
- organization_id va en TODAS las llamadas, como parámetro de consulta. Olvidarlo es el segundo error más frecuente después del centro de datos.
- Books e Invoice no comparten alcances ni URL base. ZohoBooks.customerpayments.CREATE contra /books/v3, ZohoInvoice.customerpayments.CREATE contra /invoice/v3. Si la organización tiene Books contratado, van los alcances de Books aunque las pantallas te parezcan de Invoice.
- El amount de ROKI va en lempiras con decimales, NUNCA en centavos. Zoho también usa decimales. No multipliques ni dividas por 100 en ningún punto del camino, y escribí una prueba automática que lo verifique, porque este error solo se descubre cuando le cobrás cien veces de más a un cliente real.
- La suma de los amount_applied no puede superar el amount del pago. Si supera, Zoho rechaza o genera un anticipo, y aparece plata colgada que el contador no sabe de dónde salió.
- No mandar account_id. El pago cae en una cuenta por defecto y la conciliación bancaria no cierra. Definí la cuenta de "Banking" con el contador ANTES de la primera prueba, no después.
- Los alcances se definen al generar el código, no después. Si en la semana 3 te das cuenta de que necesitabas ZohoBooks.invoices.UPDATE para escribir el checkout_url en un campo personalizado, hay que rehacer código y tokens desde cero. Pensá la lista completa en el paso 4.
- Pedir ZohoBooks.fullaccess.all porque es más rápido. Ese token guardado en un servidor mal configurado es acceso total a la contabilidad del comercio. Dos alcances alcanzan: invoices.READ y customerpayments.CREATE.
- Los límites de la API. 100 peticiones por minuto por organización, y por día 1.000 en el plan gratuito, 2.000 en Standard, 5.000 en Professional y 10.000 en Premium, Elite y Ultimate. Un proceso de respaldo que consulte cada minuto se come la cuota diaria de un plan gratuito antes del mediodía y devuelve HTTP 429.
- Los límites de los webhooks salientes de Zoho: solo 1 webhook por regla de flujo de trabajo, y máximo 500 webhooks disparados por día. Si el comercio factura más que eso, el disparo por flujo de trabajo no te sirve y tenés que consultar periódicamente las facturas nuevas.
- Creer que Zoho puede recibir el webhook de ROKI. No puede. Sin middleware no hay integración, y quien te diga lo contrario no leyó la parte de la firma HMAC.
- El external_reference tiene que ser el invoice_number, no el invoice_id. El invoice_id es un número interno que ningún humano reconoce; si algo falla, el comercio necesita poder buscar 'FAC-001' y encontrar el pago.
- Numeración de facturas repetida entre series o sucursales. Zoho exige que invoice_number sea único en la organización, pero si el comercio migró datos o usa prefijos raros, verificá antes que la búsqueda por número devuelva exactamente una factura y no dos.
- Multimoneda. Si la factura no está en la moneda base de la organización, hay que mandar exchange_rate (por defecto es 1) y ponerse de acuerdo con el contador sobre qué tipo de cambio se usa. Ignorarlo genera diferencias de centavos que después nadie puede explicar.
- Anulaciones y reintegros. ROKI anula antes de liquidar y reintegra después. En Zoho eso es borrar el pago o emitir una nota de crédito, y son cosas contablemente distintas. Definí el criterio con el contador antes de arrancar; borrar pagos de un período cerrado es un problema de auditoría.
Lo que no pudimos confirmar
Cada nombre de menu y cada puerto de esta pagina se verifico contra la documentacion oficial de la plataforma. Estos 11 puntos no se pudieron confirmar, y se dejan senalados en vez de presentarlos como ciertos:
- Paso 3: "En el API Console, seleccioná la aplicación de la lista de aplicaciones y andá a la pestaña 'Settings'. Ahí habilitás el interruptor de cada centro de - no-existe. HALLAZGO MAS GRAVE. La pestaña Settings con los interruptores multi-DC NO existe para el Self Client. La doc oficial de Zoho dice literalmente: "This is available for all client types, except the Self
- Prerequisito 1 y paso 1: la ruta es "Settings" > "Organization Profile". - nombre-distinto. El item de menu se llama "Profile", dentro de la seccion "Organization". La doc oficial (zoho.com/us/books/help/settings/organization-profile.html) dice: "Navigate to Settings in the top-right corner
- Prerequisito 2 y paso 1: "Menú desplegable con el nombre de la organización, arriba a la derecha > 'Manage Organizations'". - nombre-distinto. La opcion del desplegable se llama "Manage", no "Manage Organizations". La doc oficial (zoho.com/us/books/kb/general/manage-multiple-organizations.html) dice: click en el nombre de la organizacion arr
- Paso 2: para el registro de aplicacion escalable hay que cargar "Homepage URL" y "Authorized Redirect URLs". - nombre-distinto. "Homepage URL" esta confirmado. El otro campo es URI, no URL: zoho.com/accounts/protocol/oauth-setup.html lo rotula "Authorized Redirect URI" (singular) y la doc de CRM v8 lo rotula "Redirect URIs". T
- Paso 4: "Cargá los alcances separados por coma, una descripción en 'Scope Description'". - nombre-distinto. El rotulo "Scope Description" no aparece en ninguna doc oficial que pude leer. La doc de OAuth de Zoho Books rotula los campos del tab Generate Code como "Scope", "Time Duration" y "Description"; zoho
- Paso 4: "No pidas ZohoBooks.fullaccess.all". - no-verificable. El alcance ZohoBooks.fullaccess.all NO figura en la tabla oficial de scopes de zoho.com/books/api/v3/oauth/#scopes. Esa tabla solo lista scopes por modulo con operaciones CREATE/READ/UPDATE/DELETE/ALL
- Prerequisito 3 y paso 1: la lista de dominios del centro de datos incluye "books.zohocloud.ca" para Canadá. - no-verificable. Hay conflicto entre fuentes de Zoho. La tabla de dominios de la doc de Zoho Books (v4, introduction) lista el dominio de app canadiense como books.zoho.ca; la introduction de v3 solo lista el sufijo "
- Prerequisito 7: "Salida HTTPS por el puerto 443 ... hacia *.zohoapis.* y hacia accounts.zoho.*". - nombre-distinto. El patron accounts.zoho.* NO cubre accounts.zohocloud.ca, que es el servidor de cuentas de Canadá y que el propio documento nombra correctamente en el paso 1. Una regla de firewall escrita asi rompe l
- Prerequisito 3 y paso 1: la lista de centros de datos es books.zoho.com, .eu, .in, .com.au, .jp, .sa, zohocloud.ca. - nombre-distinto. Falta China. La doc oficial de Zoho Books lista el DC de China con dominio .com.cn y endpoint https://www.zohoapis.com.cn/books/v3. La doc de Zoho Invoice tambien lo lista (https://www.zohoapis.com.cn
- Paso 4: para Zoho Invoice los alcances son ZohoInvoice.invoices.READ, ZohoInvoice.customerpayments.CREATE (con nota de que la doc los escribe .Create). - nombre-distinto. La advertencia del documento es correcta y conviene subirla de tono. La doc oficial (zoho.com/invoice/api/v3/oauth/) escribe literalmente: "ZohoInvoice.customerpayments.Create, ZohoInvoice.customerpay
- Prerequisito 6: la configuracion de numeracion automatica de facturas (el documento ya lo marca como no verificado). - no-verificable. Correcto dejarlo marcado como dudoso: no encontre el rotulo exacto de la pantalla de numeracion automatica en la doc oficial de Zoho Books. Mantener el hedge.
Fuentes
- https://www.zoho.com/books/api/v3/oauth/
- https://www.zoho.com/books/api/v3/introduction/
- https://www.zoho.com/books/api/v3/customer-payments/
- https://www.zoho.com/books/api/v3/invoices/
- https://www.zoho.com/invoice/api/v3/oauth/
- https://www.zoho.com/invoice/api/v3/introduction/
- https://www.zoho.com/accounts/protocol/oauth/multi-dc.html
- https://www.zoho.com/accounts/protocol/oauth/self-client/overview.html
- https://www.zoho.com/us/books/help/settings/automation/workflow-actions/webhooks.html
- https://www.zoho.com/us/books/help/settings/automation.html
- https://www.zoho.com/us/books/help/settings/organization-profile.html
- https://www.zoho.com/deluge/help/encryption/hmac-sha256.html
