Public Access
1
0
Files
leads4less/db/migrations/0003_billing_foundation.sql
T
pguerrerox 94b8c357b4 feat: add billing foundation and entitlement enforcement
- add workspace-scoped billing storage, usage tracking, and add-on catalog
- enforce plan entitlements for search and deep research routes
- expand pricing and account UI around billing state, usage, and upgrades
2026-05-22 17:50:28 +00:00

133 lines
6.6 KiB
SQL

create table if not exists public.workspace_billing_accounts (
id uuid primary key default gen_random_uuid(),
workspace_id uuid not null references public.workspaces (id) on delete cascade,
plan_code text,
billing_interval text check (billing_interval in ('monthly', 'annual', 'custom')),
status text not null check (status in ('not_configured', 'inactive', 'active', 'past_due', 'canceled')),
current_period_starts_at timestamptz,
current_period_ends_at timestamptz,
cancel_at_period_end boolean not null default false,
canceled_at timestamptz,
trial_ends_at timestamptz,
external_customer_ref text,
external_subscription_ref text,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
constraint workspace_billing_accounts_workspace_id_key unique (workspace_id),
constraint workspace_billing_accounts_period_bounds_check check (
(current_period_starts_at is null and current_period_ends_at is null)
or (current_period_starts_at is not null and current_period_ends_at is not null)
)
);
create table if not exists public.workspace_usage_periods (
id uuid primary key default gen_random_uuid(),
workspace_id uuid not null references public.workspaces (id) on delete cascade,
billing_account_id uuid not null references public.workspace_billing_accounts (id) on delete cascade,
period_starts_at timestamptz not null,
period_ends_at timestamptz not null,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
constraint workspace_usage_periods_workspace_period_key unique (workspace_id, period_starts_at, period_ends_at),
constraint workspace_usage_periods_bounds_check check (period_starts_at < period_ends_at)
);
create table if not exists public.workspace_usage_counters (
id uuid primary key default gen_random_uuid(),
usage_period_id uuid not null references public.workspace_usage_periods (id) on delete cascade,
workspace_id uuid not null references public.workspaces (id) on delete cascade,
resource text not null check (resource in ('research_credits', 'exports', 'enrichments', 'api_requests')),
consumed_quantity integer not null default 0 check (consumed_quantity >= 0),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
constraint workspace_usage_counters_period_resource_key unique (usage_period_id, resource)
);
create table if not exists public.workspace_addon_purchases (
id uuid primary key default gen_random_uuid(),
workspace_id uuid not null references public.workspaces (id) on delete cascade,
addon_code text not null check (addon_code in ('export_pack_10k', 'export_pack_50k', 'enrichment_pack_1k', 'ai_assistant_monthly', 'white_label_monthly')),
resource text not null check (resource in ('research_credits', 'exports', 'enrichments', 'api_requests')),
purchased_quantity integer not null check (purchased_quantity >= 0),
remaining_quantity integer not null check (remaining_quantity >= 0),
purchased_at timestamptz not null default now(),
expires_at timestamptz,
external_purchase_ref text,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create table if not exists public.workspace_addon_balances (
id uuid primary key default gen_random_uuid(),
workspace_id uuid not null references public.workspaces (id) on delete cascade,
addon_code text not null check (addon_code in ('export_pack_10k', 'export_pack_50k', 'enrichment_pack_1k', 'ai_assistant_monthly', 'white_label_monthly')),
resource text not null check (resource in ('research_credits', 'exports', 'enrichments', 'api_requests')),
remaining_quantity integer not null default 0 check (remaining_quantity >= 0),
expires_at timestamptz,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
constraint workspace_addon_balances_workspace_addon_resource_key unique (workspace_id, addon_code, resource)
);
create index if not exists workspace_billing_accounts_status_idx on public.workspace_billing_accounts (status);
create index if not exists workspace_billing_accounts_plan_code_idx on public.workspace_billing_accounts (plan_code);
create index if not exists workspace_usage_periods_workspace_id_idx on public.workspace_usage_periods (workspace_id);
create index if not exists workspace_usage_counters_workspace_id_idx on public.workspace_usage_counters (workspace_id);
create index if not exists workspace_addon_purchases_workspace_id_idx on public.workspace_addon_purchases (workspace_id);
create index if not exists workspace_addon_balances_workspace_id_idx on public.workspace_addon_balances (workspace_id);
drop trigger if exists set_workspace_billing_accounts_updated_at on public.workspace_billing_accounts;
create trigger set_workspace_billing_accounts_updated_at
before update on public.workspace_billing_accounts
for each row
execute function public.set_updated_at();
drop trigger if exists set_workspace_usage_periods_updated_at on public.workspace_usage_periods;
create trigger set_workspace_usage_periods_updated_at
before update on public.workspace_usage_periods
for each row
execute function public.set_updated_at();
drop trigger if exists set_workspace_usage_counters_updated_at on public.workspace_usage_counters;
create trigger set_workspace_usage_counters_updated_at
before update on public.workspace_usage_counters
for each row
execute function public.set_updated_at();
drop trigger if exists set_workspace_addon_purchases_updated_at on public.workspace_addon_purchases;
create trigger set_workspace_addon_purchases_updated_at
before update on public.workspace_addon_purchases
for each row
execute function public.set_updated_at();
drop trigger if exists set_workspace_addon_balances_updated_at on public.workspace_addon_balances;
create trigger set_workspace_addon_balances_updated_at
before update on public.workspace_addon_balances
for each row
execute function public.set_updated_at();
insert into public.workspace_billing_accounts (
workspace_id,
plan_code,
billing_interval,
status,
current_period_starts_at,
current_period_ends_at
)
select w.id, 'starter_monthly', 'monthly', 'active', now(), now() + interval '1 month'
from public.workspaces w
where not exists (
select 1
from public.workspace_billing_accounts billing
where billing.workspace_id = w.id
);
update public.workspace_billing_accounts
set
plan_code = 'starter_monthly',
billing_interval = 'monthly',
status = 'active',
current_period_starts_at = coalesce(current_period_starts_at, now()),
current_period_ends_at = coalesce(current_period_ends_at, now() + interval '1 month')
where plan_code is null and status = 'not_configured';