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

# 0015 retention auto churn

# A plan unpaid 60 days is canceled in Stripe and churned as Non-payment

Status: accepted

Date: 2026-10-20

## Decision

1. The rule is `planAutoChurn` in `lib/retentionBilling.ts`. A plan qualifies when
   the billing check plans row 3 of ADR 0014 (past due), and its oldest open
   invoice is 60 or more Denver days old. Row 3 already excludes:
   * arrangement labels that hold the card;
   * the app's paid-through arrangement;
   * a competing subscription;
   * any billing review.
2. A qualifying plan is **held** for staff when any of these holds:
   * the plan is not monthly;
   * Stripe shows a payment after the first missed invoice, or an open invoice is
     partly paid (Stripe does not record when that money arrived);
   * the card is in the On Hold stage, or the app moved it to Critical from On Hold;
   * Hold Status is anything other than blank, Active or MIA, including unreadable;
   * any charge on the Stripe customer was disputed, or the dispute list cannot be
     read (that failure never stops the rest of the billing check);
   * the card's stage move needs staff review: a closed or Churned card, a restart
     (ADR 0014 row 10), or a restored stage.
3. With `RETENTION_AUTO_CHURN_ENABLED` and `RETENTION_BILLING_WRITES_ENABLED` both
   `true`, the app takes these steps, in order:

   1. It claims one of at most 10 cancellations in any 24 hours.
   2. It re-checks, right before writing, that the plan can still bill, that the
      invoice that started the clock is still open and wholly unpaid, that no
      invoice was paid since the first miss, and that the CRM card still qualifies
      (re-planned from its fields, stage, hold and any new dispute). Otherwise it
      releases the claim and writes nothing.
   3. It cancels the subscription immediately, with no final invoice and no
      proration, and reads it back.
   4. It voids every unpaid open invoice on the account-bound plans, one at a time.
      A lost response is settled by reading the invoice back.

   A partly paid invoice is left open. When either flag is off, each check saves
   the report on `retentionBillingState.autoChurn` and writes nothing to Stripe.
4. The CRM card is not written in the same check. The next check sees the canceled
   plan and churns the card through row 6 of ADR 0014, with the lost reason
   **Non-payment -- 60+ days**. The reason is matched by its exact name. If it is
   missing or the list cannot be read, the card closes without it.
5. For a plan the app canceled this way, service ends at the latest paid invoice
   period, or at a staff paid-through arrangement if that is later, rather than at
   the cancellation time. The card therefore churns on the next check, and its
   paid-through date is the last period the owner paid for. Every other
   cancellation keeps ADR 0014's service end.
6. The app makes one attempt per plan. A cancellation Stripe does not confirm is
   saved as `failed` and is never retried automatically. Once the cancellation is
   confirmed, a failed void does not undo it.

This replaces the FM-2888 position from 2026-10-17 that a past-due owner stays Past
due and Critical until they pay or staff cancel. The app writes to Stripe only here.

## Context

Kevin set the rule on 2026-10-20: 60 days after the first missed payment, if the
owner is not on hold and there is no other evidence, the card closes as lost, the
Stripe subscription is canceled, and every failed-payment invoice is voided. Owners
with no billing stay manual.

A one-time cleanup applied the same rule by hand that day. It covered 62 owners,
including unlinked ones and both Stripe accounts:

* 155 invoices voided (\$200,560);
* 2 plans canceled;
* 13 cards closed.

That run produced the refinements recorded above:

* **Clock:** it starts at the oldest open invoice on the live plan, not the last
  payment. Edgar Hicks's extension would otherwise have closed him.
* **Membership only:** only membership plans count. This rule reads only the
  account-bound plans.
* **Recent payment:** a payment after the first miss holds the owner.

## Considered options

* **Close the card first, then cancel in Stripe.** Rejected: a failed Stripe step
  would leave a closed card on a plan that is still charging. That was the Evan
  Torkelson and Michael Hicks problem the cleanup found.
* **Void only invoices older than 60 days.** Rejected (Kevin): once the plan is
  canceled, newer invoices bill for service that was never given. Stripe would keep
  sending reminders for them.
* **Hold on a recent Slack message or a staff edit in the last 14 days.** Rejected
  for the app: neither signal is available to the billing check. Staff hold an owner
  with the On Hold stage or Hold Status, which the app reads.
* **Use the cancellation time as the service end.** Rejected: the card would sit at
  Canceled for a day, then show a paid-through date the owner never paid for.

## Consequences

* Rollout is report first. Each check saves `autoChurn` with status `due` or
  `held`, and the Billing panel shows it. Turn the flag on only after the report
  matches what staff expect. Unsetting it stops new Stripe writes at the next check.
* Canceling a plan may suspend the owner's software through the CRM's SaaS
  settings. Depending on Stripe settings, it may also send a cancellation email.
  Voiding sends nothing.
* The rule covers only owners the app has linked, and only the My Funding Machine
  Stripe account. Other owners still need a person or another one-time run.
* Known gaps:
  * A `canceling` claim whose result was never saved counts as an app
    cancellation for its service end and lost reason. It counts toward the daily
    limit and is not retried.
  * A partly paid invoice holds the plan, and if one appears after the
    cancellation it stays open for staff.
  * A void that still fails after readback is not retried. The plan is already
    canceled, and ADR 0014 treats open invoices on a canceled plan as void.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.