feat(api): group buying with payment-time seat claims
Shop-owned activities on one SKU, concrete groups with an open/successful/expired/cancelled lifecycle, and one paid membership row per paid order. Checkout accepts a single-SKU quantity-1 intent, snapshots the group price and identity on the pending order, and opens or references a group; a paid seat is claimed only at payment, which locks the group and fills it exactly at capacity. Pending-payment cancellation restores SKU stock only and never rolls back paid seats; an unpaid opener cancelling closes a still-empty group. Coupons are refused on a group shop order, and the flash-sale exclusion is now enforced in both directions because the activity table exists, which activates the guard add-flash-sales shipped dormant. The activity column names follow the contract recorded in this change's design. Surfaces (shop-admin, mall) and seeding follow.
This commit is contained in:
@@ -17,6 +17,8 @@ The group-buying page presents fixed participant counts. Unlike a simple price r
|
||||
### Activities and groups have separate state
|
||||
`group_buying_activities` stores shop, SKU, localized presentation, group price minor/currency, required paid members, active window, and group lifetime. Each membership order MAY include the activity SKU at quantity 1 only. `collective_groups` references an activity and records leader order, paid-member count, expiry, and `open | successful | expired | cancelled` state. `collective_group_members` is one row per paid order and group, with a uniqueness constraint per order and customer membership policy.
|
||||
|
||||
**Column contract with the archived flash-sales guard:** the activity columns MUST be named `sku_id`, `enabled`, `starts_at`, and `ends_at`. `add-flash-sales` ships a dormant overlap check that queries exactly those names once this table exists; any other naming turns that guard into a runtime error on flash-item creation. Both directions of the exclusion are then live: flash rejects a SKU with an overlapping activity, and this change rejects an activity for a SKU with an overlapping flash item.
|
||||
|
||||
### Membership finalizes at payment, not cart checkout
|
||||
Checkout accepts an optional group-buy intent for exactly one activity SKU at quantity 1 and snapshots the activity price plus group ID on the pending order. Opening creates an empty open group; joining references an open group. Unpaid orders do not occupy a paid seat. `pay` locks the group and membership rows, expires due groups first, conditionally claims a paid seat, and transitions the group to successful exactly when its paid count reaches the required count. A full, expired, or cancelled group leaves the order pending payment and returns Conflict.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user