Refactor billing gateway and enhance localization

Refactored the 'plan-picker' component in the billing gateway to remove unwanted line items and improve checkout session and subscription handling. Enhanced the localization support by adding translations in the plan picker and introduced a new function to check trial eligibility so that existing customers can't start a new trial period. These changes enhance the usability of the application across different regions and provide accurate trial period conditions.
This commit is contained in:
giancarlo
2024-04-01 11:52:35 +08:00
parent 248ab7ef72
commit d6004f2f7e
15 changed files with 877 additions and 346 deletions

View File

@@ -118,14 +118,14 @@ create type public.subscription_status as ENUM(
'paused'
);
/* Subscription Type
- We create the subscription type for the Supabase MakerKit. These types are used to manage the type of the subscriptions
- The types are 'ONE_OFF' and 'RECURRING'.
- You can add more types as needed.
/*
Payment Status
- We create the payment status for the Supabase MakerKit. These statuses are used to manage the status of the payments
*/
create type public.subscription_type as enum (
'one-off',
'recurring'
create type public.payment_status as ENUM(
'pending',
'succeeded',
'failed'
);
/*
@@ -633,8 +633,6 @@ using (
)
);
/*
* -------------------------------------------------------
* Section: Account Roles
@@ -915,7 +913,8 @@ create table
id serial primary key,
email text,
provider public.billing_provider not null,
customer_id text not null
customer_id text not null,
unique (account_id, customer_id, provider)
);
comment on table public.billing_customers is 'The billing customers for an account';
@@ -959,8 +958,6 @@ create table if not exists public.subscriptions (
account_id uuid references public.accounts (id) on delete cascade not null,
billing_customer_id int references public.billing_customers on delete cascade not null,
status public.subscription_status not null,
type public.subscription_type not null default 'recurring',
total_amount numeric not null,
active bool not null,
billing_provider public.billing_provider not null,
cancel_at_period_end bool not null,
@@ -979,8 +976,6 @@ comment on column public.subscriptions.account_id is 'The account the subscripti
comment on column public.subscriptions.billing_provider is 'The provider of the subscription';
comment on column public.subscriptions.total_amount is 'The total price amount for the subscription';
comment on column public.subscriptions.cancel_at_period_end is 'Whether the subscription will be canceled at the end of the period';
comment on column public.subscriptions.currency is 'The currency for the subscription';
@@ -1018,17 +1013,16 @@ select
-- Functions
create or replace function public.upsert_subscription (
account_id uuid,
subscription_id text,
target_account_id uuid,
target_customer_id varchar(255),
target_subscription_id text,
active bool,
total_amount numeric,
status public.subscription_status,
billing_provider public.billing_provider,
cancel_at_period_end bool,
currency varchar(3),
period_starts_at timestamptz,
period_ends_at timestamptz,
customer_id varchar(255),
line_items jsonb,
trial_starts_at timestamptz default null,
trial_ends_at timestamptz default null,
@@ -1039,7 +1033,7 @@ declare
new_billing_customer_id int;
begin
insert into public.billing_customers(account_id, provider, customer_id)
values (account_id, billing_provider, customer_id)
values (target_account_id, billing_provider, target_customer_id)
on conflict (account_id, provider, customer_id) do update
set provider = excluded.provider
returning id into new_billing_customer_id;
@@ -1049,7 +1043,6 @@ begin
billing_customer_id,
id,
active,
total_amount,
status,
type,
billing_provider,
@@ -1060,11 +1053,10 @@ begin
trial_starts_at,
trial_ends_at)
values (
account_id,
target_account_id,
new_billing_customer_id,
subscription_id,
active,
total_amount,
status,
type,
billing_provider,
@@ -1125,23 +1117,21 @@ $$ language plpgsql;
grant execute on function public.upsert_subscription (
uuid,
varchar,
text,
bool,
numeric,
public.subscription_status,
public.billing_provider,
bool,
varchar,
timestamptz,
timestamptz,
varchar,
jsonb,
timestamptz,
timestamptz,
public.subscription_type
) to service_role;
/* -------------------------------------------------------
* Section: Subscription Items
* We create the schema for the subscription items. Subscription items are the items in a subscription.
@@ -1149,7 +1139,6 @@ grant execute on function public.upsert_subscription (
* -------------------------------------------------------
*/
create table if not exists public.subscription_items (
id text not null primary key,
subscription_id text references public.subscriptions (id) on delete cascade not null,
product_id varchar(255) not null,
variant_id varchar(255) not null,
@@ -1158,7 +1147,8 @@ create table if not exists public.subscription_items (
interval varchar(255) not null,
interval_count integer not null check (interval_count > 0),
created_at timestamptz not null default current_timestamp,
updated_at timestamptz not null default current_timestamp
updated_at timestamptz not null default current_timestamp,
unique (subscription_id, product_id, variant_id)
);
comment on table public.subscription_items is 'The items in a subscription';
@@ -1188,6 +1178,147 @@ select
)
);
/**
* -------------------------------------------------------
* Section: Orders
* We create the schema for the subscription items. Subscription items are the items in a subscription.
* For example, a subscription might have a subscription item with the product ID 'prod_123' and the variant ID 'var_123'.
* -------------------------------------------------------
*/
create table if not exists public.orders (
id text not null primary key,
account_id uuid references public.accounts (id) on delete cascade not null,
billing_customer_id int references public.billing_customers on delete cascade not null,
status public.payment_status not null,
billing_provider public.billing_provider not null,
total_amount numeric not null,
currency varchar(3) not null,
created_at timestamptz not null default current_timestamp,
updated_at timestamptz not null default current_timestamp
);
-- Open up access to subscription_items table for authenticated users and service_role
grant select on table public.orders to authenticated, service_role;
grant insert, update, delete on table public.orders to service_role;
-- RLS
alter table public.orders enable row level security;
-- SELECT
-- Users can read orders on an account they are a member of or the account is their own
create policy orders_read_self on public.orders for
select
to authenticated using (
account_id = auth.uid () or has_role_on_account (account_id)
);
/**
* -------------------------------------------------------
* Section: Order Items
* We create the schema for the order items. Order items are the items in an order.
* -------------------------------------------------------
*/
create table if not exists public.order_items (
order_id text references public.orders (id) on delete cascade not null,
product_id text not null,
variant_id text not null,
price_amount numeric,
quantity integer not null default 1,
created_at timestamptz not null default current_timestamp,
updated_at timestamptz not null default current_timestamp,
unique (order_id, product_id, variant_id)
);
-- Open up access to order_items table for authenticated users and service_role
grant select on table public.order_items to authenticated, service_role;
-- RLS
alter table public.order_items enable row level security;
-- SELECT
-- Users can read order items on an order they are a member of
create policy order_items_read_self on public.order_items for
select
to authenticated using (
exists (
select 1 from public.orders where id = order_id and (account_id = auth.uid () or has_role_on_account (account_id))
)
);
-- Functions
create or replace function public.upsert_order(
target_account_id uuid,
target_customer_id varchar(255),
order_id text,
status public.payment_status,
billing_provider public.billing_provider,
total_amount numeric,
currency varchar(3),
line_items jsonb
) returns public.orders as $$
declare
new_order public.orders;
new_billing_customer_id int;
begin
insert into public.billing_customers(account_id, provider, customer_id)
values (target_account_id, target_billing_provider, target_customer_id)
on conflict (account_id, provider, customer_id) do update
set provider = excluded.provider
returning id into new_billing_customer_id;
insert into public.orders(
account_id,
billing_customer_id,
id,
status,
billing_provider,
total_amount,
currency)
values (
target_account_id,
new_billing_customer_id,
order_id,
status,
billing_provider,
total_amount,
currency)
on conflict (id) do update
set status = excluded.status,
total_amount = excluded.total_amount,
currency = excluded.currency
returning * into new_order;
insert into public.order_items(
order_id,
product_id,
variant_id,
price_amount,
quantity)
select
target_order_id,
(line_item ->> 'product_id')::varchar,
(line_item ->> 'variant_id')::varchar,
(line_item ->> 'price_amount')::numeric,
(line_item ->> 'quantity')::integer
from jsonb_array_elements(line_items) as line_item
on conflict (order_id, product_id, variant_id) do update
set price_amount = excluded.price_amount,
quantity = excluded.quantity;
return new_order;
end;
$$ language plpgsql;
grant execute on function public.upsert_order (
uuid,
varchar,
text,
public.payment_status,
public.billing_provider,
numeric,
varchar,
jsonb
) to service_role;
/*
* -------------------------------------------------------
* Section: Functions