diff --git a/website/docs/guides/invoices/async-invoices.mdx b/website/docs/guides/invoices/async-invoices.mdx index 810a44898..750b78f80 100644 --- a/website/docs/guides/invoices/async-invoices.mdx +++ b/website/docs/guides/invoices/async-invoices.mdx @@ -11,6 +11,17 @@ Cuando el tamaño de tus facturas es muy grande, o necesitas facturar muchas fac y recibir una respuesta inmediata con el objeto Invoice y su ID, pero sin timbrar todavía. El timbrado de la factura se realiza en segundo plano, y puedes consultar el estado de la factura en cualquier momento por medio del método de obtener factura. +## Recuperación automática tras un error temporal + +Al crear una factura de forma síncrona, el SAT o el proveedor de certificación puede devolver un error temporal después de haber timbrado el CFDI. Cuando Facturapi detecta uno de estos casos, conserva la factura con `status: "pending"` y responde con el código HTTP `202 Accepted` en lugar de devolver un error definitivo. Esta respuesta significa que la solicitud fue aceptada, pero su procesamiento todavía no ha terminado. + +Facturapi intentará recuperar el CFDI automáticamente hasta 5 veces, con un intervalo de 10 minutos entre intentos. Los intentos ocurren aproximadamente 10, 20, 30, 40 y 50 minutos después de la respuesta inicial. + +- Si Facturapi recupera el CFDI, la factura cambia a `status: "valid"`, se guardan su UUID y timbre, y se envía el webhook `invoice.status_updated`. +- Si no es posible recuperarlo después del quinto intento, la factura cambia a `status: "failed"` y no se realizan más intentos. + +Mientras la factura permanezca en `pending`, no vuelvas a crearla ni asignes su folio a otro comprobante. Puedes consultar su estado mediante el método de obtener factura o suscribirte al webhook `invoice.status_updated`. + ## Crear una factura asíncrona Una factura se creará de manera asíncrona en cualquiera de estos 2 casos: diff --git a/website/i18n/en/docusaurus-plugin-content-docs/current/guides/invoices/async-invoices.mdx b/website/i18n/en/docusaurus-plugin-content-docs/current/guides/invoices/async-invoices.mdx index ef4c52468..b7c5ba0c7 100644 --- a/website/i18n/en/docusaurus-plugin-content-docs/current/guides/invoices/async-invoices.mdx +++ b/website/i18n/en/docusaurus-plugin-content-docs/current/guides/invoices/async-invoices.mdx @@ -11,6 +11,17 @@ When the size of your invoices is very large, or you need to invoice many invoic and receive an immediate response with the Invoice object and its ID, but without stamping yet. The stamping of the invoice is done in the background, and you can check the status of the invoice at any time using the get invoice method. +## Automatic recovery after a temporary error + +When creating an invoice synchronously, the SAT or the certification provider may return a temporary error after stamping the CFDI. When Facturapi detects one of these cases, it preserves the invoice with `status: "pending"` and responds with HTTP `202 Accepted` instead of returning a definitive error. This response means that the request was accepted, but processing has not finished yet. + +Facturapi will automatically try to recover the CFDI up to 5 times, with a 10-minute interval between attempts. The attempts occur approximately 10, 20, 30, 40, and 50 minutes after the initial response. + +- If Facturapi recovers the CFDI, the invoice changes to `status: "valid"`, its UUID and stamp are saved, and the `invoice.status_updated` webhook is sent. +- If it cannot be recovered after the fifth attempt, the invoice changes to `status: "failed"` and no further attempts are made. + +While the invoice remains `pending`, do not create it again or assign its folio to another document. You can check its state using the get invoice method or subscribe to the `invoice.status_updated` webhook. + ## Create an asynchronous invoice An invoice will be created asynchronously in either of these 2 cases: diff --git a/website/openapi_v2.en.yaml b/website/openapi_v2.en.yaml index 4948857b8..5d9b5b4a0 100644 --- a/website/openapi_v2.en.yaml +++ b/website/openapi_v2.en.yaml @@ -3031,6 +3031,27 @@ paths: summary: Create invoice (CFDI 4.0) description: | Creates a new Invoice. If the invoice is created in the Live environment, it will be **stamped and sent to the SAT**. + + ### Recovery from intermittent stamping errors + + If PADE returns a recognized intermittent error during stamping, Facturapi preserves the invoice with `status: "pending"`, no UUID, and its original CFD signature. The folio remains reserved for six hours, and the API responds with `202 Accepted`. This means that the request was accepted, but processing has not finished. Validation errors and other errors that are not eligible for recovery are returned normally. + + Facturapi saves a snapshot of the data needed to validate a recovery: issuer and receiver RFCs, series, folio, total, currency, CFDI type, signature, and issuance date. Ten minutes later, it asks PADE for CFDIs matching the issuer RFC, series, and folio, then validates every result against the snapshot to prevent associating the wrong CFDI. + + Facturapi makes up to five recovery attempts, one every 10 minutes. The original request at minute 0 does not count as an attempt: + + | Time | Action | + | --- | --- | + | Minute 0 | The original stamping request fails intermittently; the API returns `202` | + | Minute 10 | Recovery attempt 1 | + | Minute 20 | Recovery attempt 2 | + | Minute 30 | Recovery attempt 3 | + | Minute 40 | Recovery attempt 4 | + | Minute 50 | Recovery attempt 5 | + + If a valid CFDI is recovered, Facturapi stores the UUID, stamp, and XML; marks the invoice as `valid`; consumes the reserved folio; records usage and accounting relationships; marks the recovery task as succeeded; emits the invoice status update event; and stops retrying. + + If the fifth attempt does not recover a valid CFDI, Facturapi marks the recovery task and the invoice as `failed`, releases the reserved folio, and stops retrying. The `202` status code only communicates that the recovery task will continue in the background; the status code itself does not initiate retries. x-codeSamples: - lang: Bash label: cURL @@ -3196,6 +3217,21 @@ paths: mapping: pending: "#/components/schemas/Invoice" draft: "#/components/schemas/InvoiceDraft" + "202": + description: Request accepted; Facturapi will try to recover the CFDI up to five times, once every 10 minutes + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/Invoice" + - type: object + required: + - status + properties: + status: + type: string + enum: [pending] + example: pending "400": $ref: "#/components/responses/BadRequest" "401": @@ -10826,7 +10862,7 @@ x-webhooks: description: | Notifies about changes in the `status` field of an invoice. - Used in the case of having created the invoice asynchronously (`async` param). + Used when an invoice is created asynchronously (`async` param) or when a recovery task for an intermittent stamping error changes its status. tags: - events requestBody: @@ -14614,8 +14650,9 @@ components: - valid - canceled - draft + - failed description: | - Current status of the invoice. + Current status of the invoice. `failed` means that background stamping or automatic CFDI recovery ended unsuccessfully. example: valid cancellation_status: type: string diff --git a/website/openapi_v2.yaml b/website/openapi_v2.yaml index ed060b82a..842216858 100644 --- a/website/openapi_v2.yaml +++ b/website/openapi_v2.yaml @@ -3257,6 +3257,27 @@ paths: summary: Crear factura (CFDI 4.0) description: | Crea una nueva Factura. Si la factura es creada en ambiente Live, ésta será **timbrada y enviada al SAT**. + + ### Recuperación ante intermitencias de timbrado + + Si PADE devuelve un error intermitente reconocido durante el timbrado, Facturapi conserva la factura con `status: "pending"`, sin UUID y con su sello CFD original. El folio queda reservado durante seis horas y la API responde con `202 Accepted`. Esto significa que la solicitud fue aceptada, pero su procesamiento todavía no termina. Los errores de validación y demás errores que no sean elegibles para recuperación se devuelven de forma normal. + + Facturapi guarda una copia de los datos necesarios para validar la recuperación: RFC del emisor y receptor, serie, folio, total, moneda, tipo de CFDI, sello y fecha de emisión. Diez minutos después consulta a PADE por los CFDI que coincidan con el RFC del emisor, la serie y el folio, y valida cada resultado contra esos datos para evitar asociar un CFDI incorrecto. + + Se realizan hasta cinco intentos de recuperación, uno cada 10 minutos. La solicitud original del minuto 0 no cuenta como intento: + + | Tiempo | Acción | + | --- | --- | + | Minuto 0 | El timbrado original falla por una intermitencia; la API responde `202` | + | Minuto 10 | Intento de recuperación 1 | + | Minuto 20 | Intento de recuperación 2 | + | Minuto 30 | Intento de recuperación 3 | + | Minuto 40 | Intento de recuperación 4 | + | Minuto 50 | Intento de recuperación 5 | + + Si se recupera un CFDI válido, Facturapi guarda el UUID, el timbre y el XML; marca la factura como `valid`; consume el folio reservado; registra el uso y las relaciones contables; marca la tarea de recuperación como exitosa; emite el evento de actualización de estado de la factura; y detiene los reintentos. + + Si el quinto intento no recupera un CFDI válido, Facturapi marca la tarea de recuperación y la factura como `failed`, libera el folio reservado y deja de reintentar. El código `202` solo comunica que la tarea de recuperación continuará en segundo plano; el código de estado no es lo que inicia los reintentos. x-codeSamples: - lang: Bash label: cURL @@ -3422,6 +3443,21 @@ paths: mapping: pending: "#/components/schemas/Invoice" draft: "#/components/schemas/InvoiceDraft" + "202": + description: Solicitud aceptada; Facturapi intentará recuperar el CFDI hasta cinco veces, una cada 10 minutos + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/Invoice" + - type: object + required: + - status + properties: + status: + type: string + enum: [pending] + example: pending "400": $ref: "#/components/responses/BadRequest" "401": @@ -11032,7 +11068,7 @@ x-webhooks: description: | Notifica acerca del cambio del campo `status` de una factura. - Se utiliza en el caso de haber creado la factura de manera asíncrona. + Se utiliza cuando la factura se crea de manera asíncrona o cuando una tarea de recuperación por intermitencia de timbrado cambia su estado. tags: - events requestBody: @@ -14838,8 +14874,9 @@ components: - valid - canceled - draft + - failed description: | - Estado actual de la factura. + Estado actual de la factura. `failed` indica que el timbrado en segundo plano o la recuperación automática del CFDI terminó sin éxito. example: valid cancellation_status: type: string