---
id: CAP-MBR-MEM-013
title: Bill renewals in runs, on demand or on a schedule
feature: FEAT-MBR-MEM-05
product_area: membership
module: memberships
revision: 1
status: draft
product_intent: confirmed
implementation_evidence: partially-observed
supersedes:
  - 'inventory:CAP-MBR-BIL-001'
  - 'inventory:CAP-MBR-BIL-002'
  - 'inventory:CAP-MBR-BIL-003'
  - 'inventory:CAP-MBR-BIL-004'
  - 'inventory:CAP-MBR-BIL-005'
  - 'inventory:CAP-MBR-BIL-006'
  - 'inventory:CAP-MBR-BIL-007'
  - 'inventory:CAP-MBR-BIL-008'
  - 'inventory:CAP-MBR-BIL-009'
  - 'inventory:CAP-MBR-BIL-010'
  - 'inventory:CAP-MBR-BIL-011'
source_sha:
  services-membership: 5994c270e8a37a30f848e99ba5b5ee6b87c08893
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
intent_sources:
  - 'services-membership:README.md#2026-10-18 — Final acceptance'
  - 'services-membership:docs/migrations/participant/README.md#Billing runs'
purpose: >-
  Administrators can plan a renewal billing run over the terms ending in a range, review exactly what it will do,
  and execute it, once or on a schedule in the association's time zone.
outcomes:
  - Each renewing member gets one renewal order, one notice and each planned reminder, never twice.
  - Lapsed members are dropped with the run's reason.
  - Nothing is billed for a term that changed after it was planned.
concepts:
  - CON-MBR-MEM-005
  - CON-MBR-MEM-002
actors:
  - ACT-MBR-006
  - ACT-MBR-008
  - ACT-MBR-012
surfaces:
  - console
  - api
  - job
permissions:
  - key: membership.billing.read
    where_checked: api
    scope: tenant
  - key: membership.billing.manage
    where_checked: api
    scope: tenant
