Facturas
Escribir y leer son permisos independientes. Una tienda escribe y nunca lee; una gestoría lee y nunca escribe. Ninguno implica al otro.
Escribir una factura
Sección titulada «Escribir una factura»Cuando alguien compra, envías lo que le has cobrado. Aparece en su cuenta como un gasto con sus líneas, sin que haga nada.
invoices:writeRegistrar una factura
Idempotente sobre externalReference: reintentar la misma compra devuelve la misma factura en vez de crear una segunda. El comercio se toma de la identidad de partner del cliente que llama y no puede fijarse en el cuerpo. Los importes se envían en positivo; Costia guarda los gastos personales en negativo y hace el signo por su cuenta.
Parámetros
| Nombre | Tipo | Descripción |
|---|---|---|
locale | string | Idioma para todo lo que Costia resuelve por nombre, como las categorías. |
Cuerpo
| Campo | Tipo | Descripción |
|---|---|---|
externalReferenceobligatorio | string | el identificador del pedido en el sistema del propio partner. Obligatorio, y es la clave de idempotencia: reintentar la misma compra no debe producir un segundo gasto. |
invoiceNumber | string | el número impreso en el documento, si lo hay. Se guarda para el archivo de la persona; Costia no deriva nada de él. |
titleobligatorio | string | lo que la persona ve como nombre del gasto. El comercio va aparte y sale del partner, así que esto queda libre para describir la compra y no la tienda. |
issuedAtobligatorio | string (date) | la fecha del documento, no la del envío. Una factura registrada tarde sigue perteneciendo al día en que se emitió, que es por el que se rige una declaración. |
currencyCodeobligatorio | string | ISO 4217, tres letras. |
totalobligatorio | number | lo que pagó la persona, en positivo. Costia guarda los gastos personales en negativo y hace el signo por su cuenta. |
totalTax | number | el total de impuestos tal y como está impreso. Opcional: si falta, Costia suma las líneas, y si viene se respeta tal cual, porque un documento que redondea distinto sigue siendo el documento que la persona tiene en la mano. |
defaultTaxRate | number | el tipo que se aplica a cualquier línea que no declare el suyo. |
taxIncludedInPrices | boolean | si los importes de línea ya llevan impuestos incluidos. Si es false, Costia añade las líneas de impuesto para que la factura cuadre. |
notes | string | cualquier cosa que la persona deba leer junto a la factura. |
linesobligatorio | PartnerInvoiceLine[] | al menos una. Una factura sin líneas es un total sin nada que lo explique, que es justo lo que la persona abrió el gasto para ver. |
Respuestas
| Código | Significado |
|---|---|
200 | Ese externalReference ya estaba registrado. Vuelve la misma factura con replayed=true, así que reintentar tras un tiempo de espera agotado es seguro. |
201 | La factura se registró |
400 | VALIDATION_ERROR cuando el cuerpo incumple una regla, con fieldErrors nombrando cada una; UNKNOWN_LINE_TYPE o UNKNOWN_QUANTITY_UNIT cuando una línea trae un valor fuera del conjunto aceptado. |
401 | UNAUTHORIZED: el token no es uno emitido por Costia a un cliente registrado. |
403 | ACCESS_DENIED cuando al token le falta invoices:write; NOT_A_PARTNER_CLIENT cuando el cliente no tiene identidad de partner; PARTNER_CANNOT_WRITE cuando el partner no tiene dominio verificado; PARTNER_DISABLED cuando está deshabilitado. |
Tres cosas que conviene entender antes
Sección titulada «Tres cosas que conviene entender antes»externalReference es tu clave de idempotencia. Manda el identificador del pedido en
tu propio sistema. Si reintentas tras un tiempo de espera agotado, Costia devuelve la
misma factura con replayed: true en vez de crear una segunda. Por eso es seguro
reintentar, y por eso no debe ser un valor nuevo en cada intento.
El comercio sale de tu partner, no del cuerpo. No hay campo para ponerlo. Tu marca verificada es la que aparece, y es lo que hace que la palabra «verificado» signifique algo para quien lo lee.
Los importes van en positivo. Costia guarda los gastos personales en negativo y hace el signo por su cuenta.
El recorrido de Ambrosia
Sección titulada «El recorrido de Ambrosia»La tienda de ejemplo lo hace así, y merece la pena copiar dos decisiones.
// El navegador manda solo el identificador del pedido.const { orderUuid } = await request.json();
// El pedido se relee del backend de la tienda, no se toma del carrito que// mandó la página.const order = await fetch(`${backend}/front/orders/${orderUuid}`, { headers });
// El token se obtiene en servidor, y se refresca solo si hace falta.const { accessToken } = await auth.api.getAccessToken({ body: { providerId: 'costia' }, headers: request.headers,});
await fetch('https://api.costia.app/public/v1/invoices', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${accessToken}` }, body: JSON.stringify(invoice),});El pedido se relee en servidor. Si aceptaras la cesta que manda el navegador, cualquiera podría escribir la factura que quisiera — en su propia cuenta, así que no es una brecha, pero convertiría en ficción cada gasto espejado. Y toda la gracia de que la tienda empuje sus datos es que son sus datos.
El token nunca sale al navegador. Un token con invoices:write es justo lo que no
debe estar en una página.
Leer las facturas
Sección titulada «Leer las facturas»Todas las de la cuenta, no solo las que escribiste tú. Es lo que hace útil este permiso para una gestoría.
invoices:readListar las facturas de la persona
Todas las facturas de la cuenta, no solo las que escribió este cliente. Ordenadas por fecha descendente con un desempate estable, así que recorrer un ejercicio entero no repite ni se salta filas.
Parámetros
| Nombre | Tipo | Descripción |
|---|---|---|
dateFrom | string (date) | Fecha de factura más antigua que incluir, inclusive. |
dateTo | string (date) | Fecha de factura más reciente que incluir, inclusive. |
categoryUuids | string (uuid)[] | Limitar a estas categorías. Sus uuid salen de GET /public/v1/categories. |
page | integer (int32) | Número de página, empezando en cero. |
size | integer (int32) | Tamaño de página. Los valores mayores se recortan a 200 en vez de rechazarse, así que recorrer un ejercicio no puede convertirse en una sola llamada enorme. |
locale | string | Idioma de los nombres de categoría y de origen. |
Respuestas
| Código | Significado |
|---|---|
200 | Una página de facturas |
400 | VALIDATION_ERROR: page debe ser cero o mayor y size debe ser al menos uno. |
401 | UNAUTHORIZED |
403 | ACCESS_DENIED: al token le falta invoices:read. |
El orden es por fecha descendente con un desempate estable, así que recorrer un ejercicio entero no repite ni se salta filas aunque entren facturas nuevas mientras paginas.
invoices:readObtener una factura
La factura completa con sus líneas y el desglose de impuestos, que es lo que necesita una declaración y lo que el listado no lleva.
Parámetros
| Nombre | Tipo | Descripción |
|---|---|---|
uuidobligatorio | string (uuid) | El uuid de la factura, tal y como se devolvió al registrarla. |
locale | string | Idioma de los nombres de categoría y de origen. |
Respuestas
| Código | Significado |
|---|---|
200 | La factura |
400 | VALIDATION_ERROR: uuid no es un UUID válido. |
401 | UNAUTHORIZED |
403 | ACCESS_DENIED: al token le falta invoices:read. |
404 | RESOURCE_NOT_FOUND. Una factura de otra cuenta devuelve esto y no un 403 — decir que existe ya sería decir algo sobre esa cuenta. |
La factura completa trae sus líneas y el desglose por tipo de impuesto, que es lo que necesita una declaración y lo que el listado no lleva.
