---
id: CAP-MBR-MEM-009
title: Sell membership through the Commerce checkout
feature: FEAT-MBR-MEM-03
product_area: membership
module: memberships
revision: 1
status: draft
product_intent: confirmed
implementation_evidence: partially-observed
supersedes:
  - 'inventory:CAP-MBR-COM-001'
  - 'inventory:CAP-MBR-COM-002'
  - 'inventory:CAP-MBR-COM-003'
  - 'inventory:CAP-MBR-COM-004'
  - 'inventory:CAP-MBR-COM-005'
  - 'inventory:CAP-MBR-COM-006'
  - 'inventory:CAP-MBR-COM-009'
  - 'inventory:CAP-MBR-COM-014'
  - 'inventory:CAP-MBR-COM-015'
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 (Andrew, 2026-10-17: build against contracts)'
  - 'services-membership:docs/migrations/packages-as-products/README.md (D-1, D-2, D-5 of 2026-10-18)'
purpose: >-
  Members can join, renew, reinstate and rejoin by buying a package and its chapter dues in Rhythm's shared checkout,
  while Membership alone decides what the purchase may do.
outcomes:
  - The checkout never sells a membership operation the member is not eligible for.
  - A paid order becomes exactly one committed term, with its chapters, dues and grants.
  - Commerce prices each line without Membership ever computing an amount.
concepts:
  - CON-MBR-MEM-002
  - CON-MBR-MEM-003
  - CON-MBR-MEM-001
actors:
  - ACT-MBR-011
  - ACT-MBR-010
surfaces:
  - api
