Factuarea APIDevelopers

La teva botiga amb Factuarea

Mostra productes i packs a la teva botiga web, mantén preus i disponibilitat sincronitzats i factura cada comanda pagada amb el preu que va pagar el client.

Aquesta guia mostra com una web com Vivero Los Olivos fa servir Factuarea com el seu catàleg i el seu motor de facturació. El teu servidor llegeix productes i packs, els mostra a les teves pàgines i, quan una comanda es paga, crea la factura.

Tot s'executa des del teu servidor, mai des del navegador. Llegeix abans Packs per a la credencial, les vistes i les regles de memòria cau, que són les mateixes per als productes.

Un aparador per a productes i packs

Productes i packs són dos recursos que comparteixen un contracte d'aparador. No existeix un llistat mixt: el teu servidor sincronitza els dos recursos i ordena, pagina i cerca sobre la seva pròpia còpia. Així cada recurs conserva el seu cursor, els seus filtres i la seva memòria cau.

curl "https://api.factuarea.com/v1/companies/$COMPANY_ID/products?view=storefront&tag=huerto" \
  -H "Authorization: Bearer fact_live_…"

curl "https://api.factuarea.com/v1/companies/$COMPANY_ID/packs?view=storefront&tag=huerto" \
  -H "Authorization: Bearer fact_live_…"

Amb view=storefront els dos recursos publiquen els mateixos camps:

CampProductePack
Identitat i contingutid, object, name, slug, previous_slugs, summary, description, seo, tags, currency, updated_atEls mateixos
Preu amb i sense IVAsale_price{net, tax, gross}price{net, tax, gross}
Disponibilitatavailability{status, quantity?}La mateixa
Imatgesgallery[] amb alt, width, heightcover i gallery[] amb alt
Ofertapromotionpromotion

El preu del producte es diu sale_price perquè price ja era un text publicat i no pot canviar de tipus. Fora de l'aparador els dos recursos responen igual: 404 al detall i llistat buit en cercar per slug. Un component de pack porta product_id i slug, perquè la teva pàgina enllaci el producte (slug és null si aquell producte no és a la web).

Pas a pas

  1. Crea la credencial. Una API key live guardada al teu servidor amb products:read, packs:read i, si factures, invoices:write.
  2. Publica els productes. Un producte no és a la web fins que el seu channels.web és true. Val false per defecte, també en els productes existents, perquè res no es publiqui sense una decisió. Publica'ls un a un amb PATCH …/products/{product} i channels: {web: true}, o en bloc amb POST …/products/bulk-channels (vegeu més avall). En publicar es genera el slug si falta.
  3. Fes la primera còpia. Recorre GET …/products?view=storefront i GET …/packs?view=storefront amb el cursor i guarda els resultats.
  4. Mantén-la al dia. Subscriu-te als webhooks product.* i pack.*, inclosos product.deleted i pack.deleted, i fes servir view=all&updated[gte]=… per recuperar-te després d'una caiguda. Un producte que surt de la web hi apareix amb is_active: false o channels.web: false.
  5. Pinta les teves pàgines des de la teva còpia, i respon 301 quan una petició faci servir un slug que ara és a previous_slugs.

Publicar en bloc

POST /v1/companies/{company}/products/bulk-channels rep {"ids": [...], "channels": {"web": true}} amb fins a 50 ids per lot. La capçalera Idempotency-Key és obligatòria i cada fila s'encerta o falla pel seu compte: si un id és d'una altra empresa, aquella fila falla com a inexistent i la resta es publica, i la resposta ho indica per fila. Fes servir "web": false per retirar-los.

Preus amb i sense IVA

Mostra sale_price.gross (o price.gross) als clients que paguen l'IVA de l'empresa. L'olivera costa 39,00 € sense IVA i 42,90 € amb IVA:

{ "price": "39.00", "sale_price": { "net": "39.00", "tax": "3.90", "gross": "42.90" } }

price conserva el seu significat de sempre. Per a clients de Canàries, exempts i d'exportació, construeix el preu a partir de net: l'impost del document s'aplica a sobre i «59,90 € amb IGIC» no existeix. Els detalls són a Packs. Un producte sense impost assignat retorna tax: "0.00" i gross igual a net. Per a una combinació lliure d'opcions, POST …/products/{product}/resolve-selection retorna el sale_price d'aquella combinació.

