Variable-amount gift cards purchasable with any payment method. Code-based redemption at checkout (applied to cart like coupons). Partial use across multiple purchases, never expire. Shop owners control min/max amounts and can disable individual cards. Models: GiftCard, GiftCardTransaction, CartGiftCard + migration. Views: purchase page, cart apply/remove, shop admin manage/detail/toggle. Templates: gift_card.j2, gift_card_manage.j2, gift_card_detail.j2. Cart integration: gift cards deduct after coupons in all checkout paths. Tests: 10 new unit tests covering model logic (677 total pass).
3 KiB
3 KiB
MPS-10: Gift Card System — Models & Migration
Problem
Shops want to sell variable-amount gift cards. Buyers pick an amount (slider), purchase with any payment method (including crypto), and receive a code. The recipient enters the code at checkout (like a coupon) and the balance decrements across purchases. Gift cards never expire (permacomputer rules).
This ticket covers the data layer only. Purchase flow (MPS-11), redemption flow (MPS-12), and shop admin UI (MPS-13) are separate tickets.
Solution
New model: MpsGiftCard
| Column | Type | Notes |
|---|---|---|
id |
UUIDType |
PK (uuid1) |
shop_id |
UUIDType |
FK to Shop — card is scoped to one shop |
code |
Unicode(64) |
Unique redemption code, uppercase alphanumeric |
initial_amount_in_cents |
BigInteger |
Amount at time of purchase |
balance_in_cents |
BigInteger |
Current remaining balance |
purchaser_email |
Unicode(256) |
Email of the buyer |
gift_email |
Unicode(256) |
Optional recipient email |
invoice_id |
UUIDType |
FK to Invoice — the purchase transaction |
created_timestamp |
BigInteger |
Milliseconds |
disabled |
Boolean |
Admin kill switch, default False |
Properties:
is_valid— not disabled and balance > 0balance—cents_to_dollars(balance_in_cents)initial_amount—cents_to_dollars(initial_amount_in_cents)shop_uuid_str— string form of shop_id
Code generation: 16-char uppercase alphanumeric (secrets.token_hex(8).upper()),
prefixed with GC- for human readability. Example: GC-A1B2C3D4E5F6G7H8.
New model: MpsGiftCardTransaction
Tracks every time a gift card balance is used at checkout.
| Column | Type | Notes |
|---|---|---|
id |
UUIDType |
PK (uuid1) |
gift_card_id |
UUIDType |
FK to MpsGiftCard |
invoice_id |
UUIDType |
FK to Invoice — the purchase that used the card |
amount_in_cents |
BigInteger |
Amount deducted from balance |
created_timestamp |
BigInteger |
Milliseconds |
Shop settings columns
Add to Shop model:
| Column | Type | Default |
|---|---|---|
gift_card_enabled |
Boolean |
False |
gift_card_min_in_cents |
BigInteger |
500 ($5.00) |
gift_card_max_in_cents |
BigInteger |
25000 ($250.00) |
Helper functions
get_gift_card_by_code(dbsession, code, shop=None)— lookup by code, optionally scoped to shopget_gift_card_by_id(dbsession, gift_card_id)— standard ID lookupget_gift_cards_by_shop(dbsession, shop)— all cards for a shop (admin view)
Files Changed
| File | Change |
|---|---|
models/gift_card.py |
New: MpsGiftCard model |
models/gift_card_transaction.py |
New: MpsGiftCardTransaction model |
models/shop.py |
Add gift_card_enabled, gift_card_min_in_cents, gift_card_max_in_cents |
models/__init__.py |
Import new models |
scripts/alembic/versions/*_gift_card_tables.py |
Migration: new tables + shop columns |
Depends On
Nothing. Foundation ticket.