permissions: []
rules:
  - id: BR-CAP-MBR-MEM-009-01
    proved_by: [ATS-MBR-MEM-009-01, TST-MBR-MEM-010, TST-MBR-MEM-241]
    statement: >-
      Membership answers Commerce's request to prepare every membership item of a checkout in one call, writes nothing
      while preparing, and never computes or returns an amount.
    kind: policy
    enforced_at: [service]
    must_prove: true
    provenance: test
    source: 'services-membership:modules/commerce-participant/tests/unit/purchase-plan.spec.ts#carries no amount anywhere'
    origin: none
  - id: BR-CAP-MBR-MEM-009-02
    proved_by: [ATS-MBR-MEM-009-02]
    statement: >-
      A package line's product must be the package itself, and the program, type and package must be active and
      belong to one another.
    kind: validation
    enforced_at: [service]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/commerce-participant/tests/integration/participant.integration.spec.ts#refuses a
      package line whose product is not the package: the package is its product (D-1)
    origin: 'D-1 of 2026-10-18 (services-membership:docs/migrations/packages-as-products/README.md)'
  - id: BR-CAP-MBR-MEM-009-03
    proved_by: [ATS-MBR-MEM-009-03, TST-MBR-MEM-008, TST-MBR-MEM-096]
    statement: >-
      The member's state on the business date decides the operation: no membership allows enrolment; a current term
      or grace allows renewal; after grace and within the package's reinstatement window, reinstatement; beyond it,
      rejoining; a request for another operation is refused naming the one the state supports.
    kind: policy
    enforced_at: [service]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/commerce-participant/tests/unit/eligibility.spec.ts#refuses a renewal of a lapsed
      membership, naming rejoin (07 §2)
    origin: none
  - id: BR-CAP-MBR-MEM-009-04
    proved_by: [ATS-MBR-MEM-009-04, TST-MBR-MEM-008]
    statement: >-
      A renewal is refused before the package's renewal window opens, for a membership marked do-not-renew, and for a
      renewal on a chosen date; an enrolment cannot start in the past.
    kind: validation
    enforced_at: [service]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/commerce-participant/tests/unit/eligibility.spec.ts#refuses a renewal before the
      package’s renewal window opens
    origin: none
  - id: BR-CAP-MBR-MEM-009-05
    proved_by: [ATS-MBR-MEM-002-09, ATS-MBR-MEM-009-05, ATS-MBR-MEM-009-17, TST-MBR-MEM-008]
    statement: >-
      A suspended membership is refused, and a membership with a cancellation pending, a renewal already pending or a
      first term already pending is unavailable until that settles.
    kind: policy
    enforced_at: [service]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/commerce-participant/tests/unit/eligibility.spec.ts#answers a renewal while one is
      pending unavailable: renewal_in_progress
    origin: none
  - id: BR-CAP-MBR-MEM-009-06
    proved_by: [ATS-MBR-MEM-008-11, ATS-MBR-MEM-009-06, TST-MBR-MEM-008]
    statement: >-
      An immediate change of type, a transfer and a change of chapters mid-term need an amendment and are refused; an
      upgrade at term end is sold as a renewal into the other type.
    kind: policy
    enforced_at: [service]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/commerce-participant/tests/unit/eligibility.spec.ts#takes an upgrade at term end as a
      renewal into the other type
    origin: none
  - id: BR-CAP-MBR-MEM-009-07
    proved_by: [ATS-MBR-MEM-007-16, ATS-MBR-MEM-009-07, ATS-MBR-MEM-009-19, ATS-MBR-MEM-016-08]
    statement: >-
      The plan names the term's period from the package's term rule, each chosen unit with its dues product, the
      grants, and the one pending term it claims; a unit whose dues are planned must have its dues line in the same
      checkout.
    kind: calculation
    enforced_at: [service]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/commerce-participant/tests/integration/participant.integration.spec.ts#refuses a
      package line whose dues chapter has no dues line beside it
    origin: none
  - id: BR-CAP-MBR-MEM-009-08
    proved_by: [ATS-MBR-MEM-009-08, TST-MBR-MEM-009]
    statement: >-
      Each line is priced for the operation's audience (new for enrolment, renewing for renewal, the package's choice
      for reinstatement, rejoining for a rejoin), falling back to new when the product's price list has no row for it;
      a first term with an end is prorated by the package's method on the original price, and a renewal never is.
    kind: calculation
    enforced_at: [service]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/commerce-participant/tests/unit/pricing-policy.spec.ts#falls back to new when the grid
      has no row for the operation’s audience (D-5: a blank rejoining inherits new)
    origin: 'D-5 of 2026-10-18 and D-24 (services-membership:docs/migrations/participant/README.md#The pricing-policy field mapping)'
  - id: BR-CAP-MBR-MEM-009-09
    proved_by: [ATS-MBR-MEM-009-09, TST-MBR-MEM-007, TST-MBR-MEM-012]
    statement: >-
      Each line says through whom it is sold: the package through its program and the program's ordering profile,
      chapter dues through the chapter's own profile, locked, or the program's when the chapter has none.
    kind: policy
    enforced_at: [service]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/commerce-participant/tests/unit/selling-context.spec.ts#inherits the program’s profile
      for a chapter without one, naming the program as where it came from
    origin: 'ordering profiles, Andrew-approved 2026-10-18 (services-membership:docs/migrations/packages-as-products/README.md#Ordering profiles)'
  - id: BR-CAP-MBR-MEM-009-10
    proved_by: [ATS-MBR-MEM-009-10, ATS-MBR-MEM-009-14, TST-MBR-MEM-007, TST-MBR-MEM-241]
    statement: >-
      At placement, inside Commerce's own transaction, Membership reserves the purchase as a pending term, creating an
      unnumbered pending membership when the owner has none; of two checkouts placing for one person at once, one wins
      and the other is refused as pending.
    kind: invariant
    enforced_at: [database]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/commerce-participant/tests/integration/participant.integration.spec.ts#lets one of
      two concurrent checkouts for the same person win and refuses the other with term_pending
    origin: none
  - id: BR-CAP-MBR-MEM-009-11
    proved_by: [ATS-MBR-MEM-009-11, ATS-MBR-MEM-009-15, ATS-MBR-MEM-009-16, ATS-MBR-MEM-011-05, ATS-MBR-MEM-012-09, TST-MBR-MEM-002, TST-MBR-MEM-105]
    statement: >-
      A line needs approval when an active application process of its operation offers the package and reviews its
      applications, unless the line names the application that process approved.
    kind: policy
    enforced_at: [service]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/applications/tests/integration/decision-handler.integration.spec.ts#answers
      requires_approval for the package the process reviews when no decided application is named
    origin: none
  - id: BR-CAP-MBR-MEM-009-12
    proved_by: [ATS-MBR-MEM-009-12, ATS-MBR-MEM-009-18, ATS-MBR-MEM-009-20, ATS-MBR-MEM-014-13, ATS-MBR-MEM-020-14, TST-MBR-MEM-007, TST-MBR-MEM-241]
    statement: >-
      Fulfilment commits the reserved term once per payment authorization: it numbers a new membership, records the
      enrolment, renewal, reinstatement or rejoin with the order, writes the frozen chapters with their dues products
      and the grants, and answers a redelivery with the same records.
    kind: lifecycle
    enforced_at: [service, database]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/commerce-participant/tests/integration/participant.integration.spec.ts#answers a
      redelivered authorization with the same records and writes nothing again
    origin: none
  - id: BR-CAP-MBR-MEM-009-13
    proved_by: [ATS-MBR-MEM-009-13, TST-MBR-MEM-007]
    statement: >-
      A chapter dues line is stamped on the chapter's affiliation once the package line is fulfilled; until then it
      asks Commerce to deliver it again.
    kind: lifecycle
    enforced_at: [service]
    must_prove: true
    provenance: test
    source: >-
      services-membership:modules/commerce-participant/tests/integration/participant.integration.spec.ts#stamps the
      dues line on the affiliation once the package line is fulfilled, and asks for a redelivery before
    origin: none
  - id: BR-CAP-MBR-MEM-009-14
    proved_by: []
    statement: >-
      A placement made in Commerce's transaction commits with Membership's change history recorded, so a purchase is
      never refused at commit because of how its writer is identified.
    kind: invariant
    enforced_at: [database]
    must_prove: true
    provenance: unknown
    source: FND-MBR-MEM-030
    origin: none
  - id: BR-CAP-MBR-MEM-009-15
    proved_by: []
    statement: >-
      A renewal bought without naming chapters carries the previous term's chapters marked as inherited, as a staff
      renewal does, and the same chapter cannot be bought twice on one line.
    kind: validation
    enforced_at: [service]
    must_prove: true
    provenance: unknown
    source: FND-MBR-MEM-031
    origin: none
