diff --git a/bun.lockb b/bun.lockb index 050aece..52098e0 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 115edd1..760cef2 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "react-resizable-panels": "^4.6.5", "recharts": "^2.15.4", "sonner": "^2.0.7", + "stripe": "^17", "tailwind-merge": "^3.5.0", "tailwindcss": "^4.2.1", "tw-animate-css": "^1.3.4", diff --git a/src/integrations/supabase/types.ts b/src/integrations/supabase/types.ts index 4ce7b79..85e0b56 100644 --- a/src/integrations/supabase/types.ts +++ b/src/integrations/supabase/types.ts @@ -2327,6 +2327,110 @@ export type Database = { }, ] } + payment_requests: { + Row: { + base_amount_cents: number + case_id: string | null + client_id: string | null + collection_id: string | null + created_at: string + created_by: string | null + currency: string + description: string | null + email_sent_at: string | null + fee_amount_cents: number + homeowner_id: string | null + id: string + notes: string | null + paid_at: string | null + recipient_email: string | null + recipient_name: string + status: string + stripe_checkout_url: string | null + stripe_payment_intent_id: string | null + stripe_session_id: string | null + total_amount_cents: number + updated_at: string + } + Insert: { + base_amount_cents: number + case_id?: string | null + client_id?: string | null + collection_id?: string | null + created_at?: string + created_by?: string | null + currency?: string + description?: string | null + email_sent_at?: string | null + fee_amount_cents?: number + homeowner_id?: string | null + id?: string + notes?: string | null + paid_at?: string | null + recipient_email?: string | null + recipient_name: string + status?: string + stripe_checkout_url?: string | null + stripe_payment_intent_id?: string | null + stripe_session_id?: string | null + total_amount_cents: number + updated_at?: string + } + Update: { + base_amount_cents?: number + case_id?: string | null + client_id?: string | null + collection_id?: string | null + created_at?: string + created_by?: string | null + currency?: string + description?: string | null + email_sent_at?: string | null + fee_amount_cents?: number + homeowner_id?: string | null + id?: string + notes?: string | null + paid_at?: string | null + recipient_email?: string | null + recipient_name?: string + status?: string + stripe_checkout_url?: string | null + stripe_payment_intent_id?: string | null + stripe_session_id?: string | null + total_amount_cents?: number + updated_at?: string + } + Relationships: [ + { + foreignKeyName: "payment_requests_case_id_fkey" + columns: ["case_id"] + isOneToOne: false + referencedRelation: "cases" + referencedColumns: ["id"] + }, + { + foreignKeyName: "payment_requests_client_id_fkey" + columns: ["client_id"] + isOneToOne: false + referencedRelation: "clients" + referencedColumns: ["id"] + }, + { + foreignKeyName: "payment_requests_collection_id_fkey" + columns: ["collection_id"] + isOneToOne: false + referencedRelation: "collections" + referencedColumns: ["id"] + }, + { + foreignKeyName: "payment_requests_homeowner_id_fkey" + columns: ["homeowner_id"] + isOneToOne: false + referencedRelation: "homeowners" + referencedColumns: ["id"] + }, + ] + } profiles: { Row: { avatar_url: string | null diff --git a/supabase/migrations/20260418174953_948297af-7c8b-4cdc-8f0f-cdf66772736d.sql b/supabase/migrations/20260418174953_948297af-7c8b-4cdc-8f0f-cdf66772736d.sql new file mode 100644 index 0000000..4d86557 --- /dev/null +++ b/supabase/migrations/20260418174953_948297af-7c8b-4cdc-8f0f-cdf66772736d.sql @@ -0,0 +1,49 @@ +-- Payment requests for homeowner Stripe checkout +CREATE TABLE IF NOT EXISTS public.payment_requests ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + homeowner_id uuid REFERENCES public.homeowners(id) ON DELETE SET NULL, + collection_id uuid REFERENCES public.collections(id) ON DELETE SET NULL, + case_id uuid REFERENCES public.cases(id) ON DELETE SET NULL, + client_id uuid REFERENCES public.clients(id) ON DELETE SET NULL, + recipient_name text NOT NULL, + recipient_email text, + description text, + -- amounts in cents + base_amount_cents integer NOT NULL CHECK (base_amount_cents > 0), + fee_amount_cents integer NOT NULL DEFAULT 0, + total_amount_cents integer NOT NULL, + currency text NOT NULL DEFAULT 'usd', + status text NOT NULL DEFAULT 'pending', -- pending | paid | canceled | expired + stripe_session_id text, + stripe_payment_intent_id text, + stripe_checkout_url text, + paid_at timestamptz, + email_sent_at timestamptz, + notes text, + created_by uuid, + created_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz NOT NULL DEFAULT now() +); + +CREATE INDEX IF NOT EXISTS idx_payment_requests_status ON public.payment_requests(status); +CREATE INDEX IF NOT EXISTS idx_payment_requests_homeowner ON public.payment_requests(homeowner_id); +CREATE INDEX IF NOT EXISTS idx_payment_requests_session ON public.payment_requests(stripe_session_id); + +ALTER TABLE public.payment_requests ENABLE ROW LEVEL SECURITY; + +-- Authenticated users can view all (firm-internal) +CREATE POLICY "pr_select_auth" ON public.payment_requests + FOR SELECT TO authenticated USING (true); + +CREATE POLICY "pr_insert_auth" ON public.payment_requests + FOR INSERT TO authenticated WITH CHECK (auth.uid() IS NOT NULL); + +CREATE POLICY "pr_update_owner_or_admin" ON public.payment_requests + FOR UPDATE TO authenticated USING (is_admin(auth.uid()) OR created_by = auth.uid()); + +CREATE POLICY "pr_delete_owner_or_admin" ON public.payment_requests + FOR DELETE TO authenticated USING (is_admin(auth.uid()) OR created_by = auth.uid()); + +CREATE TRIGGER trg_payment_requests_updated_at + BEFORE UPDATE ON public.payment_requests + FOR EACH ROW EXECUTE FUNCTION public.tg_set_updated_at(); \ No newline at end of file