> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dodopayments.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Gestione dei Fallimenti di Pagamento

> Rileva i pagamenti falliti dai webhook e dall'API, leggi il motivo del fallimento, mostralo in modo sicuro ai clienti e decidi quando riprovare o raccogliere un nuovo metodo di pagamento.

<Info>
  Quando un pagamento fallisce, Dodo Payments ti comunica **perché** attraverso un `error_code` standardizzato e un `error_message` leggibile. Questa guida mostra come leggere quei campi, decidere se vale la pena riprovare e recuperare il pagamento senza esporre informazioni sensibili ai clienti.
</Info>

## Come Dodo Payments Riporta un Fallimento

Ogni pagamento fallito — sia un checkout una tantum che un rinnovo di abbonamento — porta gli stessi campi di fallimento sull'oggetto di pagamento:

| Campo           | Tipo           | Descrizione                                                                                                                                                                                            |
| --------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `status`        | string         | `failed` per un pagamento fallito. Altri stati non di successo includono `cancelled`, `requires_customer_action`, e `requires_payment_method`.                                                         |
| `error_code`    | string \| null | Il motivo standardizzato del fallimento, per esempio `INSUFFICIENT_FUNDS` o `PROCESSING_ERROR`. Vedi il riferimento [Transaction Failures](/api-reference/transaction-failures) per la lista completa. |
| `error_message` | string \| null | Una spiegazione leggibile del fallimento.                                                                                                                                                              |
| `retry_attempt` | integer        | `0` per l'addebito originale. `1` o superiore identifica un tentativo di rinnovo abbonamento programmato.                                                                                              |

<Note>
  `error_code` e `error_message` sono `null` finché un pagamento non fallisce effettivamente. Controlla sempre prima `status`, poi leggi i campi di errore.
</Note>

## Il Webhook `payment.failed`

Il modo più affidabile per rilevare un fallimento è il webhook `payment.failed`. L'evento avvolge l'intero oggetto di pagamento in `data`:

```json payment.failed payload expandable theme={null}
{
  "business_id": "bus_P3SXLcppjXgagmHS",
  "type": "payment.failed",
  "timestamp": "2025-08-04T05:36:41.609359Z",
  "data": {
    "payload_type": "Payment",
    "payment_id": "pay_2IjeQm4hqU6RA4Z4kwDee",
    "status": "failed",
    "error_code": "PROCESSING_ERROR",
    "error_message": "An error occurred while processing your card. Try again in a little bit.",
    "retry_attempt": 0,
    "subscription_id": null,
    "currency": "USD",
    "total_amount": 400,
    "payment_method": "card",
    "card_last_four": "0119",
    "card_network": "VISA",
    "payment_link": "https://test.checkout.dodopayments.com/cbq",
    "customer": {
      "customer_id": "cus_8VbC6JDZzPEqfB",
      "email": "test@acme.com",
      "name": "Test user"
    }
  }
}
```

Un gestore minimale legge `error_code` e instrada su di esso:

<CodeGroup>
  ```javascript Node.js expandable theme={null}
  import { Webhook } from "standardwebhooks";
  import express from "express";

  const app = express();
  // Mount the raw body parser so the exact payload is available for verification
  app.use(express.raw({ type: "application/json" }));

  const webhook = new Webhook(process.env.DODO_PAYMENTS_WEBHOOK_KEY);

  app.post("/webhooks/dodo", async (req, res) => {
    // Verify the signature against the raw body before trusting the payload
    const payload = req.body.toString();
    await webhook.verify(payload, req.headers);

    const event = JSON.parse(payload);

    if (event.type === "payment.failed") {
      const payment = event.data;

      console.log(
        `Payment ${payment.payment_id} failed: ${payment.error_code} (${payment.error_message})`
      );

      if (payment.subscription_id) {
        // Subscription renewal — Dodo retries soft declines for you
        await flagSubscriptionPaymentIssue(payment.subscription_id, payment.error_code);
      } else {
        // One-time payment — prompt the customer to try again
        await notifyCustomerOfFailedPayment(payment.customer.customer_id, payment.error_code);
      }
    }

    res.json({ received: true });
  });
  ```

  ```python Python expandable theme={null}
  import os
  from fastapi import FastAPI, Request
  from standardwebhooks import Webhook

  app = FastAPI()
  webhook = Webhook(os.environ["DODO_PAYMENTS_WEBHOOK_KEY"])

  @app.post("/webhooks/dodo")
  async def handle_webhook(request: Request):
      # Verify the signature before trusting the payload
      payload = await request.body()
      webhook.verify(payload, dict(request.headers))

      event = await request.json()

      if event["type"] == "payment.failed":
          payment = event["data"]

          print(
              f"Payment {payment['payment_id']} failed: "
              f"{payment['error_code']} ({payment['error_message']})"
          )

          if payment["subscription_id"]:
              # Subscription renewal — Dodo retries soft declines for you
              flag_subscription_payment_issue(payment["subscription_id"], payment["error_code"])
          else:
              # One-time payment — prompt the customer to try again
              notify_customer_of_failed_payment(payment["customer"]["customer_id"], payment["error_code"])

      return {"received": True}
  ```
</CodeGroup>

<Tip>
  Verifica sempre la firma del webhook prima di elaborarlo. Vedi la [guida Webhooks](/developer-resources/webhooks) per l'intero setup, compresa la verifica della firma e l'idempotenza.
</Tip>

## Decidi se Riprovare: Declini Soft vs. Hard

`error_code` ti dice se vale la pena riprovare con lo stesso metodo di pagamento.

