chore(openspec): archive add-shop-coupons
Sync the shop-coupons capability plus the order, frontend-mall, and frontend-shop-admin requirements into the main specs, and write the capability Purpose the archive leaves as a placeholder. Renaming the coupon scenario in the flash-sales and group-buying order deltas to the exact name now in the main order spec keeps those changes archivable, since a MODIFIED requirement replaces the whole block.
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
## Context
|
||||
|
||||
Checkout already splits a Redis cart into one database order per shop, converts SKU prices to the buyer-selected currency, and locks SKU rows. Mall coupon pages still read fixtures. The new capability crosses merchant management, customer claim/listing, and order creation.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Make shop coupons live, auditable, scoped, and safe under concurrent claims and checkout.
|
||||
- Let a customer select no more than one coupon per generated shop order.
|
||||
- Persist the realized minor-unit discount on the order.
|
||||
|
||||
**Non-Goals:**
|
||||
- Platform coupons, threshold reductions, customer targeting, or composing coupons with activity prices in this change. Later flash-sale and group-buying changes reject coupons on activity-priced shop orders.
|
||||
|
||||
## Decisions
|
||||
|
||||
### Coupon data is template plus owned snapshot
|
||||
`coupon_templates` owns `shop_id`, localized title, amount/threshold minor, currency, claim stock, active window, and enabled state. `coupons` owns `user_id`, `template_id`, `shop_id`, copied terms/window, `status`, and nullable `order_id`. A unique `(user_id, template_id)` prevents duplicate claims. The snapshot preserves a claim if a template is edited or disabled later.
|
||||
|
||||
### Checkout receives choices, not money
|
||||
The shared checkout body adds `coupon_by_shop: Record<shop UUID, coupon UUID>`. The server rejects an unknown shop, a coupon not owned by the customer or shop, an unavailable or expired coupon, or an unmet threshold. It converts coupon amount and threshold to the buyer-selected order currency with the same enabled-currency conversion used for SKU prices. The server stores `coupon_id` and `discount_minor`; clients never submit a discount.
|
||||
|
||||
### One transaction protects stock and redemption
|
||||
Checkout locks SKU rows in UUID order, then selected coupon rows in UUID order. It conditionally decrements template claim stock on claim and moves a claimed coupon to redeemed only while all eligibility predicates hold. Pending-payment cancellation restores the coupon in the same transaction as SKU stock restoration. Expiry is evaluated at claim/list/checkout and recorded lazily; no scheduler is required.
|
||||
|
||||
### Ownership and surfaces follow existing boundaries
|
||||
Customer routes use `AuthUser`; shop routes require shop roles and `own_shop`. The contract remains exclusively in `@vmall/shared`; Mall replaces direct fixture imports and shop-admin gains template CRUD plus a nav entry. Checkout, payment, and order-detail surfaces show the server `discount_minor`; clients never invent it.
|
||||
|
||||
### Later activities do not stack with coupons
|
||||
This change is coupon-only: threshold and discount use the shop merchandise subtotal after catalog (or converted) line prices. When a later change introduces flash-sale or group-buy line prices, checkout MUST reject `coupon_by_shop` for any generated shop order that applied an activity price. Subsequent `order` spec deltas MUST copy this change's coupon validation, persistence, and pending-payment restore clauses instead of replacing the whole checkout requirement with activity-only text.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- Exchange-rate changes can change an unredeemed coupon's converted value; the realized `discount_minor` is immutable once ordered. Fixing value across currencies would need a new rate-snapshot policy.
|
||||
- Activity-priced shop orders reject coupons rather than stack. Threshold reductions remain unspecified until a later change.
|
||||
- Checkout contract expansion requires the fixed-data adapter to implement the same surface.
|
||||
Reference in New Issue
Block a user