URL: /revenue/failed-payments · Id: `page:/revenue/failed-payments`

# Failed payments: recover the card

> Failed payments: retry the card, send one card-update note, and keep every other ask away meanwhile.

A failed card is retried and the [Customer](https://startupbuilder.dev/business/nouns/customer) gets one note to update it, before the [Subscription](https://startupbuilder.dev/business/nouns/subscription) lapses.

```text title="Ask"
Alder & Co’s card failed. Recover it before the Subscription lapses. Don’t send.
```

```ts title="SDK"
import { $ } from 'startup-builder'

const { invoices } = await $.Invoice.list({ where: 'status = "open" and attempts > 0', sort: 'due' })   // 2 Customers past due; Alder & Co since Oct 17
await $.Invoice.collect({ invoice: 'invoice_88' }, { mode: 'try' })   // the retry waits for you: Billing is capped at You approve each one
await $.Approval.approve({ approval: 'approval_81' }, { mode: 'try' })
await $.Message.schedule({ message: 'message_91', at: '2026-10-20T14:00:00Z' }, { mode: 'try' })   // the card-update note, with the link to their billing page
await $.Message.schedule({ message: 'message_92', at: '2026-10-20T15:00:00Z' }, { mode: 'try' })   // an upgrade note from usage-upsell, the same day
// 422 · The upgrade note to Alder & Co did not send: their card failed on Oct 17.
// keep-first: Let their CSM reach them first, then ask again once they are healthy. · then Task.next
return await $.Metric.query({ metric: 'churned-arr', shape: 'trend', window: '28d' })   // Not live yet, gaps #4
```

```json title="Response · sample"
{
  "metric": {
    "$id": "https://api.sb/leaseline.app/metrics/churned-arr",
    "$type": "Metric",
    "name": "Churned ARR"
  },
  "rows": [
    {
      "window": "28d",
      "value": -1388,
      "customers": 4,
      "failedCard": 0
    }
  ]
}
```

`2 of 3 failed cards recovered in 28 days · 0 Customers lost to a failed card · ■□□□ Too early to tell · n 3`

`Metric.query({"metric":"churned-arr","shape":"trend","window":"28d"})` · sample · leaseline.app at 2026-10-19

## What it does
| What it does | Price per unit | Moves | Who does it | Today |
| --- | --- | --- | --- | --- |
| The `dunning` Workflow retries an open Invoice with <Op verb="Invoice.collect" /> and sends one card-update note; Billing is capped at You approve each one. | $0.04 per note (Worker cost); a retry costs nothing here | [Save rate](https://startupbuilder.dev/agents/customer#save-rate), [Lost Customers](/revenue/churned-arr) | [Billing](/capabilities/how-your-agents-act#billing) | 5 of 6 calls live |

## Set it up
| Check (dry run on a 0-Customer Startup, gaps #8) | Fix | Then | Your time | Wait |
| --- | --- | --- | --- | --- |
| [`billing-connected`](/code/refusals#check-billing-connected) (Not live yet, gaps #8) · fail | Connect Stripe so every charge and Meter reading is live. | <Op verb="Integration.connect" /> | 5 min | the first sync, about 10 min |
| [`role-unstaffed`](/code/refusals#check-role-unstaffed) (Not live yet, gaps #8) · warn: Billing has no person: every charge comes to you | Staff Billing, or leave it: every retry comes to you to approve. | <Op verb="Role.staff" /> | 0 min | none |

## The records
```json title="Alder & Co’s open Invoice"
{
  "$id": "https://api.sb/leaseline.app/invoices/invoice_88",
  "$type": "Invoice",
  "number": "LL-0088",
  "total": 29,
  "due": "2026-10-17",
  "status": "open",
  "collection": "charge"
}
```

## Calls
| Call | Here | Emits | Status |
| --- | --- | --- | --- |
| <Op verb="Invoice.list" /> | 2 Customers past due; Alder & Co since Oct 17 | — | live |
| <Op verb="Invoice.collect" /> | the retry waits for you: Billing is capped at You approve each one | `Invoice.collected` | live |
| <Op verb="Approval.approve" /> |  | `Approval.approved` | live |
| <Op verb="Message.schedule" /> | the card-update note, with the link to their billing page | `Message.scheduled` | live |
| <Op verb="Message.schedule" /> | an upgrade note from usage-upsell, the same day | `Message.scheduled` | live |
| <Op verb="Metric.query" /> |  | `Metric.queried` | Not live yet, gaps #4 |

## Events
| Event | When it fires | What hears it |
| --- | --- | --- |
| `Invoice.collected` | the retry clears | ends dunning; the Customer leaves past due |
| `Approval.approved` | you approve the retry | runs <Op verb="Invoice.collect" /> |
| `Task.offered` | a Subscription turns past due | the CSM’s queue within 1 business day |

## Refuses when

```json title="The 422 · sample"
{
  "type": "https://startupbuilder.dev/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "The upgrade note to Alder & Co did not send: their card failed on Oct 17.",
  "checks": [
    {
      "check": "keep-first",
      "status": "fail",
      "doc": "/code/refusals#check-keep-first",
      "fix": "Let their CSM reach them first, then ask again once they are healthy.",
      "then": "Task.next"
    }
  ]
}
```

| Check | Refuses | Fix | Then |
| --- | --- | --- | --- |
| [`keep-first`](/code/refusals#check-keep-first) (Not live yet, gaps #8) | an upgrade, referral, review or reference ask to a Customer who is at risk, past due or leaving | Let their CSM reach them first, then ask again once they are healthy. | <Op verb="Task.next" /> |
| [`billing-connected`](/code/refusals#check-billing-connected) (Not live yet, gaps #8) | a Customer-side Play with no live billing records | Connect Stripe so every charge and Meter reading is live. | <Op verb="Integration.connect" /> |

## Plays that use it
| Play | What it does | Use it when |
| --- | --- | --- |
| `failed-payment-recovery` (Not live yet, gaps #4) | The Play that recovers a failed card before the Subscription lapses. | today: The hub Workflow dunning: <Op verb="Invoice.collect" /> retries, then one card-update note. |

## Hub guides it deepens
| On startupbuilder.dev | Id |
| --- | --- |
| [Dunning](https://startupbuilder.dev/workflows/processes/billing/dunning) | `workflow:dunning` |
| [Billing](https://startupbuilder.dev/workflows/processes/billing) | `process:billing` |

## Example
```text title="Leaseline, as the fixture ran it"
Alder & Co · card failed Oct 17 · retry asked Oct 18, waiting on your OK · card-update note Oct 20 · an upgrade note the same day refused (keep-first) · 2 of 3 failed cards recovered in 28 days · sample
```