| Tipo di declino  | Cosa significa                                                                                                        | Cosa fare                                                                                               |
| ---------------- | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| **Declino soft** | Temporaneo o correggibile (per esempio `INSUFFICIENT_FUNDS`, `PROCESSING_ERROR`, `NETWORK_ERROR`, `TRY_AGAIN_LATER`). | Riprovare, dopo un ritardo o una volta che il cliente ha corretto il proprio input, può avere successo. |
| **Declino hard** | Terminale (per esempio `STOLEN_CARD`, `LOST_CARD`, `DO_NOT_HONOR`, `FRAUDULENT`).                                     | Non riprovare con lo stesso carta. Chiedi al cliente un diverso metodo di pagamento.                    |

Il riferimento [Transaction Failures](/api-reference/transaction-failures) elenca il tipo di declino e l'azione consigliata per ogni `error_code`.

## Gestire i Fallimenti al Checkout vs. al Rinnovo

Come si recupera dipende dal fatto che il cliente sia presente.

<Tabs>
  <Tab title="At checkout (customer present)">
    Il cliente sta effettuando attivamente il checkout. Mostra un messaggio chiaro e lascia che riprovino immediatamente o utilizzino un'altra carta.

    * `requires_payment_method` — il cliente non ha mai fornito un metodo di pagamento: non ha inserito i dettagli della carta o è stato richiesto di farlo e non ha agito. Questo è di solito un **abbandono del checkout**, non un rifiuto: re-ingaggia il cliente per completare il pagamento (vedi [Abandoned Cart Recovery](/features/recovery/abandoned-cart-recovery)).
    * `requires_customer_action` — è necessaria un'autenticazione aggiuntiva (come 3DS); fai completare al cliente. Vedi [gestione 3D Secure](/features/payment-methods/cards#3d-secure-authentication).
  </Tab>

  <Tab title="On subscription renewal (customer not present)">
    Il cliente non è presente, quindi non puoi sollecitarlo in tempo reale. Quando un rinnovo fallisce, l'abbonamento passa a `on_hold` e `subscription.on_hold` si attiva.

    * **Declini soft** sono ritentati automaticamente da [Subscription Payment Retries](/features/recovery/payment-retries).
    * **Declini hard** (e riprove esaurite) sono meglio recuperati con [Subscription Dunning](/features/recovery/subscription-dunning), che invia un'email al cliente per aggiornare il proprio metodo di pagamento.

    Vedi la [Guida all'Integrazione degli Abbonamenti](/developer-resources/subscription-integration-guide#handling-subscription-on-hold) per il flusso completo da sospeso → riattivato.
  </Tab>
</Tabs>

## Riprovare un Pagamento Fallito

* **Abbonamenti:** Abilita [Subscription Payment Retries](/features/recovery/payment-retries) per recuperare declini soft senza lavoro di integrazione. Puoi anche innescare il recupero facendo aggiornare al cliente il proprio metodo di pagamento tramite l'[API di Aggiornamento Metodo di Pagamento](/api-reference/subscriptions/update-payment-method), che addebita eventuali importi in sospeso.
* **Pagamenti una tantum:** Invia nuovamente il checkout o `payment_link` affinché il cliente possa riprovare con un metodo diverso. Non esiste un ripetizione automatica per pagamenti una tantum.

<Warning>
  Non riprovare declini hard con la stessa carta. Le reti di carte possono segnalare ripetuti declini come abusivi, il che riduce il tuo tasso di autorizzazione.
</Warning>

## Comunicazione degli Errori ai Clienti in Sicurezza

Mostra ai clienti un messaggio amichevole — mai il vero `error_code`.

```javascript Customer-facing messaging expandable theme={null}
const CUSTOMER_MESSAGES = {
  INSUFFICIENT_FUNDS: "Your card has insufficient funds. Please use another card.",
  EXPIRED_CARD: "Your card has expired. Please use a card with a valid expiry date.",
  INCORRECT_CVC: "The security code (CVC) is incorrect. Please re-enter it.",
};

function customerMessage(errorCode) {
  // Sensitive declines must never reveal the real reason
  const SENSITIVE = ["STOLEN_CARD", "LOST_CARD", "PICKUP_CARD", "FRAUDULENT"];
  if (SENSITIVE.includes(errorCode)) {
    return "Your card was declined. Please contact your bank or use another card.";
  }
  return CUSTOMER_MESSAGES[errorCode] ?? "Your payment could not be processed. Please try another card.";
}
```

<Warning>
  **Non rivelare mai la vera ragione per `STOLEN_CARD`, `LOST_CARD`, `PICKUP_CARD`, o `FRAUDULENT`.** Mostrare questi può avvisare un attore fraudolento. Mostra un messaggio di declino generico e registra solo il specifico `error_code` internamente.
</Warning>

## Correlati

<CardGroup cols={2}>
  <Card title="Transaction Failures" icon="circle-exclamation" href="/api-reference/transaction-failures">
    Ogni codice di declino, il suo tipo e l'azione raccomandata.
  </Card>

  <Card title="Error Codes" icon="triangle-exclamation" href="/api-reference/error-codes">
    Errori di API e logica aziendale che non sono declini di carta.
  </Card>

  <Card title="Subscription Payment Retries" icon="arrow-rotate-right" href="/features/recovery/payment-retries">
    Recupero automatico dei declini soft sui rinnovi degli abbonamenti.
  </Card>

  <Card title="Subscription Dunning" icon="repeat" href="/features/recovery/subscription-dunning">
    Sequenze email che recuperano declini hard.
  </Card>

  <Card title="Payment Webhooks" icon="webhook" href="/developer-resources/webhooks/intents/payment">
    Schema completo del payload per eventi di pagamento.
  </Card>

  <Card title="Testing Failures" icon="flask" href="/miscellaneous/testing-process">
    Carte di test che simulano declini e fallimenti di rinnovo.
  </Card>
</CardGroup>