Disponibilitat

availability.status val in_stock, low, out_of_stock o untracked (no es controla l'stock, tracta'l com a disponible). Una presentació compta en les seves pròpies unitats comercials: una caixa de 6 amb 20 unitats en stock són 3 caixes.

L'aparador mai dona quantitats exactes tret que l'empresa posi web_stock_display=exact. El valor per defecte, low_only, dona la quantitat només quan l'estat és low. L'stock mai bloqueja una venda, així que no amaguis pel teu compte el botó de compra en out_of_stock tret que sigui la teva política.

Imatges

Les imatges de producte i de pack segueixen la mateixa regla: URL absolutes públiques i permanents, sense credencial, signatura ni caducitat, amb un nom UUID que no es reutilitza. Enllaça-les directament o copia-les al teu CDN. Tota imatge porta un alt (la galeria de producte fa servir el nom del producte) i, en productes, width i height per reservar l'espai abans de carregar-la.

Memòria cau

Mantén la teva pròpia memòria cau i recolza't en els webhooks per refrescar-la. Si tot i això revalides, envia If-None-Match amb l'ETag que vas guardar: un recurs sense canvis respon 304 sense cos. Recorda que un 304 consumeix quota de l'API. Productes i packs fan servir les mateixes capçaleres.

Webs B2B amb tarifes de client

Si la teva web ven a empreses que tenen la seva pròpia tarifa, demana el preu de cada client en lloc de mostrar el públic.

1. Demana amb client_id. Envia el contacte del client que ha iniciat sessió (el mateix client_id que porta una factura) a POST …/price-lists/resolve, o a …/resolve-many per a fins a 100 productes. Afegeix channel: "web" perquè s'apliquin les ofertes de la web:

curl -X POST https://api.factuarea.com/v1/companies/$COMPANY_ID/price-lists/resolve \
  -H "Authorization: Bearer fact_live_…" \
  -H "Content-Type: application/json" \
  -d '{
        "product_id": "019f1b77-3e90-7c24-8a1d-5f0b2e6c9d48",
        "client_id": "019f2a0c-1d52-7b8e-a4f0-3c6d9e2b5a17",
        "channel": "web"
      }'

La tarifa es tria com en un document. Un price_list_id que enviïs mana (i null significa sense tarifa). Si no l'envies, regeix la tarifa predeterminada del client, i s'ignora si és inactiva. La resposta inclou unit_price, el source i el nom de la tarifa, regular_unit_price i, quan s'aplica una oferta, promotion.

2. Mostra gross i cobra l'import exacte. El gross de promotion.price usa l'IVA del producte i el règim de l'empresa. No aplica el recàrrec d'equivalència, l'exempció ni l'IGIC del client. Per mostrar o cobrar l'import exacte, envia les línies a POST …/taxes/calculate-totals: és el mateix càlcul que fa la factura.

3. Mantén la memòria cau al dia. Subscriu-te als webhooks price_list.* (created, updated, deleted, item_updated i item_deleted) i refresca només el que ha canviat. Després d'una caiguda, o en començar, posa't al dia llistant amb updated[gte] igual a l'últim updated_at que vas desar, a GET …/price-lists i a GET …/price-lists/{priceList}/items. L'updated_at d'una tarifa també es mou quan canvia un dels seus preus.

Factura una comanda pagada

Quan el pagament es confirma, el teu servidor crea la factura amb POST /v1/companies/{company}/invoices. L'id de la comanda va a external_id i cada línia porta la data de la venda i el preu realment cobrat:

curl -X POST https://api.factuarea.com/v1/companies/$COMPANY_ID/invoices \
  -H "Authorization: Bearer fact_live_…" \
  -H "Idempotency-Key: 019f2a10-5c3e-7d41-8b20-6a9e1c4d7f33" \
  -H "Content-Type: application/json" \
  -d '{
        "client_id": "019f2a0c-1d52-7b8e-a4f0-3c6d9e2b5a17",
        "issued_on": "2026-06-30",
        "due_on": "2026-06-30",
        "external_id": "WEB-1042",
        "prices_include_tax": true,
        "lines": [
          { "pack_id": "019f1c2e-7b40-7a11-9c35-2d4e6f8a0b12", "quantity": 2,
            "sold_on": "2026-06-30", "channel": "web",
            "unit_price": "59.90", "prices_include_tax": true },
          { "product_id": "019f1b77-3e90-7c24-8a1d-5f0b2e6c9d48", "quantity": 1,
            "sold_on": "2026-06-30", "channel": "web",
            "unit_price": "42.90", "prices_include_tax": true }
        ],
        "payment": { "method": "credit_card", "reference": "WEB-1042-PAY" }
      }'
