File size: 3,251 Bytes
aa9892b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
-- 카페 μŒμ„± 선물쿠폰 MVP β€” gift_coupons (2026-05-28, P1a Phase B)
--
-- λ°œν–‰λœ 쿠폰 = 결제 μ™„λ£Œ β†’ λ°›λŠ”μ΄ 펼침 β†’ λ§€μž₯ μ‚¬μš©μ²˜λ¦¬.
-- μš”μ²­μ„œ: doc/runbook/req_cafe_gift_mvp.md Β§1
-- 섀계: doc/runbook/cafe_gift_p1a.md Β§2 sql/049
--
-- λΆ„λ¦¬λœ 토큰 (Plan κ²°μ • #2, #6):
--   - code: 8자 영숫자 (O/0/I/1 μ œμ™Έ, μ•ŒνŒŒλ²³ 32) β€” λ§€μž₯ 직원 수기 μž…λ ₯용
--   - reveal_token: 32 byte hex (DB default) β€” λ°›λŠ”μ΄ /g/<token> reveal μ§„μž…μš©
--   - λ‘˜ λ‹€ unguessable + 뢄리 = code λ…ΈμΆœ μ‹œ reveal_token μ•ˆμ „, reveal μ‹œ code μ•ˆ λ…ΈμΆœ κ°€λŠ₯
--
-- 만료:
--   - expires_at = gift_orders.expires_at 볡사 (λ°±μ—”λ“œκ°€ set, μ •μ±… λ³€κ²½ 좔적)
--   - status='expired' μžλ™ μ „μ΄λŠ” cron (후속), P1a λŠ” 라이브 쑰회 μ‹œ 만료 검사
--
-- β›” νšŒκ·€ κ°€λ“œ: μ‹ κ·œ ν…Œμ΄λΈ”. RLS cafe owner only (사μž₯ redeem κΆŒν•œ).

CREATE TABLE IF NOT EXISTS public.gift_coupons (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),

    order_id        UUID NOT NULL UNIQUE REFERENCES public.gift_orders(id) ON DELETE CASCADE,
    cafe_id         UUID NOT NULL REFERENCES public.cafe_merchants(id) ON DELETE RESTRICT,

    -- λ§€μž₯ 직원 수기 μž…λ ₯용 (μ‚¬λžŒμ΄ 읽기 μ‰¬μš΄ μ½”λ“œ)
    code            TEXT NOT NULL UNIQUE,
    -- λ°›λŠ”μ΄ 펼침용 (λ°±μ—”λ“œκ°€ 생성, μΆ”μΈ‘ λΆˆκ°€)
    reveal_token    TEXT NOT NULL UNIQUE
                    DEFAULT encode(gen_random_bytes(20), 'hex'),

    status          TEXT NOT NULL DEFAULT 'issued'
                    CHECK (status IN ('issued','viewed','redeemed','expired','refunded')),
    viewed_at       TIMESTAMPTZ,                       -- λ°›λŠ”μ΄κ°€ /g/<token> 첫 μ§„μž… μ‹œκ°
    redeemed_at     TIMESTAMPTZ,
    redeemed_by     UUID REFERENCES auth.users(id) ON DELETE SET NULL,  -- μ‚¬μš©μ²˜λ¦¬ν•œ λ§€μž₯ 직원

    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    expires_at      TIMESTAMPTZ NOT NULL              -- order.expires_at 볡사 (λ°±μ—”λ“œ μ„ΈνŒ…)
);

CREATE INDEX IF NOT EXISTS idx_gift_coupons_cafe_status
    ON public.gift_coupons (cafe_id, status, created_at DESC);

ALTER TABLE public.gift_coupons ENABLE ROW LEVEL SECURITY;

-- 사μž₯: 본인 카페 쿠폰 SELECT + UPDATE (redeem)
DROP POLICY IF EXISTS gift_coupons_cafe_rw ON public.gift_coupons;
CREATE POLICY gift_coupons_cafe_rw ON public.gift_coupons FOR ALL
    USING (cafe_id IN (
        SELECT id FROM public.cafe_merchants WHERE owner_user_id = auth.uid()
    ))
    WITH CHECK (cafe_id IN (
        SELECT id FROM public.cafe_merchants WHERE owner_user_id = auth.uid()
    ));

-- INSERT λŠ” λ°±μ—”λ“œ service-role (gift_order_service.mock_pay κ°€ λ°œκΈ‰)
-- reveal_token μ‘°νšŒλŠ” λ°±μ—”λ“œ service-role (gift_reveal λΌμš°ν„° β€” Phase C)

COMMENT ON TABLE public.gift_coupons IS
    'λ°œν–‰ 쿠폰. code = λ§€μž₯ 직원 μž…λ ₯용 8자 영숫자. reveal_token = λ°›λŠ”μ΄ 펼침용 hex.';
COMMENT ON COLUMN public.gift_coupons.code IS
    '8자 영숫자 μ•ŒνŒŒλ²³ ABCDEFGHJKLMNPQRSTUVWXYZ23456789 (O/0/I/1 μ œμ™Έ, 가독성). λ°±μ—”λ“œ 생성 + UNIQUE 좩돌 μž¬μ‹œλ„.';
COMMENT ON COLUMN public.gift_coupons.reveal_token IS
    '32 byte hex. λ°›λŠ”μ΄ /g/<token> μ§„μž…. λ°±μ—”λ“œλ§Œ service-role 둜 쑰회.';