feat(api): shop coupons with per-shop checkout redemption

Templates belong to a shop; claiming copies their terms into a customer-owned
snapshot so a later edit or disable cannot rewrite a held coupon. Claim stock
is taken with a guarded decrement after locking the template, and a unique
(user, template) index makes a duplicate claim a 409. Deleting a template
leaves claimed snapshots standing via ON DELETE SET NULL.

Checkout accepts at most one owned coupon per generated shop order, locks the
selected coupons by primary key after the SKU locks, and resolves eligibility
and the discount server-side (ownership, shop, status, window, converted
threshold). The realized discount and coupon id land on the order, and a
pending-payment cancellation restores the coupon in the same transaction as
stock.

The shared contract gains the coupon types, claim/list/manage methods, and the
checkout coupon map; the fixed-data adapter implements the same surface.

Surfaces (shop-admin management, mall coupon pages, checkout selection) and
seeding still follow in tasks 3.1-4.2.
This commit is contained in:
2026-09-18 12:03:00 +00:00
parent 39d0158f29
commit 23955434c6
20 changed files with 1363 additions and 46 deletions
+8 -8
View File
@@ -1,15 +1,15 @@
## 1. Coupon domain and contract
- [ ] 1.1 Add additive Postgres migrations for coupon templates, owned coupon snapshots, order coupon binding, and realized discount minor values.
- [ ] 1.2 Implement the Rust coupon module (DTOs, repository, service, handlers, router registration) with customer and shop RBAC/ownership boundaries.
- [ ] 1.3 Add `@vmall/shared` coupon types, API-client methods, localized UI strings, and matching fixed-data adapter methods.
- [x] 1.1 Add additive Postgres migrations for coupon templates, owned coupon snapshots, order coupon binding, and realized discount minor values.
- [x] 1.2 Implement the Rust coupon module (DTOs, repository, service, handlers, router registration) with customer and shop RBAC/ownership boundaries.
- [x] 1.3 Add `@vmall/shared` coupon types, API-client methods, localized UI strings, and matching fixed-data adapter methods.
## 2. Atomic claims and checkout redemption
- [ ] 2.1 Implement conditional template stock claim and per-customer duplicate protection with valid-window checks.
- [ ] 2.2 Extend checkout request handling with per-shop coupon choices, server-side currency conversion and threshold validation, and persisted discounts.
- [ ] 2.3 Restore redeemed coupons atomically with stock when a pending-payment order is cancelled.
- [ ] 2.4 Add API integration coverage for claim contention, cross-shop/expired/ineligible rejection, multi-shop redemption, and cancellation restoration.
- [x] 2.1 Implement conditional template stock claim and per-customer duplicate protection with valid-window checks.
- [x] 2.2 Extend checkout request handling with per-shop coupon choices, server-side currency conversion and threshold validation, and persisted discounts.
- [x] 2.3 Restore redeemed coupons atomically with stock when a pending-payment order is cancelled.
- [x] 2.4 Add API integration coverage for claim contention, cross-shop/expired/ineligible rejection, multi-shop redemption, and cancellation restoration.
## 3. Merchant and customer surfaces
@@ -20,4 +20,4 @@
## 4. Verification and specification
- [ ] 4.1 Seed deterministic coupon templates and verify the live claim-to-checkout-to-cancel flow against the API.
- [ ] 4.2 Run cargo test for vmall-api, builds for mall and shop-admin, and strict validation for this OpenSpec change.
- [ ] 4.2 Run cargo test for vmall-api, builds for mall and shop-admin, and strict validation for this OpenSpec change.