forked from vercel/nextjs-subscription-payments
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathschema.sql
160 lines (148 loc) · 5.01 KB
/
schema.sql
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
-- Core types
create type pricing_type as enum ('one_time', 'recurring');
create type pricing_plan_interval as enum ('day', 'week', 'month', 'year');
create type subscription_status as enum ('trialing', 'active', 'canceled', 'incomplete', 'incomplete_expired', 'past_due', 'unpaid', 'paused', 'inactive');
/**
* USERS
* Note: This table contains user data. Users should only be able to view and update their own data.
*/
create table users (
id uuid references auth.users not null primary key,
full_name text,
avatar_url text,
billing_address jsonb,
payment_method jsonb,
paystack_authorization jsonb,
paystack_card_details jsonb
);
alter table users enable row level security;
create policy "Can view own user data." on users for select using (auth.uid() = id);
create policy "Can update own user data." on users for update using (auth.uid() = id);
/**
* This trigger automatically creates a user entry when a new user signs up via Supabase Auth.
*/
create function public.handle_new_user()
returns trigger as $$
begin
insert into public.users (id, full_name, avatar_url)
values (new.id, new.raw_user_meta_data->>'full_name', new.raw_user_meta_data->>'avatar_url');
return new;
end;
$$ language plpgsql security definer;
create trigger on_auth_user_created
after insert on auth.users
for each row execute procedure public.handle_new_user();
/**
* CUSTOMERS
* Note: this is a private table that contains a mapping of user IDs to payment provider customer IDs.
*/
create table customers (
id uuid references auth.users not null primary key,
stripe_customer_id text,
paystack_customer_id text
);
alter table customers enable row level security;
create index idx_customers_paystack_id on customers(paystack_customer_id);
/**
* PRODUCTS
* Note: products are created and managed in payment provider and synced to our DB via webhooks.
*/
create table products (
id text primary key,
active boolean,
name text,
description text,
image text,
metadata jsonb
);
alter table products enable row level security;
create policy "Allow public read-only access." on products for select using (true);
/**
* PRICES
*/
create table prices (
id text primary key,
product_id text references products,
active boolean,
description text,
unit_amount bigint,
currency text check (char_length(currency) = 3),
type pricing_type,
interval pricing_plan_interval,
interval_count integer,
trial_period_days integer,
metadata jsonb
);
alter table prices enable row level security;
create policy "Allow public read-only access." on prices for select using (true);
/**
* SUBSCRIPTIONS
*/
create table subscriptions (
id text primary key,
user_id uuid references auth.users not null,
status subscription_status,
metadata jsonb,
price_id text references prices,
quantity integer,
cancel_at_period_end boolean,
created timestamp with time zone default timezone('utc'::text, now()) not null,
current_period_start timestamp with time zone default timezone('utc'::text, now()) not null,
current_period_end timestamp with time zone default timezone('utc'::text, now()) not null,
ended_at timestamp with time zone default timezone('utc'::text, now()),
cancel_at timestamp with time zone default timezone('utc'::text, now()),
canceled_at timestamp with time zone default timezone('utc'::text, now()),
trial_start timestamp with time zone default timezone('utc'::text, now()),
trial_end timestamp with time zone default timezone('utc'::text, now())
);
alter table subscriptions enable row level security;
create policy "Can only view own subs data." on subscriptions for select using (auth.uid() = user_id);
create index idx_subscriptions_user_id on subscriptions(user_id);
/**
* PAYSTACK-SPECIFIC FUNCTIONS AND TRIGGERS
*/
create function handle_paystack_subscription_change()
returns trigger as $$
begin
if NEW.metadata->>'paystack_status' is not null then
case NEW.metadata->>'paystack_status'
when 'active' then
NEW.status := 'active';
when 'non-renewing' then
NEW.status := 'canceled';
when 'attention' then
NEW.status := 'past_due';
when 'completed' then
NEW.status := 'ended';
else
NEW.status := 'inactive';
end case;
end if;
return NEW;
end;
$$ language plpgsql;
create trigger on_paystack_subscription_change
before insert or update on subscriptions
for each row
execute function handle_paystack_subscription_change();
create function get_or_create_paystack_customer(user_id uuid)
returns text as $$
declare
customer_id text;
begin
select paystack_customer_id into customer_id
from customers
where id = user_id;
if customer_id is null then
insert into customers (id)
values (user_id)
on conflict (id) do nothing;
end if;
return customer_id;
end;
$$ language plpgsql security definer;
/**
* REALTIME SUBSCRIPTIONS
*/
drop publication if exists supabase_realtime;
create publication supabase_realtime for table products, prices;