Spaces:
Running
Running
| /** | |
| * ------------------------------------------------------- | |
| * Section - Billing | |
| * ------------------------------------------------------- | |
| */ | |
| /** | |
| * Subscription Status | |
| * Tracks the current status of the account subscription | |
| */ | |
| DO | |
| $$ | |
| BEGIN | |
| IF NOT EXISTS(SELECT 1 | |
| FROM pg_type t | |
| JOIN pg_namespace n ON n.oid = t.typnamespace | |
| WHERE t.typname = 'subscription_status' | |
| AND n.nspname = 'basejump') THEN | |
| create type basejump.subscription_status as enum ( | |
| 'trialing', | |
| 'active', | |
| 'canceled', | |
| 'incomplete', | |
| 'incomplete_expired', | |
| 'past_due', | |
| 'unpaid' | |
| ); | |
| end if; | |
| end; | |
| $$; | |
| /** | |
| * Billing customer | |
| * This is a private table that contains a mapping of user IDs to your billing providers IDs | |
| */ | |
| create table if not exists basejump.billing_customers | |
| ( | |
| -- UUID from auth.users | |
| account_id uuid references basejump.accounts (id) on delete cascade not null, | |
| -- The user's customer ID in Stripe. User must not be able to update this. | |
| id text primary key, | |
| -- The email address the customer wants to use for invoicing | |
| email text, | |
| -- The active status of a customer | |
| active boolean, | |
| -- The billing provider the customer is using | |
| provider text | |
| ); | |
| -- Open up access to billing_customers | |
| GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE basejump.billing_customers TO service_role; | |
| GRANT SELECT ON TABLE basejump.billing_customers TO authenticated; | |
| -- enable RLS for billing_customers | |
| alter table | |
| basejump.billing_customers | |
| enable row level security; | |
| /** | |
| * Billing subscriptions | |
| * This is a private table that contains a mapping of account IDs to your billing providers subscription IDs | |
| */ | |
| create table if not exists basejump.billing_subscriptions | |
| ( | |
| -- Subscription ID from Stripe, e.g. sub_1234. | |
| id text primary key, | |
| account_id uuid references basejump.accounts (id) on delete cascade not null, | |
| billing_customer_id text references basejump.billing_customers (id) on delete cascade not null, | |
| -- The status of the subscription object, one of subscription_status type above. | |
| status basejump.subscription_status, | |
| -- Set of key-value pairs, used to store additional information about the object in a structured format. | |
| metadata jsonb, | |
| -- ID of the price that created this subscription. | |
| price_id text, | |
| plan_name text, | |
| -- Quantity multiplied by the unit amount of the price creates the amount of the subscription. Can be used to charge multiple seats. | |
| quantity integer, | |
| -- If true the subscription has been canceled by the user and will be deleted at the end of the billing period. | |
| cancel_at_period_end boolean, | |
| -- Time at which the subscription was created. | |
| created timestamp with time zone default timezone('utc' :: text, now()) not null, | |
| -- Start of the current period that the subscription has been invoiced for. | |
| current_period_start timestamp with time zone default timezone('utc' :: text, now()) not null, | |
| -- End of the current period that the subscription has been invoiced for. At the end of this period, a new invoice will be created. | |
| current_period_end timestamp with time zone default timezone('utc' :: text, now()) not null, | |
| -- If the subscription has ended, the timestamp of the date the subscription ended. | |
| ended_at timestamp with time zone default timezone('utc' :: text, now()), | |
| -- A date in the future at which the subscription will automatically get canceled. | |
| cancel_at timestamp with time zone default timezone('utc' :: text, now()), | |
| -- If the subscription has been canceled, the date of that cancellation. If the subscription was canceled with `cancel_at_period_end`, `canceled_at` will still reflect the date of the initial cancellation request, not the end of the subscription period when the subscription is automatically moved to a canceled state. | |
| canceled_at timestamp with time zone default timezone('utc' :: text, now()), | |
| -- If the subscription has a trial, the beginning of that trial. | |
| trial_start timestamp with time zone default timezone('utc' :: text, now()), | |
| -- If the subscription has a trial, the end of that trial. | |
| trial_end timestamp with time zone default timezone('utc' :: text, now()), | |
| provider text | |
| ); | |
| -- Open up access to billing_subscriptions | |
| GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE basejump.billing_subscriptions TO service_role; | |
| GRANT SELECT ON TABLE basejump.billing_subscriptions TO authenticated; | |
| -- enable RLS for billing_subscriptions | |
| alter table | |
| basejump.billing_subscriptions | |
| enable row level security; | |
| /** | |
| * ------------------------- | |
| * Section - RLS Policies | |
| * ------------------------- | |
| * This is where we define access to tables in the basejump schema | |
| */ | |
| create policy "Can only view own billing customer data." on basejump.billing_customers for | |
| select | |
| using ( | |
| basejump.has_role_on_account(account_id) = true | |
| ); | |
| create policy "Can only view own billing subscription data." on basejump.billing_subscriptions for | |
| select | |
| using ( | |
| basejump.has_role_on_account(account_id) = true | |
| ); | |
| /** | |
| * ------------------------------------------------------- | |
| * Section - Public functions | |
| * ------------------------------------------------------- | |
| * Each of these functions exists in the public name space because they are accessible | |
| * via the API. it is the primary way developers can interact with Basejump accounts | |
| */ | |
| /** | |
| * Returns the current billing status for an account | |
| */ | |
| CREATE OR REPLACE FUNCTION public.get_account_billing_status(account_id uuid) | |
| RETURNS jsonb | |
| security definer | |
| set search_path = public, basejump | |
| AS | |
| $$ | |
| DECLARE | |
| result jsonb; | |
| role_result jsonb; | |
| BEGIN | |
| select public.current_user_account_role(get_account_billing_status.account_id) into role_result; | |
| select jsonb_build_object( | |
| 'account_id', get_account_billing_status.account_id, | |
| 'billing_subscription_id', s.id, | |
| 'billing_enabled', case | |
| when a.personal_account = true then config.enable_personal_account_billing | |
| else config.enable_team_account_billing end, | |
| 'billing_status', s.status, | |
| 'billing_customer_id', c.id, | |
| 'billing_provider', config.billing_provider, | |
| 'billing_email', | |
| coalesce(c.email, u.email) -- if we don't have a customer email, use the user's email as a fallback | |
| ) | |
| into result | |
| from basejump.accounts a | |
| join auth.users u on u.id = a.primary_owner_user_id | |
| left join basejump.billing_subscriptions s on s.account_id = a.id | |
| left join basejump.billing_customers c on c.account_id = coalesce(s.account_id, a.id) | |
| join basejump.config config on true | |
| where a.id = get_account_billing_status.account_id | |
| order by s.created desc | |
| limit 1; | |
| return result || role_result; | |
| END; | |
| $$ LANGUAGE plpgsql; | |
| grant execute on function public.get_account_billing_status(uuid) to authenticated; | |
| /** | |
| * Allow service accounts to upsert the billing data for an account | |
| */ | |
| CREATE OR REPLACE FUNCTION public.service_role_upsert_customer_subscription(account_id uuid, | |
| customer jsonb default null, | |
| subscription jsonb default null) | |
| RETURNS void AS | |
| $$ | |
| BEGIN | |
| -- if the customer is not null, upsert the data into billing_customers, only upsert fields that are present in the jsonb object | |
| if customer is not null then | |
| insert into basejump.billing_customers (id, account_id, email, provider) | |
| values (customer ->> 'id', service_role_upsert_customer_subscription.account_id, customer ->> 'billing_email', | |
| (customer ->> 'provider')) | |
| on conflict (id) do update | |
| set email = customer ->> 'billing_email'; | |
| end if; | |
| -- if the subscription is not null, upsert the data into billing_subscriptions, only upsert fields that are present in the jsonb object | |
| if subscription is not null then | |
| insert into basejump.billing_subscriptions (id, account_id, billing_customer_id, status, metadata, price_id, | |
| quantity, cancel_at_period_end, created, current_period_start, | |
| current_period_end, ended_at, cancel_at, canceled_at, trial_start, | |
| trial_end, plan_name, provider) | |
| values (subscription ->> 'id', service_role_upsert_customer_subscription.account_id, | |
| subscription ->> 'billing_customer_id', (subscription ->> 'status')::basejump.subscription_status, | |
| subscription -> 'metadata', | |
| subscription ->> 'price_id', (subscription ->> 'quantity')::int, | |
| (subscription ->> 'cancel_at_period_end')::boolean, | |
| (subscription ->> 'created')::timestamptz, (subscription ->> 'current_period_start')::timestamptz, | |
| (subscription ->> 'current_period_end')::timestamptz, (subscription ->> 'ended_at')::timestamptz, | |
| (subscription ->> 'cancel_at')::timestamptz, | |
| (subscription ->> 'canceled_at')::timestamptz, (subscription ->> 'trial_start')::timestamptz, | |
| (subscription ->> 'trial_end')::timestamptz, | |
| subscription ->> 'plan_name', (subscription ->> 'provider')) | |
| on conflict (id) do update | |
| set billing_customer_id = subscription ->> 'billing_customer_id', | |
| status = (subscription ->> 'status')::basejump.subscription_status, | |
| metadata = subscription -> 'metadata', | |
| price_id = subscription ->> 'price_id', | |
| quantity = (subscription ->> 'quantity')::int, | |
| cancel_at_period_end = (subscription ->> 'cancel_at_period_end')::boolean, | |
| current_period_start = (subscription ->> 'current_period_start')::timestamptz, | |
| current_period_end = (subscription ->> 'current_period_end')::timestamptz, | |
| ended_at = (subscription ->> 'ended_at')::timestamptz, | |
| cancel_at = (subscription ->> 'cancel_at')::timestamptz, | |
| canceled_at = (subscription ->> 'canceled_at')::timestamptz, | |
| trial_start = (subscription ->> 'trial_start')::timestamptz, | |
| trial_end = (subscription ->> 'trial_end')::timestamptz, | |
| plan_name = subscription ->> 'plan_name'; | |
| end if; | |
| end; | |
| $$ LANGUAGE plpgsql; | |
| GRANT EXECUTE ON FUNCTION public.service_role_upsert_customer_subscription(uuid, jsonb, jsonb) TO service_role; |