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.
4.3 KiB
wallet Specification
Purpose
The wallet gives the existing customer-account ledger its entry points: a simulated demo recharge, a withdrawal application that freezes funds until a platform admin reviews it exactly once (approve consumes the frozen amount, reject returns it to available), and a paginated view of the caller's own fund entries. Balances only move through the account module's credit/debit/freeze/release primitives in the same transaction as the business record, and every movement pairs with an append-only ledger entry.
Requirements
Requirement: Demo wallet recharge
An authenticated user SHALL recharge their wallet through a simulated demo flow that records a wallet recharge row and credits the available monetary account with one signed ledger entry in one transaction, without invoking any external payment channel. The API payload and the wallet UI SHALL clearly label the recharge as simulated/demo. Amounts SHALL be positive integer minor units in the account's currency, and monetary arithmetic SHALL NOT use floating point.
Scenario: demo recharge credits balance
- WHEN a signed-in buyer submits a demo recharge of 5000 minor units
- THEN the available balance increases by exactly 5000 minor units and exactly one ledger entry with reason
wallet_rechargerecords the resulting balance
Scenario: flow is labeled as demo
- WHEN a buyer opens the recharge flow
- THEN it is visibly marked as a simulated recharge and no external payment provider is contacted
Requirement: Guarded withdrawal freeze
An authenticated user SHALL apply to withdraw a positive amount in their account currency. The application SHALL atomically move the amount from available to frozen balance through guarded conditional updates that succeed only when available balance covers the amount, and each balance change SHALL be paired with a ledger entry in the same transaction. When available balance is insufficient the request SHALL fail without changing any balance, and concurrent applications SHALL never overdraw or produce a negative balance.
Scenario: withdrawal freezes funds
- WHEN a buyer applies to withdraw 1000 minor units
- THEN available balance decreases and frozen balance increases by exactly 1000 minor units and two ledger entries record the move
Scenario: concurrent applications cannot overdraw
- WHEN two withdrawal applications together exceed the available balance
- THEN at most one application succeeds and all balances remain non-negative
Requirement: One-time withdrawal review
Platform admins SHALL list pending withdrawal applications and approve or reject each exactly once through a guarded status transition that requires the pending status and returns 409 on a repeated review. Approve SHALL deduct the frozen amount with a ledger entry recording that the funds left the platform. Reject SHALL return the frozen amount to available balance with a ledger entry. Every review SHALL record the reviewing admin, timestamp, and optional note.
Scenario: reject returns funds
- WHEN an admin rejects a pending withdrawal application
- THEN the frozen amount returns to available balance and the application status becomes rejected
Scenario: approve consumes frozen funds
- WHEN an admin approves a pending withdrawal application
- THEN frozen balance decreases by the requested amount and the application status becomes approved
Scenario: repeated review conflicts
- WHEN an admin reviews an application that was already reviewed
- THEN the API returns 409 and no balance or ledger row changes
Requirement: Paginated fund entries
An authenticated user SHALL page through their own monetary account entries newest first. Each entry SHALL carry the signed delta minor, resulting balance minor, reason, optional business reference, and timestamp, mapped from the existing append-only ledger rows. Entries SHALL never expose or mutate another user's ledger.
Scenario: entries page maps the ledger
- WHEN the buyer requests a page of fund entries
- THEN only their own available/frozen ledger rows appear with signed deltas and resulting balances
Scenario: entries are user-isolated
- WHEN one user requests fund entries
- THEN no entry belonging to another user is ever returned