rules:
  - id: BR-CAP-MBR-MEM-013-01
    proved_by: [ATS-MBR-MEM-013-01, TST-MBR-MEM-072]
    statement: >-
      A billing template names its program, its filters and actions, and, when it drops lapsed terms, a status reason
      that applies to terminations.
    kind: validation
    enforced_at: [api, service]
    must_prove: true
    provenance: test
    source: 'services-membership:tests/acceptance/billing/MBR-ACC-0402.acceptance.ts#MBR-ACC-0402'
    origin: none
  - id: BR-CAP-MBR-MEM-013-02
    proved_by: [ATS-MBR-MEM-013-02, TST-MBR-MEM-086, TST-MBR-MEM-238]
    statement: >-
      A run is created from a template, copying its filters, actions and drop reason so the run is reproducible, or
      from its own, over a range of term end dates; a template of another program is refused.
    kind: policy
    enforced_at: [service]
    must_prove: true
    provenance: test
    source: 'services-membership:tests/e2e/billing-api.e2e.spec.ts#creates a run from the template'
    origin: none
  - id: BR-CAP-MBR-MEM-013-03
    proved_by: [ATS-MBR-MEM-013-03, TST-MBR-MEM-004, TST-MBR-MEM-006]
    statement: >-
      Planning on a business date takes the committed terms of principal memberships of the program ending in the
      range, of the types and packages the filters allow, and leaves out terms already renewed, terms that never end,
      suspended memberships, and flow-down or assigned memberships.
    kind: policy
    enforced_at: [service]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/billing/tests/unit/run-plan.spec.ts#leaves out a term already renewed (a later
      effective or pending term)
    origin: none
  - id: BR-CAP-MBR-MEM-013-04
    proved_by: [ATS-MBR-MEM-013-04, TST-MBR-MEM-006]
    statement: >-
      A candidate whose term ends on or after the run's date less its grace is renewing; one that ended earlier is
      lapsed.
    kind: calculation
    enforced_at: [service]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/billing/tests/unit/run-plan.spec.ts#classes a term ending before the date less its
      grace as lapsed
    origin: none
  - id: BR-CAP-MBR-MEM-013-05
    proved_by: [ATS-MBR-MEM-013-05, TST-MBR-MEM-006]
    statement: >-
      A renewing term is planned a renewal order, or an auto-renewal order and an expiring-card warning when the member
      auto-renews with a stored payment method, plus the notice and each reminder the run asks for; a lapsed term is
      planned a drop; do-not-renew and do-not-drop members are left alone unless the filters include them.
    kind: policy
    enforced_at: [service]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/billing/tests/unit/run-plan.spec.ts#plans an auto-renewal order and the expiring card
      warning for a stored payment method
    origin: none
  - id: BR-CAP-MBR-MEM-013-06
    proved_by: [ATS-MBR-MEM-013-06, TST-MBR-MEM-006, TST-MBR-MEM-089]
    statement: >-
      A plan counts its candidates, their classes, the exclusions and the planned actions; a replan adds only what is
      new.
    kind: calculation
    enforced_at: [service]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/billing/tests/unit/run-plan.spec.ts#counts the candidates, their classes, the
      exclusions and the planned actions
    origin: none
  - id: BR-CAP-MBR-MEM-013-07
    proved_by: [ATS-MBR-MEM-003-10, ATS-MBR-MEM-013-07, ATS-MBR-MEM-013-14, TST-MBR-MEM-004, TST-MBR-MEM-078]
    statement: >-
      Execution acts on each planned action once and settles it with its effect; an action whose term changed since it
      was planned is settled as stale and does nothing, and executing a run again does nothing.
    kind: invariant
    enforced_at: [service]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/billing/tests/integration/billing-runs.integration.spec.ts#refuses as stale an action
      whose term changed after planning
    origin: none
  - id: BR-CAP-MBR-MEM-013-08
    proved_by: [ATS-MBR-MEM-006-16, ATS-MBR-MEM-009-17, ATS-MBR-MEM-013-08, ATS-MBR-MEM-013-15, ATS-MBR-MEM-013-16, ATS-MBR-MEM-013-17, ATS-MBR-MEM-014-15, ATS-MBR-MEM-020-16, TST-MBR-MEM-004, TST-MBR-MEM-070]
    statement: >-
      A renewal order is prepared through Membership's own checkout preparation and placed with the action as its
      retry key, so an order Membership would refuse is never placed and a repeat never places two; a member who
      renewed meanwhile is skipped.
    kind: policy
    enforced_at: [service]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/billing/tests/integration/billing-runs.integration.spec.ts#executes on the jobs engine:
      one order per renewing candidate (Jo’s with both dues chapters), a notice and reminder each, one drop per lapsed term
    origin: none
  - id: BR-CAP-MBR-MEM-013-09
    proved_by: [ATS-MBR-MEM-006-16, ATS-MBR-MEM-009-17, ATS-MBR-MEM-013-08, ATS-MBR-MEM-013-15, ATS-MBR-MEM-013-16, ATS-MBR-MEM-013-17, ATS-MBR-MEM-014-15, ATS-MBR-MEM-020-16, TST-MBR-MEM-004, TST-MBR-MEM-070]
    statement: >-
      A drop terminates the lapsed term with the run's reason, effective on the run's date, and an expired term keeps
      its dates.
    kind: lifecycle
    enforced_at: [service]
    must_prove: true
    provenance: test
    source: 'services-membership:tests/acceptance/billing/MBR-ACC-0400.acceptance.ts#MBR-ACC-0400'
    origin: none
  - id: BR-CAP-MBR-MEM-013-10
    proved_by: []
    statement: >-
      A failed execution returns the run to planned, keeping the actions it settled, and an execution that never starts
      does not leave the run executing for ever.
    kind: lifecycle
    enforced_at: [service]
    must_prove: true
    provenance: unknown
    source: FND-MBR-MEM-048
    origin: none
  - id: BR-CAP-MBR-MEM-013-11
    proved_by: [ATS-MBR-MEM-013-09, TST-MBR-MEM-075, TST-MBR-MEM-076]
    statement: >-
      Plan, execute and cancel are made at the version read and only from a status that allows them; a run is
      cancelled only before it executes, and its planned actions stay as the record of what would have happened.
    kind: validation
    enforced_at: [service]
    must_prove: true
    provenance: test
    source: 'services-membership:tests/acceptance/billing/MBR-ACC-0406.acceptance.ts#MBR-ACC-0406'
    origin: none
  - id: BR-CAP-MBR-MEM-013-12
    proved_by: [ATS-MBR-MEM-013-10, TST-MBR-MEM-004, TST-MBR-MEM-085]
    statement: >-
      A recurring run creates one run per occurrence of its schedule, in the association's time zone, over the terms
      ending at the configured offsets from the occurrence's date; it plans it on that date and executes it when set to;
      a redelivered occurrence finds the run it created.
    kind: policy
    enforced_at: [service, database]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/billing/tests/integration/billing-runs.integration.spec.ts#creates and plans one run per
      occurrence of a recurring run, a redelivered occurrence finding it
    origin: none
  - id: BR-CAP-MBR-MEM-013-13
    proved_by: [ATS-MBR-MEM-013-11, TST-MBR-MEM-005]
    statement: >-
      Of two changes to a recurring run made at the same version, one applies and the other is refused.
    kind: invariant
    enforced_at: [service]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/billing/tests/integration/recurring-billing-run-concurrency.integration.spec.ts#applies
      one of two changes made at the same version and refuses the other with version_mismatch
    origin: none
  - id: BR-CAP-MBR-MEM-013-14
    proved_by: [ATS-MBR-MEM-013-12, TST-MBR-MEM-081, TST-MBR-MEM-238]
    statement: >-
      Readers may read templates, runs, actions and recurring runs; only billing managers create, plan, execute or
      cancel them.
    kind: permission
    enforced_at: [api]
    must_prove: true
    provenance: test
    source: 'services-membership:tests/e2e/billing-api.e2e.spec.ts#lets a reader read the run and refuses it the planning'
    origin: none
  - id: BR-CAP-MBR-MEM-013-15
    proved_by: [ATS-MBR-MEM-013-13, TST-MBR-MEM-088]
    statement: >-
      Notices, reminders and warnings go to a person's first e-mail address; an organization member, and a person
      with no address, is skipped as having no recipient.
    kind: policy
    enforced_at: [service]
    must_prove: true
    provenance: test
    source: 'services-membership:tests/acceptance/billing/MBR-ACC-0418.acceptance.ts#MBR-ACC-0418'
    origin: none