scenarios:
  - id: SC-CAP-MBR-MEM-009-01
    given: A new Associate with no membership adds the Associate package to her cart.
    when: Commerce asks, places the order and later fulfils it.
    then: Asking writes nothing, placement reserves a pending unnumbered membership, and fulfilment commits the numbered membership once.
    provenance: test
    source: 'services-membership:tests/e2e/commerce-purchase.e2e.spec.ts#fulfils into a numbered membership holding the authorization id'
  - id: SC-CAP-MBR-MEM-009-02
    given: A Regular applicant chooses Atlanta, which sells dues.
    when: The cart lacks Atlanta's dues line, then includes it.
    then: The package line is refused without the dues line, and planned with Atlanta and its dues product with it.
    provenance: test
    source: >-
      services-membership:tests/e2e/commerce-purchase.e2e.spec.ts#plans the chapter with its dues product when its dues
      line is in the checkout
  - id: SC-CAP-MBR-MEM-009-03
    given: Two browser tabs buy the same person's first membership.
    when: Both orders are placed at once.
    then: One reserves the term and the other is refused as pending.
    provenance: test
    source: >-
      services-membership:tests/e2e/commerce-purchase.e2e.spec.ts#lets one of two concurrent placements claim the person’s
      term and refuses the other term_pending
