Files
vmall/openspec/specs/membership/spec.md
T
james 9904696e76 feat: wave 2 migration (P3, P5, P7 openspec changes)
Implements, verifies, and archives the three remaining Wave 2 changes from
openspec/MIGRATION-PLAN.md.

- add-wallet-settlement (P3): demo recharge, guarded withdrawal freeze and
  one-time admin review, paginated own fund entries, idempotent per-shop
  weekly/monthly settlement statements with commission rate and one-time
  payout confirmation.
- add-merchant-onboarding (P5): personal/enterprise applications with one live
  application per user, guarded review with mandatory rejection reason, and
  transactional shop + owner provisioning returning one-time credentials;
  mall onboarding/status pages and an admin review console.
- add-membership-messaging (P7): platform member levels, append-only growth
  accrual on order completion with guarded one-way leveling, order/shipment/
  refund system messages with unread/read state and soft deletion, plus the
  mall header unread badge.

Backend: migrations 0019-0023, new wallet, settlement, merchant_onboarding,
membership and messaging modules, event hooks in order/fulfillment/aftersale,
and integration suites for each. Shared contract extended and all three
frontends updated; code indexes, domain docs, backend guidelines and the
migration tracker synced.

Verification: cargo test -p vmall-api green twice consecutively; mall, admin
and shop-admin builds pass; browser smoke on every new surface; openspec
validate --all --strict green (33 passed).

The three changes share the @vmall/shared contract, the mall mock adapter and
per-app locale/nav files, so they are committed together to keep every commit
buildable.
2026-09-25 15:25:29 +00:00

5.2 KiB

membership Specification

Purpose

A customer's spending converts into status: platform-managed member levels (bilingual name and benefits, icon, unique integer growth threshold) plus an append-only growth ledger that accrues the realized paid amount of each completed order in whole base-currency units. Accrual is idempotent per order and, inside the same transaction, raises the customer's level with a guarded one-way update; the status read re-derives the displayed level against current thresholds and shows progress to the next one. Growth history is own-only.

Requirements

Requirement: Member level catalog

The platform SHALL manage member levels through admin-only APIs. Each level SHALL carry a bilingual name ({en, zh}), an icon key, an integer growth-value threshold, and a bilingual benefits description ({en, zh}). Growth thresholds SHALL be unique across levels, and the effective ordering of levels SHALL follow the threshold. Deleting a level that any customer currently holds SHALL be rejected instead of reassigning or orphaning members.

Scenario: create a level

  • WHEN a platform admin creates a level with name, icon, growth threshold, and benefits in both locales
  • THEN the level is listed with its bilingual content and threshold ordering

Scenario: duplicate threshold is rejected

  • WHEN a platform admin creates or edits a level to reuse another level's growth threshold
  • THEN the request is rejected and the existing levels are unchanged

Scenario: deleting a level in use is rejected

  • WHEN a platform admin deletes a level that at least one customer holds
  • THEN the request is rejected and no customer's level changes

Requirement: Growth value accrual ledger

When a customer confirms receipt and an order reaches completed, the customer SHALL earn growth value equal to that order's realized paid amount converted to the base currency and truncated to whole units through integer minor-unit arithmetic using the base currency exponent, with no floating-point computation. Accrual SHALL append exactly one entry per order to a growth ledger that, like the points ledger, is append-only and records the delta, running growth total, reason, and order reference. Retried or repeated completion events SHALL NOT create a second entry or a second accrual.

Scenario: confirm receipt accrues growth

  • WHEN a customer confirms receipt of an order whose realized paid amount converts to 120 whole base-currency units
  • THEN the growth ledger gains one entry with delta 120 referencing that order and the customer's growth total rises by 120

Scenario: repeated completion is idempotent

  • WHEN the completion handling for the same order runs again
  • THEN no second ledger entry exists and the growth total is unchanged

Scenario: ledger entries are immutable

  • WHEN any code path handles growth after an entry was written
  • THEN the entry is only ever appended to, never updated or deleted

Requirement: Automatic level upgrade

Inside the growth accrual transaction, the customer's level SHALL be re-derived as the level with the highest growth threshold less than or equal to the customer's growth total and written to users.level with a guarded update. Leveling SHALL be one-way: a customer is only ever moved to a level with a strictly higher threshold than the current one, and never demoted automatically. A growth total exactly at a threshold qualifies for that level, and when several thresholds are passed the highest qualifying level wins. A customer below every threshold SHALL hold no level.

Scenario: growth at the threshold upgrades

  • WHEN an accrual brings a customer's growth total exactly to a level's threshold
  • THEN the customer's level becomes that level in the same transaction as the ledger entry

Scenario: jumping past intermediate levels

  • WHEN an accrual passes two levels' thresholds at once
  • THEN the customer holds the highest qualifying level, not the intermediate one

Scenario: no level below every threshold

  • WHEN a customer's growth total is below the lowest defined threshold
  • THEN the customer holds no level and a later qualifying accrual assigns one

Requirement: Level benefits display

An authenticated customer SHALL read their own membership status: current level with name, icon, and benefits, total growth value, the next level's threshold and remaining growth to reach it, and a paginated view of their own growth ledger. The status SHALL re-derive the displayed level against current thresholds, and one customer SHALL never read another customer's growth history.

Scenario: progress to the next level

  • WHEN a customer between two thresholds opens their membership status
  • THEN the response shows the current level's benefits and the exact growth remaining to the next level

Scenario: top level has no next level

  • WHEN a customer at or above the highest threshold reads their membership status
  • THEN the response contains no next level and no remaining growth target

Scenario: growth history is own-only

  • WHEN a customer requests their growth ledger
  • THEN only entries of the authenticated customer are returned, newest first and paginated