docs(openspec): plan shop coupons, accounts, points, flash sales, and group buying
Capture exclusive promotion composition, empty-group cancel, and mall surface scope so the five marketing changes can be implemented without stacking or lifecycle ambiguity. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
e10cae5789
commit
53548196f8
@@ -0,0 +1,36 @@
|
||||
## Context
|
||||
|
||||
The buyer center shows fixed balance, frozen balance, and points. Later points redemption requires protected debits and audit history; mutable profile columns would not provide either. VMall supports multiple currencies, while points are integer units rather than money.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Provide customer account summaries and append-only balance history.
|
||||
- Make every later debit, credit, freeze, and release transactionally safe.
|
||||
- Replace fixture account statistics in the mall.
|
||||
|
||||
**Non-Goals:**
|
||||
- Funding, payment, withdrawal, refunds, settlement, point-earning campaigns, or a public ledger listing.
|
||||
|
||||
## Decisions
|
||||
|
||||
### Accounts are keyed by kind and currency
|
||||
`customer_accounts` has one row per `(user_id, kind, currency)`, where monetary kinds (`available`, `frozen`) carry an ISO currency and `points` has no currency. `balance_minor BIGINT` holds money and integer points; constraints reject negative stored balances and invalid kind/currency pairs. Seed/new users receive the defined account rows with zero balances.
|
||||
|
||||
### Entries are immutable audit facts
|
||||
`customer_account_entries` records account ID, signed delta, resulting balance, reason code, optional reference type/ID, and timestamp. Entries are inserted in the same transaction after their guarded account update. No public API mutates an account in this change; a module service provides the internal mutation API for later capabilities.
|
||||
|
||||
### Guarded mutation is the only write path
|
||||
Debit/freeze uses a conditional update whose predicate proves sufficient balance, returning Conflict on zero rows. Credit/release uses atomic addition. The service locks all affected accounts in stable `(kind, currency)` order, applies the transfers, then appends entries. This avoids races and keeps available/frozen transfers balanced.
|
||||
|
||||
### Mall reads a defined summary
|
||||
`GET /api/me/stats` returns the customer’s configured display-currency `balance_minor`, `frozen_minor`, `points`, and monetary currency. The shared client and mall user center consume it; localization remains UI-owned. `frozen_minor` is usually zero in this change because no public flow freezes funds. There is no customer ledger page or `GET` entries API.
|
||||
|
||||
### Demo credits belong to later spend flows
|
||||
New and seeded customers receive zero balances. Granting spendable points is not a campaign in this change; `add-points-mall` SHALL credit demo points through this module’s guarded credit path (append-only entry) after this change is archived. Direct `SET balance` is forbidden.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- Multi-currency balances introduce selection rules; the initial public summary exposes the buyer’s configured currency, while APIs remain extensible for future wallets.
|
||||
- An internal service, rather than public mutation endpoints, prevents granting balances without a business event but means points mall must depend on this archived change.
|
||||
- Exposing frozen balance before any freeze flow exists can look like a missing feature; keep the kind so later withdrawals do not migrate schema, and keep the mall figure honest (zero until a freeze exists).
|
||||
Reference in New Issue
Block a user