edge_cases: []
constraints:
  - A package line names at most 20 chapters.
depends_on:
  - CAP-MBR-MEM-001
  - CAP-MBR-MEM-007
related:
  - CAP-MBR-MEM-010
  - CAP-MBR-MEM-012
  - CAP-MBR-MEM-013
  - CAP-MBR-MEM-016
journeys:
  - JNY-MBR-MEM-002
open_questions: []
evidence:
  - repository: services-membership
    source_sha: 5994c270e8a37a30f848e99ba5b5ee6b87c08893
    paths:
      - modules/commerce-participant/services/participant-handler.ts
      - modules/commerce-participant/services/prepare
      - modules/commerce-participant/services/fulfilment/fulfill.ts
      - modules/commerce-participant/contracts
      - modules/commerce-participant/db/commerce-claims.table.ts
      - modules/commerce-participant/ports/commerce-orders.ts
      - db/migrations/20261018044117_commerce_participant.sql
      - modules/commerce-participant/tests
      - tests/e2e/commerce-purchase.e2e.spec.ts
      - tests/e2e/s11-independent-chapter-dues.e2e.spec.ts
      - tests/acceptance/commerce
      - docs/migrations/participant/README.md
      - docs/migrations/packages-as-products/README.md
  - repository: react-console
    source_sha: baf6dae382d6f41dce976c7f848667ccc37e076f
    paths:
      - acceptance/membership/reports/defects/commerce.md
controls: [TC-AVL-002, TC-AVL-003, TC-AVL-004, TC-AZ-001, TC-AZ-003, TC-AZ-004, 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-006, TC-LOG-002, TC-LOG-003, 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-03-01, FAS-MBR-MEM-03-02, FAS-MBR-MEM-03-03, FAS-MBR-MEM-03-04, FAS-MBR-MEM-03-05]
suites: [TST-MBR-MEM-002, TST-MBR-MEM-007, TST-MBR-MEM-008, TST-MBR-MEM-009, TST-MBR-MEM-010, TST-MBR-MEM-012, TST-MBR-MEM-096, TST-MBR-MEM-105, TST-MBR-MEM-241]
---

# Sell membership through the Commerce checkout

## Purpose

Most members join and renew by paying. Rhythm's checkout belongs to Commerce, which knows carts, payments and orders but not membership rules. Membership must tell Commerce what each purchase would do, reserve it while the order is paid, and commit it once Commerce allows.

## How It Works

When a cart holds a membership package, and perhaps chapter dues, Commerce asks Membership to prepare those lines. Membership reads the member's state on the business date and decides what the purchase is: a new member enrols, a current or grace member renews, a recently lapsed member reinstates, and a long-lapsed member rejoins. A request for the wrong operation is refused, naming the right one. Renewals wait for the package's renewal window. Suspended members are refused, and a member with a pending purchase or cancellation must wait. Membership plans the term's dates, the chosen chapters with their dues products, and any grants. It tells Commerce which price audience and proration apply and through whom each line is sold, but never an amount. A reviewed package waits for approval unless the approved application is named. At placement Membership reserves a pending term in Commerce's own transaction, so two checkouts for one person can never both win. When Commerce authorizes fulfilment, the term is committed once with its chapters, dues and grants, and the membership is numbered. Changes of type or chapter mid-term need an amendment that is not built yet. The product should let placements commit with history recorded and should treat a renewal's carried chapters as inherited. Today placement fails at commit and carried chapters are recorded as chosen.

## Relationships

- Depends on `CAP-MBR-MEM-001` and `CAP-MBR-MEM-007` because a fulfilled line writes the same term, grants and affiliations staff would.
- Depends on Commerce's orders (`MOD-COM-ORD`) for carts, placement, payment and the order lines, and on Accounting's products (`MOD-ACC-PRD`) for packages and dues products.
- Related to `CAP-MBR-MEM-010` because cancellations and payment reversals undo or suspend what fulfilment committed.
