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.
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