---
id: CAP-MBR-STR-012
title: Offer membership through an Accounting product
feature: FEAT-MBR-STR-04
product_area: membership
module: structure
revision: 1
status: draft
product_intent: proposed
implementation_evidence: observed
supersedes:
  - react-console acceptance inventory CAP-MBR-CFG-004
source_sha:
  services-membership: 5994c270e8a37a30f848e99ba5b5ee6b87c08893
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
intent_sources:
  - >-
    services-membership:docs/migrations/packages-as-products/README.md#D-1 (Andrew, 2026-10-18: the package is
    the product)
  - >-
    services-membership:docs/migrations/packages-as-products/README.md#D-2 (Andrew, 2026-10-18: the console
    saves the product through Accounting first, then the package)
purpose: >-
  Staff add membership-specific behavior to a product whose shared catalogue information remains in
  Accounting.
outcomes:
  - The offering has one shared product identity.
  - Membership holds the term and renewal configuration needed for the offering.
concepts:
  - CON-MBR-STR-004
actors:
  - ACT-MBR-001
  - ACT-MBR-002
  - ACT-MBR-003
  - ACT-MBR-005
surfaces:
  - api
  - job
preconditions: []
postconditions: []
permissions:
  - key: membership.configuration.read
    where_checked: api
    scope: tenant
  - key: membership.configuration.manage
    where_checked: api
    scope: tenant
  - key: membership.application.access
    where_checked: api
    scope: tenant
rules:
  - id: BR-CAP-MBR-STR-012-01
    proved_by: [ATS-MBR-STR-012-01, ATS-MBR-STR-012-13, TST-MBR-STR-039, TST-MBR-STR-096]
    statement: >-
      Package creation requires the identity of an existing Accounting membership-package product and refuses
      a missing identity, missing product or wrong product type.
    kind: validation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-membership:tests/e2e/packages-as-products.e2e.spec.ts#packages as products, through the API ›
      refuses a package whose product Accounting does not have, 400
    origin: >-
      services-membership:docs/migrations/packages-as-products/README.md#D-2 (Andrew, 2026-10-18: the console
      saves the product through Accounting first, then the package)
  - id: BR-CAP-MBR-STR-012-02
    proved_by: [ATS-MBR-STR-012-02, ATS-MBR-STR-012-14, TST-MBR-STR-096]
    statement: >-
      Package writes reject shared product fields such as name, description, status and visibility, and reads
      obtain those fields from Accounting.
    kind: validation
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: test
    source: >-
      services-membership:tests/e2e/packages-as-products.e2e.spec.ts#packages as products, through the API ›
      refuses the common fields a package no longer carries of its own (they are the product’s)
    origin: >-
      services-membership:docs/migrations/packages-as-products/README.md#D-1 (Andrew, 2026-10-18: the package
      is the product)
  - id: BR-CAP-MBR-STR-012-03
    proved_by: [ATS-MBR-STR-012-03, TST-MBR-STR-031, TST-MBR-STR-096]
    statement: >-
      Package activity follows Accounting product activity, including the activity filter used by package
      lists.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-membership:tests/e2e/packages-as-products.e2e.spec.ts#packages as products, through the API ›
      lists packages with the product’s fields and filters is_active by the product’s status
    origin: >-
      services-membership:docs/migrations/packages-as-products/README.md#D-1 (Andrew, 2026-10-18: the package
      is the product)
  - id: BR-CAP-MBR-STR-012-04
    proved_by: [ATS-MBR-STR-012-04, TST-MBR-STR-096]
    statement: One Accounting product can have only one Membership package extension.
    kind: validation
    enforced_at:
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-membership:tests/e2e/packages-as-products.e2e.spec.ts#packages as products, through the API ›
      refuses a second extension of the same product: the package id is the product id
    origin: >-
      services-membership:docs/migrations/packages-as-products/README.md#D-1 (Andrew, 2026-10-18: the package
      is the product)
  - id: BR-CAP-MBR-STR-012-05
    proved_by: [ATS-MBR-STR-011-06, ATS-MBR-STR-012-05, ATS-MBR-STR-012-11, ATS-MBR-STR-018-12, TST-MBR-STR-095]
    statement: Each package names a program and a membership type belonging to that program.
    kind: validation
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-membership:tests/e2e/database-refusals.e2e.spec.ts#mistakes only the database catches, through
      the API › DEF-MBR-0086: a configuration write a check, a foreign key or an integer column refuses ›
      refuses a package of a type of another program 400 naming membership_type_id, and writes no package nor
      unit rule
    origin: none
  - id: BR-CAP-MBR-STR-012-06
    proved_by: [ATS-MBR-STR-012-06, TST-MBR-STR-095]
    statement: A default renewal target must be a different package within the same program.
    kind: validation
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-membership:tests/e2e/database-refusals.e2e.spec.ts#mistakes only the database catches, through
      the API › DEF-MBR-0086: a configuration write a check, a foreign key or an integer column refuses ›
      refuses a package renewing into itself 400 naming renews_into_package_id
    origin: none
  - id: BR-CAP-MBR-STR-012-07
    proved_by: []
    statement: >-
      Package configuration includes renewal timing, continuation, auto-renew choice, reinstatement, seats and
      pricing hints, with Commerce responsible for amounts.
    kind: policy
    enforced_at:
      - service
    must_prove: false
    provenance: test
    source: >-
      services-membership:tests/acceptance/configuration/MBR-ACC-0115.acceptance.ts#MBR-ACC-0115: The renewal
      window and the reinstatement period decide what a member may buy, read live from the package: narrowing
      the window shuts an open renewal, widening the reinstatement period turns a rejoin back into a
      reinstatement, a blank period means no reinstatement at all, and the default renewal package is the one
      the configuration names
    origin: none
  - id: BR-CAP-MBR-STR-012-08
    proved_by: [ATS-MBR-STR-012-07, ATS-MBR-STR-012-12, ATS-MBR-STR-014-03, TST-MBR-STR-095]
    statement: A referenced change policy set must exist in the workspace.
    kind: validation
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-membership:tests/e2e/database-refusals.e2e.spec.ts#mistakes only the database catches, through
      the API › DEF-MBR-0086: a configuration write a check, a foreign key or an integer column refuses ›
      refuses a change policy set that does not exist 400 naming change_policy_set_id
    origin: none
  - id: BR-CAP-MBR-STR-012-09
    proved_by: [ATS-MBR-STR-012-08]
    statement: Package deletion removes the Membership extension while leaving its Accounting product in place.
    kind: lifecycle
    enforced_at:
      - service
    must_prove: true
    provenance: observed
    source: 'services-membership:modules/configuration/services/persistence/configuration-store.ts:configurationStore'
    origin: none
  - id: BR-CAP-MBR-STR-012-10
    proved_by: [ATS-MBR-STR-012-09]
    statement: Package display order is returned as zero because the Accounting port has no product display order.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: observed
    source: 'services-membership:modules/structure/ports/accounting-products.ts:accountingProductsFromTables'
    origin: none
  - id: BR-CAP-MBR-STR-012-11
    proved_by: [ATS-MBR-STR-012-10]
    statement: >-
      Activity filtering follows each underlying package page, so even an empty filtered page can carry a
      continuation cursor.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: observed
    source: >-
      services-membership:modules/configuration/services/persistence/membership-packages.ts:listMembershipPackages
    origin: none
