suna/backend/supabase/migrations/20250818180950_topup_credit...

179 lines
6.5 KiB
PL/PgSQL

CREATE TABLE IF NOT EXISTS public.credit_purchases (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
amount_dollars DECIMAL(10, 2) NOT NULL CHECK (amount_dollars > 0),
stripe_payment_intent_id TEXT UNIQUE,
stripe_charge_id TEXT,
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'completed', 'failed', 'refunded')),
description TEXT,
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW(),
completed_at TIMESTAMPTZ,
expires_at TIMESTAMPTZ,
CONSTRAINT credit_purchases_amount_positive CHECK (amount_dollars > 0)
);
CREATE TABLE IF NOT EXISTS public.credit_balance (
user_id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
balance_dollars DECIMAL(10, 2) NOT NULL DEFAULT 0 CHECK (balance_dollars >= 0),
total_purchased DECIMAL(10, 2) NOT NULL DEFAULT 0 CHECK (total_purchased >= 0),
total_used DECIMAL(10, 2) NOT NULL DEFAULT 0 CHECK (total_used >= 0),
last_updated TIMESTAMPTZ DEFAULT NOW(),
metadata JSONB DEFAULT '{}'
);
CREATE TABLE IF NOT EXISTS public.credit_usage (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
amount_dollars DECIMAL(10, 2) NOT NULL CHECK (amount_dollars > 0),
thread_id UUID REFERENCES public.threads(thread_id) ON DELETE SET NULL,
message_id UUID REFERENCES public.messages(message_id) ON DELETE SET NULL,
description TEXT,
usage_type TEXT DEFAULT 'token_overage' CHECK (usage_type IN ('token_overage', 'manual_deduction', 'adjustment')),
created_at TIMESTAMPTZ DEFAULT NOW(),
subscription_tier TEXT,
metadata JSONB DEFAULT '{}'
);
CREATE INDEX IF NOT EXISTS idx_credit_purchases_user_id ON public.credit_purchases(user_id);
CREATE INDEX IF NOT EXISTS idx_credit_purchases_status ON public.credit_purchases(status);
CREATE INDEX IF NOT EXISTS idx_credit_purchases_created_at ON public.credit_purchases(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_credit_purchases_stripe_payment_intent ON public.credit_purchases(stripe_payment_intent_id);
CREATE INDEX IF NOT EXISTS idx_credit_usage_user_id ON public.credit_usage(user_id);
CREATE INDEX IF NOT EXISTS idx_credit_usage_created_at ON public.credit_usage(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_credit_usage_thread_id ON public.credit_usage(thread_id);
ALTER TABLE public.credit_purchases ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.credit_balance ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.credit_usage ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS "Users can view their own credit purchases" ON public.credit_purchases;
DROP POLICY IF EXISTS "Service role can manage all credit purchases" ON public.credit_purchases;
DROP POLICY IF EXISTS "Users can view their own credit balance" ON public.credit_balance;
DROP POLICY IF EXISTS "Service role can manage all credit balances" ON public.credit_balance;
DROP POLICY IF EXISTS "Users can view their own credit usage" ON public.credit_usage;
DROP POLICY IF EXISTS "Service role can manage all credit usage" ON public.credit_usage;
CREATE POLICY "Users can view their own credit purchases" ON public.credit_purchases
FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "Service role can manage all credit purchases" ON public.credit_purchases
FOR ALL USING (auth.role() = 'service_role');
CREATE POLICY "Users can view their own credit balance" ON public.credit_balance
FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "Service role can manage all credit balances" ON public.credit_balance
FOR ALL USING (auth.role() = 'service_role');
CREATE POLICY "Users can view their own credit usage" ON public.credit_usage
FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "Service role can manage all credit usage" ON public.credit_usage
FOR ALL USING (auth.role() = 'service_role');
CREATE OR REPLACE FUNCTION public.add_credits(
p_user_id UUID,
p_amount DECIMAL,
p_purchase_id UUID DEFAULT NULL
)
RETURNS DECIMAL
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
new_balance DECIMAL;
BEGIN
INSERT INTO public.credit_balance (user_id, balance_dollars, total_purchased)
VALUES (p_user_id, p_amount, p_amount)
ON CONFLICT (user_id) DO UPDATE
SET
balance_dollars = credit_balance.balance_dollars + p_amount,
total_purchased = credit_balance.total_purchased + p_amount,
last_updated = NOW()
RETURNING balance_dollars INTO new_balance;
RETURN new_balance;
END;
$$;
CREATE OR REPLACE FUNCTION public.use_credits(
p_user_id UUID,
p_amount DECIMAL,
p_description TEXT DEFAULT NULL,
p_thread_id UUID DEFAULT NULL,
p_message_id UUID DEFAULT NULL
)
RETURNS BOOLEAN
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
current_balance DECIMAL;
success BOOLEAN := FALSE;
BEGIN
SELECT balance_dollars INTO current_balance
FROM public.credit_balance
WHERE user_id = p_user_id
FOR UPDATE;
IF current_balance IS NOT NULL AND current_balance >= p_amount THEN
UPDATE public.credit_balance
SET
balance_dollars = balance_dollars - p_amount,
total_used = total_used + p_amount,
last_updated = NOW()
WHERE user_id = p_user_id;
INSERT INTO public.credit_usage (
user_id,
amount_dollars,
description,
thread_id,
message_id,
usage_type
)
VALUES (
p_user_id,
p_amount,
p_description,
p_thread_id,
p_message_id,
'token_overage'
);
success := TRUE;
END IF;
RETURN success;
END;
$$;
CREATE OR REPLACE FUNCTION public.get_credit_balance(p_user_id UUID)
RETURNS DECIMAL
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
balance DECIMAL;
BEGIN
SELECT balance_dollars INTO balance
FROM public.credit_balance
WHERE user_id = p_user_id;
RETURN COALESCE(balance, 0);
END;
$$;
GRANT SELECT ON public.credit_purchases TO authenticated;
GRANT SELECT ON public.credit_balance TO authenticated;
GRANT SELECT ON public.credit_usage TO authenticated;
GRANT ALL ON public.credit_purchases TO service_role;
GRANT ALL ON public.credit_balance TO service_role;
GRANT ALL ON public.credit_usage TO service_role;
GRANT EXECUTE ON FUNCTION public.add_credits TO service_role;
GRANT EXECUTE ON FUNCTION public.use_credits TO service_role;
GRANT EXECUTE ON FUNCTION public.get_credit_balance TO authenticated, service_role;