scenarios:
  - id: SC-CAP-MBR-MEM-013-01
    given: A renewal run planned on 15 March 2027 over the FAA members, inside Regular's 120-day window.
    when: It is executed, then executed again.
    then: Pat, Lee and Jo each get one order (Jo's with both chapters' dues), a notice and a reminder; Sam, Kim and Alex are dropped; the second execution does nothing.
    provenance: test
    source: 'services-membership:modules/billing/tests/integration/billing-runs.integration.spec.ts#does nothing on a second execution'
  - id: SC-CAP-MBR-MEM-013-02
    given: A monthly recurring notice run in the association's time zone.
    when: An occurrence is delivered twice.
    then: One run exists for that business day, planned on it.
    provenance: test
    source: 'services-membership:tests/acceptance/billing/MBR-ACC-0415.acceptance.ts#MBR-ACC-0415'
edge_cases: []
constraints:
  - A run's filters and actions are each at most 4 KB.
depends_on:
  - CAP-MBR-MEM-009
  - CAP-MBR-MEM-003
related:
  - CAP-MBR-MEM-014
journeys:
  - JNY-MBR-MEM-004
open_questions:
  - OQ-MBR-MEM-003
evidence:
  - repository: services-membership
    source_sha: 5994c270e8a37a30f848e99ba5b5ee6b87c08893
    paths:
      - modules/billing/aws/api/billing.routes.ts
      - modules/billing/services/planning/run-plan.ts
      - modules/billing/services/runs/billing-run-operations.ts
      - modules/billing/services/execution
      - modules/billing/services/recurring/recurring-billing-runs.ts
      - modules/billing/services/configuration/billing-template-records.ts
      - modules/billing/db/billing-templates.table.ts
      - modules/billing/db/billing-runs.table.ts
      - modules/billing/db/billing-run-actions.table.ts
      - modules/billing/db/recurring-billing-runs.table.ts
      - modules/billing/db/billing-run-jobs.table.ts
      - modules/billing/db/billing-run-job-steps.table.ts
      - modules/billing/db/billing-run-job-events.table.ts
      - modules/billing/tests
      - tests/e2e/billing-api.e2e.spec.ts
      - tests/acceptance/billing
  - repository: react-console
    source_sha: baf6dae382d6f41dce976c7f848667ccc37e076f
    paths:
      - acceptance/membership/reports/defects/billing.md