scenarios:
  - id: SC-CAP-MBR-STR-012-01
    given: Accounting already holds Regular Annual as a membership-package product.
    when: Staff extend it for the National program’s Regular classification.
    then: The offering returns the existing product’s shared information.
    provenance: test
    source: >-
      services-membership:tests/e2e/packages-as-products.e2e.spec.ts#packages as products, through the API ›
      extends a product of type membership_package and answers the product’s name, status and visibility as
      the package’s
edge_cases:
  - id: SC-CAP-MBR-STR-012-02
    given: Accounting makes a package’s product inactive.
    when: Staff attempt to add a renewal using the package.
    then: The renewal attempt fails.
    provenance: test
    source: >-
      services-membership:tests/e2e/packages-as-products.e2e.spec.ts#packages as products, through the API ›
      refuses a renewal term on a package whose product Accounting deactivated
constraints: []
depends_on:
  - CAP-MBR-STR-011
  - CAP-MBR-STR-018
related:
  - CAP-MBR-STR-013
  - CAP-MBR-STR-014
  - CAP-MBR-STR-015
journeys:
  - JNY-MBR-STR-004
open_questions:
  - OQ-MBR-STR-004
evidence:
  - >-
    services-membership: Examination of package persistence, request schema, the Accounting products port and
    the packages table.
  - >-
    services-membership: Examination of packages-as-products and database refusals end-to-end, acceptance
    journeys MBR-ACC-0091, 0099, 0101, 0115, 0121.
  - 'services-membership: Examination of the packages-as-products record.'
  - 'services-membership:modules/configuration/services/persistence/configuration-store.ts'
  - 'services-membership:modules/structure/ports/accounting-products.ts'
  - 'services-membership:tests/acceptance/configuration/MBR-ACC-0115.acceptance.ts'
  - 'services-membership:tests/e2e/database-refusals.e2e.spec.ts'
  - 'services-membership:tests/e2e/packages-as-products.e2e.spec.ts'
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-004, TC-DAT-007, TC-IAM-001, TC-IAM-002, TC-IAM-004, TC-IAM-005, TC-LOG-001, TC-LOG-002, TC-LOG-003, TC-LOG-004, TC-PI-001, TC-PI-003, TC-PI-004, TC-PI-005, TC-SEC-001, TC-SEC-004, TC-TEN-001, TC-TEN-002, TC-TEN-003]
exercised_by: [FAS-MBR-STR-04-01, FAS-MBR-STR-04-02, FAS-MBR-STR-04-03, FAS-MBR-STR-04-04, FAS-MBR-STR-04-05]
suites: [TST-MBR-STR-031, TST-MBR-STR-039, TST-MBR-STR-095, TST-MBR-STR-096]
---

# Offer membership through an Accounting product

## Purpose

Staff add membership-specific behavior to a product whose shared catalogue information remains in Accounting.

## How It Works

Staff prepare the sellable product in Accounting before creating its Membership extension. The extension uses that product’s identity. It adds the program, classification and term behavior that make the offering a membership. Shared catalogue fields are obtained from the product whenever the package is read. Staff adjust renewal and reinstatement settings through versioned partial changes. They can select a different renewal package within the same program. Optional grants and unit rules describe additional consequences of the offering. A package’s active appearance follows its product, while other eligibility conditions remain relevant to a sale. Activity-filtered lists may need another page even when the current page is empty. Deleting the extension leaves the product in Accounting. The product should resolve inactive-product preparation and empty reinstatement semantics before configuration screens rely on them.

## Relationships

- Programs and their types determine which membership the offering grants.
- Term calculations consume the package’s date settings.
- Unit dues products can identify a package-specific local offering.
