Registrar pagaments
Registra pagaments parcials, consulta el saldo en curs des del ledger i anul·la un pagament que s'ha retornat — sense emetre una rectificativa que ningú no t'ha demanat.
Les factures i les factures de compra mantenen un ledger de pagaments:
una llista de pagaments individuals, cadascun amb el seu propi import, data
i mètode. Registra els pagaments d'un en un a mesura que entra els diners —
l'API recalcula els imports cobrat i pendent després de cada
entrada, i la factura es reporta com a paid quan el saldo arriba a zero.
El ledger és de només addició. Els diners que van entrar i van tornar a sortir (una devolució de rebut, una retrocessió de targeta, un pagament imputat a la factura equivocada) no s'esborren: l'entrada s'anul·la, conserva el seu import, data, mètode i referència, i guanya un motiu, un instant i un autor. Un pagament anul·lat deixa de computar, així que la factura torna al circuit de cobrament.
El status que llegeixes a la factura es deriva del ledger, no
s'emmagatzema: paid quan els pagaments vigents cobreixen l'import
exigible, partially_paid mentre en cobreixen una part, i en qualsevol
altre cas l'estat propi del document (sent, o overdue així que passa el
seu venciment). Registrar o anul·lar un pagament el canvia en la lectura
següent — no hi ha cap pas de sincronització que pugui quedar-se enrere.
Els dos imports que hi ha al darrere són paid_amount (suma dels
pagaments vigents) i pending_amount (import exigible − paid_amount).
El cicle complet d'un pagament de venda és, doncs:
POST /v1/invoices/{id}/payments.paid_amount / pending_amount, o el sub-recurs del ledger.POST /v1/invoices/{id}/payments/{payment_id}/reversal. La factura torna a sent o a overdue i admet un pagament nou.Registrar un pagament de venda
POST /v1/invoices/{id}/payments afegeix un pagament a una factura de
venda. El body és petit:
| Camp | Tipus | Requerit | Notes |
|---|---|---|---|
amount | number | Sí | Més gran que 0. No pot superar pending_amount. |
paid_on | string (YYYY-MM-DD) | Sí | La data en què es va rebre els diners. |
payment_method | string (enum) | Sí | Un dels valors del catàleg (vegeu a sota). |
reference | string | No | La teva pròpia referència (p. ex. un número de transferència). |
notes | string | No | Nota interna lliure. |
payment_method és un enum tancat de set valors: bank_transfer,
direct_debit, cash, credit_card, check, paypal, other. Obtén el
catàleg amb etiquetes des de GET /v1/payment-methods
en lloc de fixar els valors a mà.
La resposta és 201 Created amb el pagament acabat de crear sota data:
{
"data": {
"id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0b",
"object": "payment",
"invoice_id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01",
"amount": 500.00,
"payment_date": "2026-05-20",
"payment_method": "bank_transfer",
"payment_method_text": "Transferencia bancaria",
"reference": "TRF-2026-0042",
"notes": null,
"is_reversed": false,
"reversed_at": null,
"reversal_reason": null,
"reversal_reason_text": null,
"reversal_note": null,
"created_at": "2026-05-20T10:30:00Z",
"updated_at": "2026-05-20T10:30:00Z"
}
}Els cinc camps revers* descriuen l'estat d'aquella entrada al ledger.
Un pagament vigent informa is_reversed: false i null als altres
quatre; consulta Anul·lar un pagament.
import os, requests
resp = requests.post(
'https://api.factuarea.com/v1/invoices/01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01/payments',
json={
'amount': 500.00,
'paid_on': '2026-05-20',
'payment_method': 'bank_transfer',
'reference': 'TRF-2026-0042',
},
headers={'Authorization': f"Bearer {os.environ['FACTUAREA_API_KEY']}"},
)
resp.raise_for_status()
payment = resp.json()['data']
print(payment['id'], payment['amount'])const res = await fetch(
'https://api.factuarea.com/v1/invoices/01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01/payments',
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.FACTUAREA_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: 500.0,
paid_on: '2026-05-20',
payment_method: 'bank_transfer',
reference: 'TRF-2026-0042',
}),
},
);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const { data } = await res.json();
console.log(data.id, data.amount);curl -s -X POST \
https://api.factuarea.com/v1/invoices/01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01/payments \
-H "Authorization: Bearer $FACTUAREA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 500.00,
"paid_on": "2026-05-20",
"payment_method": "bank_transfer",
"reference": "TRF-2026-0042"
}' | jq '.data'Pagaments parcials i saldo
El saldo en curs no viu a l'objecte del pagament — viu a la
factura. Després de registrar un o diversos pagaments, llegeix la
factura (GET /v1/invoices/{id}) per veure com està:
{
"data": {
"id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01",
"object": "invoice",
"status": "sent",
"total": 1210.00,
"paid_amount": 500.00,
"pending_amount": 710.00,
"payments": {
"detail": [
{
"id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0b",
"object": "payment",
"invoice_id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01",
"amount": 500.00,
"payment_date": "2026-05-20",
"payment_method": "bank_transfer",
"payment_method_text": "Transferencia bancaria",
"reference": "TRF-2026-0042",
"notes": null,
"is_reversed": false,
"reversed_at": null,
"reversal_reason": null,
"reversal_reason_text": null,
"reversal_note": null,
"created_at": "2026-05-20T10:30:00Z",
"updated_at": "2026-05-20T10:30:00Z"
}
],
"total": 500.00,
"pending": 710.00
}
}
}paid_amount/pending_amount— els totals cobrat i pendent. Sempre presents, calculats a partir dels pagaments vigents: una entrada anul·lada continua apayments.detail, però ja no suma apaid_amount.payments.total/payments.pending— les mateixes dues xifres, reflectides dins de l'objectepayments. Sempre presents.payments.detail— l'array de pagaments individuals. Es materialitza només a l'endpoint de detall (GET /v1/invoices/{id}); als endpoints de llistat arriba com a[](mentretotalipendingcontinuen poblats) perquè els llistats siguin lleugers. Fes servir el sub-recurs per obtenir el detall per separat.
Quan l'últim pagament tanca el saldo (pending_amount arriba a 0), la
factura reporta status: "paid".
Un pagament l'amount del qual supera pending_amount es rebutja amb
422 i subcode: "payment_exceeds_pending_amount" (param: "amount").
Un pagament exactament igual a l'import pendent és vàlid i salda la
factura. Consulta Errors.
Llistar pagaments
GET /v1/invoices/{id}/payments retorna el ledger complet d'una factura,
ordenat per data de pagament. Una factura sense pagaments retorna
{ "data": [] }, mai un 404.
{
"data": [
{
"id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0b",
"object": "payment",
"invoice_id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01",
"amount": 500.00,
"payment_date": "2026-05-20",
"payment_method": "bank_transfer",
"payment_method_text": "Transferencia bancaria",
"reference": "TRF-2026-0042",
"notes": null,
"is_reversed": false,
"reversed_at": null,
"reversal_reason": null,
"reversal_reason_text": null,
"reversal_note": null,
"created_at": "2026-05-20T10:30:00Z",
"updated_at": "2026-05-20T10:30:00Z"
}
]
}Els pagaments anul·lats continuen en aquesta llista. La seva
absència no és mai el senyal: el senyal és is_reversed: true. El codi
que detecti una anul·lació perquè una entrada desapareix del ledger no
s'activarà mai, perquè no s'esborra res. Filtra pel flag i, si
recalcules el saldo pel teu compte, suma només les entrades amb
is_reversed: false.
Anul·lar un pagament
POST /v1/invoices/{id}/payments/{payment_id}/reversal anul·la un
pagament d'una factura de venda indicant-ne el motiu. El pagament no
s'esborra: un pagament que va existir i va deixar de tenir efecte és
informació comptable, i el seu rastre (motiu, instant i autor) és el que
explica per què la factura va deixar d'estar cobrada.
| Camp | Tipus | Requerit | Notes |
|---|---|---|---|
reason | string (enum) | Sí | Un dels cinc valors del catàleg tancat de sota. |
note | string | No | Nota lliure, màx. 500 caràcters. |
La resposta és 200 OK amb el pagament ja anul·lat sota data — la
mateixa forma InvoicePaymentDetail que retornen l'alta i el llistat:
{
"data": {
"id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0b",
"object": "payment",
"invoice_id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01",
"amount": 500.00,
"payment_date": "2026-05-20",
"payment_method": "direct_debit",
"payment_method_text": "Domiciliación bancaria",
"reference": "TRF-2026-0042",
"notes": null,
"is_reversed": true,
"reversed_at": "2026-06-02T08:15:00Z",
"reversal_reason": "direct_debit_return",
"reversal_reason_text": "Devolución de adeudo SEPA",
"reversal_note": "Devuelto por el banco con motivo MD01.",
"created_at": "2026-05-20T10:30:00Z",
"updated_at": "2026-06-02T08:15:00Z"
}
}El catàleg de motius és tancat
reason és obligatori i només s'admeten aquests cinc valors. No existeix
un valor refund a propòsit: un reemborsament genuí és una factura
rectificativa, no una anul·lació (consulta
Anul·lació o rectificativa?).
reason | Quan fer-lo servir |
|---|---|
direct_debit_return | El banc del client va retornar el rebut SEPA. |
card_dispute | El pagament amb targeta es va retrocedir després d'una disputa. |
misapplied_payment | Els diners van entrar, però es van imputar a la factura equivocada. |
bounced_effect | Un efecte o pagaré va resultar impagat al venciment. |
recording_error | L'entrada va ser un error: mai no va correspondre a diners reals. |
Qualsevol altre valor retorna 422 payment_reversal_reason_invalid amb
param: "reason".
Què li passa a la factura
L'import anul·lat deixa de computar a paid_amount, pending_amount i a
qualsevol agregat de tresoreria, així que una factura paid torna al
circuit de cobrament en la lectura següent:
overduesi la seva data de venciment ja ha passat,sentaltrament,- i torna a admetre un pagament nou.
No hi ha manera de «despagar» una factura a mà: paid és una lectura del
ledger, no un estat que es pugui escriure. L'endpoint genèric de canvi
d'estat no l'admet com a destinació, precisament perquè el saldo cobrat i
el ledger no puguin discrepar mai. Anul·lar un pagament és l'únic camí de
tornada.
import os, requests
resp = requests.post(
'https://api.factuarea.com/v1/invoices/01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01'
'/payments/01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0b/reversal',
json={
'reason': 'direct_debit_return',
'note': 'Devuelto por el banco con motivo MD01.',
},
headers={'Authorization': f"Bearer {os.environ['FACTUAREA_API_KEY']}"},
)
resp.raise_for_status()
payment = resp.json()['data']
print(payment['is_reversed'], payment['reversal_reason'])const res = await fetch(
'https://api.factuarea.com/v1/invoices/01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01' +
'/payments/01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0b/reversal',
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.FACTUAREA_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
reason: 'direct_debit_return',
note: 'Devuelto por el banco con motivo MD01.',
}),
},
);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const { data } = await res.json();
console.log(data.is_reversed, data.reversal_reason);curl -s -X POST \
https://api.factuarea.com/v1/invoices/01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01/payments/01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0b/reversal \
-H "Authorization: Bearer $FACTUAREA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"reason": "direct_debit_return",
"note": "Devuelto por el banco con motivo MD01."
}' | jq '.data | {is_reversed, reversal_reason, reversal_reason_text}'Irreversible. No hi ha des-anul·lació: anul·lar per segona vegada
el mateix pagament retorna 422 payment_already_reversed, perquè
trepitjar el primer rastre esborraria el registre de per què la factura
va deixar d'estar cobrada. Si els diners van tornar a entrar, registra
un pagament nou.
La ruta està imbricada a propòsit: a l'API v1 un pagament de venda només
existeix penjant de la seva factura. Creuar una factura i un pagament que
no es corresponen retorna 404 —no 422—, igual que un pagament d'una
altra empresa: els tres casos col·lapsen en la mateixa resposta sense
revelar quin d'ells era.
Errors de l'anul·lació
| Estat | Codi | Significat |
|---|---|---|
422 | payment_reversal_reason_invalid | reason és fora del catàleg tancat (param: "reason"). |
422 | payment_reversal_invalid | note no és vàlida — p. ex. supera els 500 caràcters (param: "note"). |
422 | payment_already_reversed | El pagament ja estava anul·lat. |
404 | resource_not_found | Factura o pagament desconeguts, parella que no es correspon, o dades d'una altra empresa. |
L'anul·lació necessita el scope invoices:write, el mateix que registra
un pagament. No existeix una família de scopes payments:*: a l'API
pública un pagament de venda és part de la seva factura, i la credencial
que pot cobrar una factura és la que pot desfer aquest cobrament.
Anul·lació o rectificativa?
Aquesta és la decisió que importa, i equivocar-se té conseqüències fiscals. Anul·lar un pagament no és emetre una factura rectificativa.
- Els diners van tornar, però l'operació no es minora — una devolució
de rebut, una retrocessió de targeta, un efecte impagat, un pagament
imputat a la factura equivocada. El client té els seus diners de
tornada, però tu vas vendre el que vas vendre i encara t'ho deuen.
Anul·la el pagament: el deute continua viu, la factura torna a
sento aoverduei reapareix al teu pendent de cobrament. Els ingressos no canvien, així que no hi ha res a rectificar. - El que es minora és l'operació — un reemborsament genuí, un
descompte concedit a posteriori, una devolució de mercaderia, un import
mal facturat. Aquí els ingressos sí que baixen, i això és el que
documenta una factura rectificativa
(
POST /v1/invoices/{id}/corrective). La factura original conserva el seu cobrament; la rectificativa és el document que minora la base imposable.
| Què ha passat | Anul·lar el pagament | Factura rectificativa |
|---|---|---|
| Devolució de rebut SEPA | direct_debit_return | — |
| Retrocessió o disputa de targeta | card_dispute | — |
| Efecte impagat | bounced_effect | — |
| Pagament imputat a la factura equivocada | misapplied_payment | — |
| Entrada que mai no va correspondre a diners reals | recording_error | — |
| Reemborsament genuí al client | — | Sí |
| Descompte posterior, devolució de mercaderia | — | Sí |
| Import, impost o client equivocats | — | Sí |
Una anul·lació mai no emet una rectificativa, i mai no toca el registre VeriFactu: la factura que vas enviar continua sent la factura que vas enviar. L'única cosa que va canviar va ser el seu cobrament.
Un rebut retornat no és un crèdit incobrable. Anul·lar un pagament no minora el teu IVA repercutit i no és la via de l'art. 80.Cuatro LIVA, que té els seus propis requisits formals — reclamació judicial o requeriment notarial, els seus propis terminis i una comunicació a l'Administració. Una integració que reaccioni a una devolució emetent una rectificativa declararà una minoració d'ingressos que no ha passat.
L'esdeveniment payment.reversed
Cada anul·lació emet payment.reversed, perquè una integració
s'assabenti que un cobrament s'ha desfet sense haver-ho d'anar a
preguntar. El payload porta el pagament anul·lat sota data.object més
un bloc data.reversal amb el reason i l'origin: gateway quan va
ser la passarel·la de pagament la que va informar de la devolució,
disputa o retrocessió (ja hi va haver moviment real al banc), i manual
quan ho va registrar una persona.
Subscriu-t'hi allà on ja reacciones a payment.received: una factura que
vas donar per cobrada pot deixar d'estar-ho i, fins que aquest
esdeveniment va existir, no hi havia manera d'assabentar-se'n. Consulta
Esdeveniments per al payload i
Webhooks per a l'entrega i la signatura.
Pagaments de factura de compra
Les factures de compra mantenen el seu propi ledger (total_retention, la
retenció IRPF agregada, viu al recurs de la factura de compra). El contracte
és asimètric respecte al de venda — llegeix-lo amb atenció abans de
reutilitzar codi:
POST /v1/purchase_invoices/{id}/paymentsretorna201amb el pagament creat sotadata(objectepurchase_invoice_payment), no la factura completa.GET /v1/purchase_invoices/{id}/paymentsretorna{ "data": [...] }, del més recent al més antic.- No hi ha endpoint d'anul·lació al costat de compra: anul·lar és una operació de la factura de venda, perquè el que restitueix és un deute a favor teu.
- El body afegeix un
bank_account_idopcional (enter), i aquípayment_methodés un string lliure (màx. 30 caràcters), no l'enum tancat que es fa servir al costat de venda.
| Camp | Tipus | Requerit | Notes |
|---|---|---|---|
amount | number | Sí | Més gran que 0. No pot superar l'import pendent. |
paid_on | string (YYYY-MM-DD) | Sí | Entre la data d'emissió i avui. |
payment_method | string | Sí | Text lliure, màx. 30 caràcters. |
bank_account_id | integer | No | Compte bancari des del qual es va fer el pagament. |
reference | string | No | La teva pròpia referència. |
notes | string | No | Nota interna lliure. |
{
"data": {
"id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0c",
"object": "purchase_invoice_payment",
"amount": 423.50,
"paid_on": "2026-05-21",
"payment_method": "transferencia",
"bank_account_id": 12,
"reference": "TRF-2026-0099",
"notes": null,
"created_at": "2026-05-21T09:00:00Z"
}
}import os, requests
resp = requests.post(
'https://api.factuarea.com/v1/purchase_invoices/01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a05/payments',
json={
'amount': 423.50,
'paid_on': '2026-05-21',
'payment_method': 'transferencia',
'bank_account_id': 12,
},
headers={'Authorization': f"Bearer {os.environ['FACTUAREA_API_KEY']}"},
)
resp.raise_for_status()
print(resp.json()['data']['id'])const res = await fetch(
'https://api.factuarea.com/v1/purchase_invoices/01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a05/payments',
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.FACTUAREA_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: 423.5,
paid_on: '2026-05-21',
payment_method: 'transferencia',
bank_account_id: 12,
}),
},
);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const { data } = await res.json();
console.log(data.id);curl -s -X POST \
https://api.factuarea.com/v1/purchase_invoices/01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a05/payments \
-H "Authorization: Bearer $FACTUAREA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 423.50,
"paid_on": "2026-05-21",
"payment_method": "transferencia",
"bank_account_id": 12
}' | jq '.data'Les regles de pagament de la factura de compra (BR-PUR-019) s'apliquen
com a 422: un import per sobre del saldo pendent
(subcode: "payment_exceeds_pending_amount"), una data fora de
data_emissió … avui (subcode: "invalid_payment_date"), o un pagament
sobre una factura cancel·lada (subcode: "purchase_invoice_not_payable").
Mètodes de pagament
GET /v1/payment-methods retorna el catàleg tancat que dona suport al camp
payment_method de venda, cadascun amb un value i una etiqueta llegible
(en castellà). És un catàleg d'enum global — no específic d'empresa.
{
"data": [
{ "value": "bank_transfer", "label": "Transferencia bancaria" },
{ "value": "direct_debit", "label": "Domiciliación bancaria" },
{ "value": "cash", "label": "Efectivo" },
{ "value": "credit_card", "label": "Tarjeta de crédito" },
{ "value": "check", "label": "Cheque" },
{ "value": "paypal", "label": "PayPal" },
{ "value": "other", "label": "Otro" }
]
}Llegeix-lo un cop en arrencar i mostra les etiquetes a la teva interfície;
retorna el value a payment_method.
Errors
422payment_exceeds_pending_amount— l'import és més gran que el saldo pendent (param: "amount"). És una violació de regla de negoci, així que és422, mai409.422payment_reversal_reason_invalid— elreasonde l'anul·lació és fora del catàleg tancat (param: "reason").422payment_reversal_invalid— lanotede l'anul·lació no és vàlida, p. ex. supera els 500 caràcters (param: "note").422payment_already_reversed— aquest pagament ja estava anul·lat; registra'n un de nou en comptes de desfer l'anul·lació.409en unPOSTde pagament es reserva per a l'embolcall estàndard d'idempotència / conflicte (unIdempotency-Keyreutilitzat amb un body diferent, o un conflicte de concurrència) — no per a les dades del pagament en si.
Consulta Errors per a l'embolcall complet i el catàleg de codis.