controls: [TC-AVL-002, TC-AVL-003, TC-AVL-004, TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-AZ-005, TC-AZ-006, TC-CHG-001, TC-CHG-002, TC-CHG-003, TC-CHG-004, TC-CRY-001, TC-CRY-002, TC-CRY-003, TC-DAT-001, TC-DAT-002, TC-DAT-003, TC-DAT-007, TC-IAM-001, TC-IAM-002, TC-IAM-004, TC-IAM-005, TC-IAM-006, TC-LOG-001, TC-LOG-002, TC-LOG-003, TC-LOG-004, TC-LOG-005, TC-MON-001, TC-MON-002, TC-MON-004, TC-PI-001, TC-PI-002, TC-PI-003, TC-PI-004, TC-PI-005, TC-PRV-001, TC-PRV-002, TC-SEC-001, TC-SEC-004, TC-TEN-001, TC-TEN-002, TC-TEN-003]
exercised_by: [FAS-MBR-MEM-05-01, FAS-MBR-MEM-05-02, FAS-MBR-MEM-05-03, FAS-MBR-MEM-05-04, FAS-MBR-MEM-05-05]
suites: [TST-MBR-MEM-004, TST-MBR-MEM-005, TST-MBR-MEM-006, TST-MBR-MEM-070, TST-MBR-MEM-072, TST-MBR-MEM-075, TST-MBR-MEM-076, TST-MBR-MEM-078, TST-MBR-MEM-081, TST-MBR-MEM-085, TST-MBR-MEM-086, TST-MBR-MEM-088, TST-MBR-MEM-089, TST-MBR-MEM-238]
---

# Bill renewals in runs, on demand or on a schedule

## Purpose

Each renewal season the association invoices members whose terms are ending, reminds them, warns those whose stored card will expire, and drops those who did not renew. An administrator needs to see exactly what a run will do before it does it, and to trust that nothing is done twice.

## How It Works

An administrator keeps billing templates: a program, filters on types and packages, the actions wanted, and the reason to record on drops. A run is created from a template over a range of term end dates and planned on a business date. Planning takes the committed terms ending in the range, leaves out renewed, never-ending, suspended and organization-child memberships, and classes each term as renewing or lapsed by its grace. A renewing member is planned a renewal order (or an auto-renewal order when a payment method is stored), a notice and reminders. A lapsed member is planned a drop. The plan counts everything so it can be reviewed. Executing runs on the durable jobs engine and acts on each action once. An action whose term changed since planning does nothing. Orders are prepared through Membership's own checkout preparation and placed with a retry key. Notices go through Platform's communications to the member's first e-mail address. Drops terminate the term with the run's reason. A recurring run repeats a template on a schedule in the association's time zone. Today renewal orders cannot reach Commerce and notices are stored but not sent. Organizations receive no notices.

## Relationships

- Depends on `CAP-MBR-MEM-009` because renewal orders are prepared exactly as a checkout is.
- Depends on `CAP-MBR-MEM-003` because a drop is a staff termination.
- Depends on Platform's communications (`MOD-PFM-NOT`) and durable jobs (`MOD-PFM-OPS`), and on Commerce orders (`MOD-COM-ORD`).
