Files
vmall/openspec/changes/archive/2026-09-24-add-wallet-settlement/tasks.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

7.1 KiB
Raw Blame History

1. Persistence and shared contract

  • 1.1 Add migration 0019_wallet.sql adapting tigshop's user_recharge_order and user_withdraw_apply: wallet_recharges (user, currency, amount_minor BIGINT, status, timestamps) and wallet_withdrawals (user, currency, amount_minor BIGINT, account_details JSONB, status pending/approved/rejected with CHECK, reviewing admin, reviewed_at, review_note, timestamps) with foreign keys, non-negative amount CHECKs, and customer-facing indexes. (Renumbered from the proposed 0018: 0016-0018 were taken by aftersales/freight/reviews.)
  • 1.2 Add migration 0020_settlement.sql adapting tigshop's vendor_settlement_order: settlement_statements (shop, period_kind week/month, period_start, period_end, order_count, gross_minor, refund_minor, commission_rate_bps, commission_minor, payable_minor, status pending/confirmed with CHECK, generated_by, confirmed_by, confirmed_at, timestamps) with a unique index on (shop_id, period_kind, period_start), plus a platform-level settings row for settlement.commission_rate_bps. Also adds orders.completed_at (settlement attributes an order to the period it was confirmed received; refunds bump updated_at) and sets it in the existing completion transition.
  • 1.3 Add @vmall/shared wallet types and customer-client methods: getWallet (available/frozen minor with currency), listWalletEntries (paged signed ledger entries), rechargeWallet (demo), applyWithdrawal, and listMyWithdrawals.
  • 1.4 Add @vmall/shared settlement and review methods: admin client listWithdrawalApplications, reviewWithdrawal, getCommissionRate, setCommissionRate, listSettlementStatements, getSettlementStatement, generateSettlementStatement, confirmSettlementStatement; shop client listShopSettlementStatements, getShopSettlementStatement, generateShopSettlementStatement. All money fields are i64 minor units; no floating-point amounts cross the contract.

2. Wallet backend

  • 2.1 Implement apps/api/src/modules/wallet/ (repo, service returning ApiResult<Dto>, DTOs, handlers, module registration) with user-scoped /wallet routes and /admin/wallet/withdrawals review routes declaring the platform-admin role.
  • 2.2 Implement demo recharge: one transaction records a wallet_recharges row and credits the available account through the existing customer-accounts primitives with a wallet_recharge ledger entry; the DTO carries an explicit demo marker.
  • 2.3 Implement the withdrawal lifecycle: application freezes funds via guarded UPDATE ... WHERE balance_minor >= $amount moving available to frozen with paired ledger entries; admin approve deducts frozen balance with a ledger entry; admin reject returns frozen balance to available with a ledger entry; each review is guarded by UPDATE ... WHERE status = 'pending', returns 409 on repeat, and records reviewer, timestamp, and note.
  • 2.4 Implement paginated fund-entry listing mapped to customer_account_entries for the caller's monetary accounts (newest first; signed delta, resulting balance, reason, reference) with ownership filtering.
  • 2.5 Add apps/api/tests/wallet.rs covering concurrent withdrawal overdraw, insufficient-balance no-op, double-review 409, reject unfreeze, approve frozen deduction, recharge ledger pairing, and entry pagination isolation, reusing the tests/common/mod.rs fixtures.

3. Settlement backend

  • 3.1 Implement apps/api/src/modules/settlement/ (repo, service returning ApiResult<Dto>, DTOs, handlers, module registration) with /shop/settlement/* routes scoped through AuthUser::own_shop and /admin/settlement/* routes declaring the platform-admin role.
  • 3.2 Implement idempotent statement generation for a shop and closed week/month period: snapshot order count and gross totals of confirmed-received orders in the period, deduct completed refunds (aftersales rows with status refunded, per-order totals maintained by add-aftersale-refunds — if developed in parallel with P0, merge the P0 aftersales migration first), apply the platform commission rate in integer basis points with integer minor-unit arithmetic (payable = gross − refunds − commission), and return the existing statement untouched on repeat generation.
  • 3.3 Implement statement listing and detail with per-order and refund breakdown, with merchant routes restricted to the own shop and platform-admin routes seeing every shop.
  • 3.4 Implement pending -> confirmed payout confirmation: guarded UPDATE ... WHERE status = 'pending' (409 on repeat) crediting the payable amount to the shop owner's available account with exactly one ledger entry referencing the statement.
  • 3.5 Implement platform commission-rate configuration (integer basis points), applied only to statements generated after a change; generated statements keep their snapshot.
  • 3.6 Add apps/api/tests/settlement.rs covering generation idempotency and period uniqueness, refund deduction, commission snapshot immutability after a rate change, double-confirmation 409 with exactly one payout ledger entry, and cross-shop isolation, reusing the tests/common/mod.rs fixtures.

4. Frontend surfaces

  • 4.1 Add the mall buyer-center wallet page (available/frozen balance, paginated fund entries, clearly labeled demo recharge, withdrawal request and history) through @vmall/shared, with bilingual strings in the existing Mall locale source.
  • 4.2 Add the wallet domain to the Mall fixed-data adapter (apps/mall/mock/api.ts) with matching behavior, and wire wallet into the Mall API selection LIVE_PICKS and default live domains (apps/mall/plugins/api.ts).
  • 4.3 Add admin pages for withdrawal review (approve/reject with outcome and 409 feedback), commission-rate configuration, and settlement statement list/detail with manual generation and one-time payout confirmation.
  • 4.4 Add shop-admin pages for own-shop settlement statement list/detail with manual generation and shop-account summary plus withdrawal request/history.
  • 4.5 Keep all three frontends on the @vmall/shared contract only (no page-level fetch of wallet/settlement endpoints) and render page chrome with @vmall/ui primitives.

5. Verification and tracker cleanup

  • 5.1 Run the wallet and settlement integration tests in apps/api/tests/wallet.rs and apps/api/tests/settlement.rs (reusing the tests/common/mod.rs fixtures), then cargo test -p vmall-api to prove the suite stays green against the shared test database.
  • 5.2 Build all three frontends because the shared API contract changes: pnpm --filter @vmall/mall build, pnpm --filter @vmall/admin build, and pnpm --filter @vmall/shop-admin build.
  • 5.3 Browser-smoke the API plus frontends: demo recharge updates the wallet; withdrawal freeze, admin reject unfreeze, and admin approve deduction round trip; wallet entries pagination; commission-rate configuration; idempotent statement generation with refund deduction; one-time payout confirmation writing one ledger entry; and shop-admin own-shop statement isolation.
  • 5.4 Check every OpenSpec task, then run openspec change validate add-wallet-settlement --strict and openspec validate --all --strict.