CampQuè fa
external_idL'id de la teva comanda. Fa la factura idempotent
sold_onEl dia de la venda. Amb ell s'avaluen la vigència i les ofertes
unit_priceEl preu cobrat, per pack o per unitat. Guanya a qualsevol preu o oferta vigent
prices_include_taxSi aquest unit_price porta IVA. Funciona per línia i, si s'omet, la línia pren el del document
channelweb perquè s'apliqui una oferta només web. Per defecte és documents
paymentMarca la factura com a pagada i converteix la petició en un cobrament (vegeu Cobrament desatès)

Regles que has de seguir

  • Envia sempre el preu cobrat. Sense unit_price, un pack es valora al preu vigent quan es crea la factura, no al que va veure el client. Si el pack va passar de 59,90 € a 64,90 € entre la pàgina i el pagament, enviar unit_price: "59.90" factura el que es va pagar.
  • La suma quadra al cèntim. Amb preu cobrat, el grup de línies es reparteix perquè sumi exactament el que s'ha pagat. Tres Pack Huerto urbano a 59,90 € sumen exactament 179,70 € i, si cal, una línia de component es parteix en dues (per exemple 17 i 1 unitats de plançons). El mateix val per a un producte amb preu amb IVA: una olivera cobrada a 36,47 € queda en una línia de 33,15 € més 3,32 € d'IVA.
  • Una venda cobrada sempre es factura. Si el pack es va arxivar o va acabar la vigència després del pagament, una línia amb sold_on i unit_price es factura igualment, amb la composició actual del pack.
  • Una venda al preu d'oferta compta com a venuda amb l'oferta. Si el unit_price cobrat coincideix amb el preu d'oferta d'aquell canal i aquella data, la línia conserva l'oferta. Consulta Ofertes.
  • L'stock es mou quan el mou el tipus de document, pel llibre d'stock normal. El pack no té stock propi.

Reintents

Dos mecanismes treballen junts. La capçalera Idempotency-Key cobreix el reintent de transport de la mateixa petició. L'external_id identifica la comanda i no caduca mai:

  • Amb payment, type: F2 o options.register_verifactu, repetir la petició amb el mateix external_id respon 200 amb la mateixa factura i Idempotent-Replayed: true, sense una segona factura. Un altre total o tipus respon 409 unattended_replay_mismatch.
  • Sense ells, un reenviament actualitza l'esborrany existent en lloc de crear-ne un altre, i si el document ja no és editable respon amb el seu error 422 d'estat.

Reintenta els timeouts i els 5xx amb la mateixa clau i el mateix external_id. No reintentis un 4xx sense corregir la petició.

Errors 422 previsibles

StatuscodeCausa
404pack_not_foundEl pack no existeix o és d'una altra empresa
422pack_not_sellableArxivat, inactiu, fora de vigència a sold_on o amb un component inactiu. Només afecta línies sense unit_price, amb el motiu i param a la línia
422quantityLa quantitat de packs no és entera, és menor que 1, o una línia resultant supera el sostre de la seva columna
422pack_print_mode_not_allowedEl mode d'impressió no és vàlid per a aquell tipus de document
422pack_lines_must_be_expandedVas enviar línies d'un pack construïdes per tu
422simplified_invoice_not_allowedUna factura simplificada per sobre del seu topall

No pots construir les línies d'un pack pel teu compte: envia {pack_id, quantity} i el servidor les expandeix. Per editar un esborrany sense perdre'n la fotografia, reenvia {pack_group_id, quantity}. Editar el preu o la quantitat d'una línia de component d'un grup respon 422 pack_line_locked.

Propers passos

En aquesta pàgina

Et donem un cop de mà?Contactar amb suport