diff --git a/apps/web/.env.development b/apps/web/.env.development index 060ca404a..83f2d963a 100644 --- a/apps/web/.env.development +++ b/apps/web/.env.development @@ -1,5 +1,18 @@ +# SITE NEXT_PUBLIC_SITE_URL=http://localhost:3000 NEXT_PUBLIC_PRODUCT_NAME=Makerkit +NEXT_PUBLIC_SITE_TITLE="Makerkit - The easiest way to build and manage your SaaS" +NEXT_PUBLIC_SITE_DESCRIPTION="Makerkit is the easiest way to build and manage your SaaS. It provides you with the tools you need to build your SaaS, without the hassle of building it from scratch." +NEXT_PUBLIC_DEFAULT_THEME_MODE=light +NEXT_PUBLIC_THEME_COLOR="#ffffff" +NEXT_PUBLIC_THEME_COLOR_DARK="#0a0a0a" + +# AUTH +NEXT_PUBLIC_AUTH_PASSWORD=true +NEXT_PUBLIC_AUTH_MAGIC_LINK=false + +# BILLING +NEXT_PUBLIC_BILLING_PROVIDER=stripe # SUPABASE NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54321 diff --git a/apps/web/.env.production b/apps/web/.env.production index b3bb2daaf..7444db1d4 100644 --- a/apps/web/.env.production +++ b/apps/web/.env.production @@ -1 +1,4 @@ -## DO NOT ADD VARS HERE UNLESS THEY ARE PUBLIC \ No newline at end of file +## DO NOT ADD VARS HERE UNLESS THEY ARE PUBLIC (eg. prefixed with NEXT_PUBLIC_) + +NEXT_PUBLIC_PRODUCT_NAME=Makerkit +NEXT_PUBLIC_BILLING_PROVIDER=stripe \ No newline at end of file diff --git a/apps/web/app/(dashboard)/home/[account]/billing/server-actions.ts b/apps/web/app/(dashboard)/home/[account]/billing/server-actions.ts index a359de9d0..766d96cea 100644 --- a/apps/web/app/(dashboard)/home/[account]/billing/server-actions.ts +++ b/apps/web/app/(dashboard)/home/[account]/billing/server-actions.ts @@ -62,7 +62,11 @@ export async function createTeamAccountCheckoutSession(params: { throw new Error('Product not found'); } - const { lineItems, trialDays } = getLineItemsFromPlanId(product, planId); + const plan = product?.plans.find((plan) => plan.id === planId); + + if (!plan) { + throw new Error('Plan not found'); + } // find the customer ID for the account if it exists // (eg. if the account has been billed before) @@ -75,12 +79,10 @@ export async function createTeamAccountCheckoutSession(params: { // call the payment gateway to create the checkout session const { checkoutToken } = await service.createCheckoutSession({ accountId, - lineItems, + plan, returnUrl, customerEmail, customerId, - trialDays, - paymentType: product.paymentType, }); // return the checkout token to the client diff --git a/apps/web/config/app.config.ts b/apps/web/config/app.config.ts index 7c3b715fb..76dd292a3 100644 --- a/apps/web/config/app.config.ts +++ b/apps/web/config/app.config.ts @@ -2,11 +2,6 @@ import { z } from 'zod'; const production = process.env.NODE_ENV === 'production'; -enum Themes { - Light = 'light', - Dark = 'dark', -} - const AppConfigSchema = z.object({ name: z .string({ @@ -29,7 +24,7 @@ const AppConfigSchema = z.object({ description: `This is the default locale of your SaaS.`, }) .default('en'), - theme: z.nativeEnum(Themes), + theme: z.enum(['light', 'dark', 'system']), production: z.boolean(), themeColor: z.string(), themeColorDark: z.string(), @@ -37,14 +32,14 @@ const AppConfigSchema = z.object({ const appConfig = AppConfigSchema.parse({ name: process.env.NEXT_PUBLIC_PRODUCT_NAME, - title: 'Awesomely - Your SaaS Title', - description: 'Your SaaS Description', + title: process.env.NEXT_PUBLIC_SITE_TITLE, + description: process.env.NEXT_PUBLIC_SITE_DESCRIPTION, url: process.env.NEXT_PUBLIC_SITE_URL, locale: process.env.NEXT_PUBLIC_DEFAULT_LOCALE, - theme: Themes.Light, + theme: process.env.NEXT_PUBLIC_DEFAULT_THEME_MODE, + themeColor: process.env.NEXT_PUBLIC_THEME_COLOR, + themeColorDark: process.env.NEXT_PUBLIC_THEME_COLOR_DARK, production, - themeColor: '#ffffff', - themeColorDark: '#0a0a0a', }); export default appConfig; diff --git a/apps/web/config/auth.config.ts b/apps/web/config/auth.config.ts index 5b0a30a19..d90a6c078 100644 --- a/apps/web/config/auth.config.ts +++ b/apps/web/config/auth.config.ts @@ -20,8 +20,8 @@ const authConfig = AuthConfigSchema.parse({ // NB: Enable the providers below in the Supabase Console // in your production project providers: { - password: true, - magicLink: false, + password: process.env.NEXT_PUBLIC_AUTH_PASSWORD === 'true', + magicLink: process.env.NEXT_PUBLIC_AUTH_MAGIC_LINK === 'true', oAuth: ['google'], }, } satisfies z.infer); diff --git a/apps/web/config/billing.config.ts b/apps/web/config/billing.config.ts index 5c1151052..a43e45f91 100644 --- a/apps/web/config/billing.config.ts +++ b/apps/web/config/billing.config.ts @@ -1,7 +1,11 @@ -import { createBillingSchema } from '@kit/billing'; +import { BillingProviderSchema, createBillingSchema } from '@kit/billing'; + +const provider = BillingProviderSchema.parse( + process.env.NEXT_PUBLIC_BILLING_PROVIDER, +); export default createBillingSchema({ - provider: 'stripe', + provider, products: [ { id: 'starter', @@ -9,23 +13,37 @@ export default createBillingSchema({ description: 'The perfect plan to get started', currency: 'USD', badge: `Value`, - paymentType: 'recurring', plans: [ { name: 'Starter Monthly', - id: 'price_1NNwYHI1i3VnbZTqI2UzaHIe', - price: 9.99, - recurring: { - interval: 'month', - }, + id: 'starter-monthly', + trialPeriod: 7, + paymentType: 'recurring', + interval: 'month', + lineItems: [ + { + id: 'price_1NNwYHI1i3VnbZTqI2UzaHIe', + name: 'Base', + description: 'Base plan', + cost: 9.99, + type: 'base', + }, + ], }, { name: 'Starter Yearly', id: 'starter-yearly', - price: 99.99, - recurring: { - interval: 'year', - }, + paymentType: 'recurring', + interval: 'year', + lineItems: [ + { + id: 'price_1NNwYHI1i3VnbZTqI2UzaHIe1', + name: 'Base', + description: 'Base plan', + cost: 99.99, + type: 'base', + }, + ], }, ], features: ['Feature 1', 'Feature 2', 'Feature 3'], @@ -37,23 +55,36 @@ export default createBillingSchema({ highlighted: true, description: 'The perfect plan for professionals', currency: 'USD', - paymentType: 'recurring', plans: [ { name: 'Pro Monthly', id: 'pro-monthly', - price: 19.99, - recurring: { - interval: 'month', - }, + paymentType: 'recurring', + interval: 'month', + lineItems: [ + { + id: 'price_1NNwYHI1i3VnbZTqI2UzaHIe2', + name: 'Base', + description: 'Base plan', + cost: 19.99, + type: 'base', + }, + ], }, { name: 'Pro Yearly', id: 'pro-yearly', - price: 199.99, - recurring: { - interval: 'year', - }, + paymentType: 'recurring', + interval: 'year', + lineItems: [ + { + id: 'price_1NNwYHI1i3VnbZTqI2UzaHIe3', + name: 'Base', + description: 'Base plan', + cost: 199.99, + type: 'base', + }, + ], }, ], features: [ @@ -69,23 +100,36 @@ export default createBillingSchema({ name: 'Enterprise', description: 'The perfect plan for enterprises', currency: 'USD', - paymentType: 'recurring', plans: [ { name: 'Enterprise Monthly', id: 'enterprise-monthly', - price: 99.99, - recurring: { - interval: 'month', - }, + paymentType: 'recurring', + interval: 'month', + lineItems: [ + { + id: 'price_1NNwYHI1i3VnbZTqI2UzaHIe4', + name: 'Base', + description: 'Base plan', + cost: 29.99, + type: 'base', + }, + ], }, { name: 'Enterprise Yearly', id: 'enterprise-yearly', - price: 999.99, - recurring: { - interval: 'year', - }, + paymentType: 'recurring', + interval: 'year', + lineItems: [ + { + id: 'price_1NNwYHI1i3VnbZTqI2UzaHIe5', + name: 'Base', + description: 'Base plan', + cost: 299.99, + type: 'base', + }, + ], }, ], features: [ diff --git a/packages/billing-gateway/src/components/current-plan-card.tsx b/packages/billing-gateway/src/components/current-plan-card.tsx index 74c5164b7..a829726f6 100644 --- a/packages/billing-gateway/src/components/current-plan-card.tsx +++ b/packages/billing-gateway/src/components/current-plan-card.tsx @@ -1,8 +1,7 @@ import { formatDate } from 'date-fns'; import { BadgeCheck, CheckCircle2 } from 'lucide-react'; -import { z } from 'zod'; -import { BillingSchema, getProductPlanPairFromId } from '@kit/billing'; +import { BillingConfig, getProductPlanPair } from '@kit/billing'; import { formatCurrency } from '@kit/shared/utils'; import { Database } from '@kit/supabase/database'; import { @@ -29,12 +28,9 @@ export function CurrentPlanCard({ config, }: React.PropsWithChildren<{ subscription: Database['public']['Tables']['subscriptions']['Row']; - config: z.infer; + config: BillingConfig; }>) { - const { plan, product } = getProductPlanPairFromId( - config, - subscription.variant_id, - ); + const { plan, product } = getProductPlanPair(config, subscription.variant_id); return ( diff --git a/packages/billing-gateway/src/components/plan-picker.tsx b/packages/billing-gateway/src/components/plan-picker.tsx index c5bb33a8f..a602ac126 100644 --- a/packages/billing-gateway/src/components/plan-picker.tsx +++ b/packages/billing-gateway/src/components/plan-picker.tsx @@ -8,12 +8,13 @@ import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { - BillingSchema, - RecurringPlanSchema, + BillingConfig, + getBaseLineItem, getPlanIntervals, - getProductPlanPairFromId, + getProductPlanPair, } from '@kit/billing'; import { formatCurrency } from '@kit/shared/utils'; +import { Badge } from '@kit/ui/badge'; import { Button } from '@kit/ui/button'; import { Form, @@ -23,6 +24,7 @@ import { FormLabel, FormMessage, } from '@kit/ui/form'; +import { If } from '@kit/ui/if'; import { Label } from '@kit/ui/label'; import { RadioGroup, @@ -34,7 +36,7 @@ import { cn } from '@kit/ui/utils'; export function PlanPicker( props: React.PropsWithChildren<{ - config: z.infer; + config: BillingConfig; onSubmit: (data: { planId: string; productId: string }) => void; pending?: boolean; }>, @@ -42,7 +44,7 @@ export function PlanPicker( const intervals = useMemo( () => getPlanIntervals(props.config), [props.config], - ); + ) as string[]; const form = useForm({ reValidateMode: 'onChange', @@ -50,17 +52,21 @@ export function PlanPicker( resolver: zodResolver( z .object({ - planId: z.string().min(1), + planId: z.string(), interval: z.string().min(1), }) .refine( (data) => { - const { product, plan } = getProductPlanPairFromId( - props.config, - data.planId, - ); + try { + const { product, plan } = getProductPlanPair( + props.config, + data.planId, + ); - return product && plan; + return product && plan; + } catch { + return false; + } }, { message: `Please pick a plan to continue`, path: ['planId'] }, ), @@ -73,6 +79,15 @@ export function PlanPicker( }); const { interval: selectedInterval } = form.watch(); + const planId = form.getValues('planId'); + + const selectedPlan = useMemo(() => { + try { + return getProductPlanPair(props.config, planId).plan; + } catch { + return; + } + }, [form, props.config, planId]); return (
@@ -147,23 +162,16 @@ export function PlanPicker( {props.config.products.map((product) => { - const plan = - product.paymentType === 'one-time' - ? product.plans[0] - : product.plans.find((item) => { - if ( - 'recurring' in item && - (item as z.infer) - .recurring.interval === selectedInterval - ) { - return item; - } - }); + const plan = product.plans.find( + (item) => item.interval === selectedInterval, + ); if (!plan) { - throw new Error('Plan not found'); + return null; } + const baseLineItem = getBaseLineItem(props.config, plan.id); + return ( -
+
+ +
+ + {plan.trialPeriod} day trial + +
+
+
{formatCurrency( product.currency.toLowerCase(), - plan.price, + baseLineItem.cost, )} -
-
- - per {selectedInterval} - +
+ + per {selectedInterval} + +
@@ -233,7 +251,13 @@ export function PlanPicker( 'Processing...' ) : ( <> - Proceed to payment + + Start {selectedPlan?.trialPeriod} day trial + + )} diff --git a/packages/billing-gateway/src/components/pricing-table.tsx b/packages/billing-gateway/src/components/pricing-table.tsx index 0b8e7f013..e0c7e8fe3 100644 --- a/packages/billing-gateway/src/components/pricing-table.tsx +++ b/packages/billing-gateway/src/components/pricing-table.tsx @@ -5,14 +5,8 @@ import { useState } from 'react'; import Link from 'next/link'; import { CheckCircle, Sparkles } from 'lucide-react'; -import { z } from 'zod'; -import { - BillingSchema, - RecurringPlanInterval, - RecurringPlanSchema, - getPlanIntervals, -} from '@kit/billing'; +import { BillingConfig, getBaseLineItem, getPlanIntervals } from '@kit/billing'; import { formatCurrency } from '@kit/shared/utils'; import { Button } from '@kit/ui/button'; import { Heading } from '@kit/ui/heading'; @@ -20,9 +14,6 @@ import { If } from '@kit/ui/if'; import { Trans } from '@kit/ui/trans'; import { cn } from '@kit/ui/utils'; -type Config = z.infer; -type Interval = z.infer; - interface Paths { signUp: string; } @@ -32,7 +23,7 @@ export function PricingTable({ paths, CheckoutButtonRenderer, }: { - config: Config; + config: BillingConfig; paths: Paths; CheckoutButtonRenderer?: React.ComponentType<{ @@ -40,9 +31,8 @@ export function PricingTable({ highlighted?: boolean; }>; }) { - const intervals = getPlanIntervals(config).filter(Boolean); - - const [interval, setInterval] = useState(intervals[0]!); + const intervals = getPlanIntervals(config).filter(Boolean) as string[]; + const [interval, setInterval] = useState(intervals[0]!); return (
@@ -63,12 +53,7 @@ export function PricingTable({ } > {config.products.map((product) => { - const plan = product.plans.find((item) => - 'recurring' in item - ? (item as z.infer).recurring - .interval === interval - : true, - ); + const plan = product.plans.find((plan) => plan.interval === interval); if (!plan) { console.warn(`No plan found for ${product.name}`); @@ -76,15 +61,14 @@ export function PricingTable({ return; } - if (product.hidden) { - return null; - } + const basePlan = getBaseLineItem(config, plan.id); return ( - {formatCurrency(props.product.currency, props.plan.price)} + {formatCurrency(props.product.currency, props.baseLineItem.cost)} @@ -262,9 +250,9 @@ function ListItem({ children }: React.PropsWithChildren) { function PlanIntervalSwitcher( props: React.PropsWithChildren<{ - intervals: Interval[]; - interval: Interval; - setInterval: (interval: Interval) => void; + intervals: string[]; + interval: string; + setInterval: (interval: string) => void; }>, ) { return ( diff --git a/packages/billing-gateway/src/server/services/billing-gateway/billing-gateway-factory.service.ts b/packages/billing-gateway/src/server/services/billing-gateway/billing-gateway-factory.service.ts index 3979f5571..63352bbf9 100644 --- a/packages/billing-gateway/src/server/services/billing-gateway/billing-gateway-factory.service.ts +++ b/packages/billing-gateway/src/server/services/billing-gateway/billing-gateway-factory.service.ts @@ -1,10 +1,13 @@ import { z } from 'zod'; -import { BillingProvider, BillingStrategyProviderService } from '@kit/billing'; +import { + BillingProviderSchema, + BillingStrategyProviderService, +} from '@kit/billing'; export class BillingGatewayFactoryService { static async GetProviderStrategy( - provider: z.infer, + provider: z.infer, ): Promise { switch (provider) { case 'stripe': { diff --git a/packages/billing-gateway/src/server/services/billing-gateway/billing-gateway.service.ts b/packages/billing-gateway/src/server/services/billing-gateway/billing-gateway.service.ts index b0331e06a..a7e7da0ed 100644 --- a/packages/billing-gateway/src/server/services/billing-gateway/billing-gateway.service.ts +++ b/packages/billing-gateway/src/server/services/billing-gateway/billing-gateway.service.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -import { BillingProvider } from '@kit/billing'; +import { BillingProviderSchema } from '@kit/billing'; import { CancelSubscriptionParamsSchema, CreateBillingCheckoutSchema, @@ -20,7 +20,9 @@ import { BillingGatewayFactoryService } from './billing-gateway-factory.service' * const billingGatewayService = new BillingGatewayService(provider); */ export class BillingGatewayService { - constructor(private readonly provider: z.infer) {} + constructor( + private readonly provider: z.infer, + ) {} /** * Creates a checkout session for billing. diff --git a/packages/billing/src/create-billing-schema.ts b/packages/billing/src/create-billing-schema.ts index c517f6673..1ac902466 100644 --- a/packages/billing/src/create-billing-schema.ts +++ b/packages/billing/src/create-billing-schema.ts @@ -1,229 +1,156 @@ import { z } from 'zod'; -export const RecurringPlanInterval = z.enum(['month', 'year']); +const BillingIntervalSchema = z.enum(['month', 'year']); +const LineItemTypeSchema = z.enum(['base', 'per-seat', 'metered']); -export const BillingProvider = z.enum(['stripe', 'paddle', 'lemon-squeezy']); +export const BillingProviderSchema = z.enum([ + 'stripe', + 'paddle', + 'lemon-squeezy', +]); -export const PaymentType = z.enum(['recurring', 'one-time']); +export const PaymentTypeSchema = z.enum(['one-time', 'recurring']); -export const LineItemUsageType = z.enum(['licensed', 'metered']); - -const RecurringLineItemSchema = z +export const LineItemSchema = z .object({ id: z.string().min(1), - interval: RecurringPlanInterval, - metered: z.boolean().optional().default(false), - costPerUnit: z.number().positive().optional(), - perSeat: z.boolean().default(false).optional().default(false), - usageType: LineItemUsageType.optional().default('licensed'), + name: z.string().min(1), + description: z.string().optional(), + cost: z.number().positive(), + type: LineItemTypeSchema, + unit: z.string().optional(), + included: z.number().optional(), + }) + .refine((data) => data.type !== 'metered' || (data.unit && data.included), { + message: 'Metered line items must have a unit and included amount', + path: ['type', 'unit', 'included'], + }); + +export const PlanSchema = z + .object({ + id: z.string().min(1), + name: z.string().min(1), + description: z.string().optional(), + interval: BillingIntervalSchema.optional(), + lineItems: z.array(LineItemSchema), + trialPeriod: z.number().optional(), + paymentType: PaymentTypeSchema, + }) + .refine((data) => data.lineItems.length > 0, { + message: 'Plans must have at least one line item', + path: ['lineItems'], + }) + .refine((data) => data.lineItems.some((item) => item.type === 'base'), { + message: 'Plans must include a base line item', + path: ['lineItems'], }) .refine( - (schema) => { - if (!schema.metered && schema.perSeat) { - return false; - } - - return true; - }, + (data) => data.paymentType !== 'one-time' || data.interval === undefined, { - message: 'Line item must be either metered or a member seat', - path: ['metered', 'perSeat'], + message: 'One-time plans must not have an interval', + path: ['paymentType', 'interval'], }, ) .refine( - (schema) => { - if (schema.metered && !schema.usageType) { - return false; - } + (data) => data.paymentType !== 'recurring' || data.interval !== undefined, + { + message: 'Recurring plans must have an interval', + path: ['paymentType', 'interval'], + }, + ) + .refine( + (item) => { + const ids = item.lineItems.map((item) => item.id); - return true; + return ids.length === new Set(ids).size; }, { - message: 'Line item must have a usage type', - path: ['usageType'], + message: 'Line item IDs must be unique', + path: ['lineItems'], }, ); -const RecurringSchema = z +const ProductSchema = z .object({ - interval: RecurringPlanInterval, - metered: z.boolean().optional(), - costPerUnit: z.number().positive().optional(), - perSeat: z.boolean().optional(), - usageType: LineItemUsageType.optional(), - addOns: z.array(RecurringLineItemSchema).optional(), + id: z.string().min(1), + name: z.string().min(1), + description: z.string().min(1), + currency: z.string().min(1), + badge: z.string().optional(), + features: z.array(z.string()).nonempty(), + highlighted: z.boolean().optional(), + plans: z.array(PlanSchema), + }) + .refine((data) => data.plans.length > 0, { + message: 'Products must have at least one plan', + path: ['plans'], }) .refine( - (schema) => { - if (schema.metered) { - return schema.costPerUnit; - } + (item) => { + const planIds = item.plans.map((plan) => plan.id); - return true; + return planIds.length === new Set(planIds).size; }, { - message: 'Metered plans must have a cost per unit', - path: ['costPerUnit'], - }, - ) - .refine( - (schema) => { - if (schema.perSeat && !schema.metered) { - return false; - } - - return true; - }, - { - message: 'Per seat plans must be metered', - path: ['perSeat'], - }, - ) - .refine( - (schema) => { - if (schema.metered) { - return !!schema.usageType; - } - - return true; - }, - { - message: 'Metered plans must have a usage type', - path: ['usageType'], - }, - ); - -export const RecurringPlanSchema = z.object({ - name: z.string().min(1).max(100), - id: z.string().min(1), - price: z.number().positive(), - recurring: RecurringSchema, - trialDays: z.number().positive().optional(), -}); - -export const OneTimePlanSchema = z.object({ - id: z.string().min(1), - name: z.string().min(1).max(100), - price: z.number().positive(), -}); - -export const ProductSchema = z - .object({ - id: z.string(), - name: z.string(), - description: z.string(), - currency: z.string().optional().default('USD'), - plans: RecurringPlanSchema.strict() - .array() - .nonempty() - .or(OneTimePlanSchema.strict().array().nonempty()), - paymentType: PaymentType, - features: z.array(z.string()), - badge: z.string().min(1).optional(), - highlighted: z.boolean().default(false).optional(), - hidden: z.boolean().default(false).optional(), - }) - .refine( - (schema) => { - const recurringPlans = schema.plans.filter((plan) => 'recurring' in plan); - - if (recurringPlans.length && schema.paymentType === 'one-time') { - return false; - } - - return true; - }, - { - message: 'One-time products cannot have recurring plans', - path: ['paymentType'], - }, - ) - .refine( - (schema) => { - const recurringPlans = schema.plans.filter((plan) => 'recurring' in plan); - - if (recurringPlans.length === 0 && schema.paymentType === 'recurring') { - return false; - } - - return true; - }, - { - message: - 'The product must have at least one recurring plan if the payment type is recurring', - path: ['paymentType'], - }, - ) - .refine( - (schema) => { - return !(schema.paymentType === 'one-time' && schema.plans.length > 1); - }, - { - message: 'One-time products can only have one plan', + message: 'Plan IDs must be unique', path: ['plans'], }, ); -export const BillingSchema = z +const BillingSchema = z .object({ + provider: BillingProviderSchema, products: z.array(ProductSchema).nonempty(), - provider: BillingProvider, }) .refine( - (schema) => { - const ids = schema.products.map((product) => product.id); + (data) => { + const ids = data.products.flatMap((product) => + product.plans.flatMap((plan) => plan.lineItems.map((item) => item.id)), + ); - return new Set(ids).size === ids.length; + return ids.length === new Set(ids).size; }, { - message: 'Duplicate product IDs', - path: ['products'], - }, - ) - .refine( - (schema) => { - const planIds = getAllPlanIds(schema); - - return new Set(planIds).size === planIds.length; - }, - { - message: 'Duplicate plan IDs', + message: 'Line item IDs must be unique', path: ['products'], }, ); -/** - * Create and validate the billing schema - * @param config The billing configuration - */ export function createBillingSchema(config: z.infer) { - console.log(JSON.stringify(config)); return BillingSchema.parse(config); } -/** - * Retrieves the intervals of all plans specified in the given configuration. - * @param config The billing configuration containing products and plans. - */ +export type BillingConfig = z.infer; +export type ProductSchema = z.infer; + export function getPlanIntervals(config: z.infer) { - return Array.from( - new Set( - config.products.flatMap((product) => { - const isRecurring = product.paymentType === 'recurring'; + const intervals = config.products.flatMap((product) => + product.plans.map((plan) => plan.interval), + ); - if (isRecurring) { - const plans = product.plans as z.infer[]; - - return plans.map((plan) => plan.recurring.interval); - } - - return []; - }), - ), - ).filter(Boolean); + return Array.from(new Set(intervals)); } -export function getProductPlanPairFromId( +export function getBaseLineItem( + config: z.infer, + planId: string, +) { + for (const product of config.products) { + for (const plan of product.plans) { + if (plan.id === planId) { + const item = plan.lineItems.find((item) => item.type === 'base'); + + if (item) { + return item; + } + } + } + } + + throw new Error('Base line item not found'); +} + +export function getProductPlanPair( config: z.infer, planId: string, ) { @@ -237,21 +164,3 @@ export function getProductPlanPairFromId( throw new Error('Plan not found'); } - -export function getAllPlanIds(config: z.infer) { - const ids: string[] = []; - - for (const product of config.products) { - for (const plan of product.plans) { - ids.push(plan.id); - } - } - - return ids; -} - -export function isRecurringPlan( - plan: z.infer, -): plan is z.infer { - return 'recurring' in plan; -} diff --git a/packages/billing/src/index.ts b/packages/billing/src/index.ts index 00a54004d..0c675acff 100644 --- a/packages/billing/src/index.ts +++ b/packages/billing/src/index.ts @@ -1,4 +1,3 @@ export * from './create-billing-schema'; export * from './services/billing-strategy-provider.service'; export * from './services/billing-webhook-handler.service'; -export * from './line-items-mapper'; diff --git a/packages/billing/src/line-items-mapper.ts b/packages/billing/src/line-items-mapper.ts deleted file mode 100644 index 27a923c6e..000000000 --- a/packages/billing/src/line-items-mapper.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { z } from 'zod'; - -import { ProductSchema, isRecurringPlan } from './create-billing-schema'; - -export function getLineItemsFromPlanId( - product: z.infer, - planId: string, -) { - const plan = product.plans.find((plan) => plan.id === planId); - - if (!plan) { - throw new Error('Plan not found'); - } - - const lineItems = []; - - let trialDays = undefined; - - if (isRecurringPlan(plan)) { - const lineItem: { - id: string; - quantity: number; - usageType?: 'metered' | 'licensed'; - } = { - id: plan.id, - quantity: 1, - }; - - trialDays = plan.trialDays; - - if (plan.recurring.usageType) { - lineItem.usageType = plan.recurring.usageType; - } - - lineItems.push(lineItem); - - if (plan.recurring.addOns) { - for (const addOn of plan.recurring.addOns) { - lineItems.push({ - id: addOn.id, - quantity: 1, - }); - } - } - } - - return { - lineItems, - trialDays, - }; -} diff --git a/packages/billing/src/schema/create-billing-checkout.schema.ts b/packages/billing/src/schema/create-billing-checkout.schema.ts index 22a85bdc5..3e18cbf34 100644 --- a/packages/billing/src/schema/create-billing-checkout.schema.ts +++ b/packages/billing/src/schema/create-billing-checkout.schema.ts @@ -1,31 +1,12 @@ import { z } from 'zod'; -import { LineItemUsageType, PaymentType } from '../create-billing-schema'; +import { PlanSchema } from '../create-billing-schema'; -export const CreateBillingCheckoutSchema = z - .object({ - returnUrl: z.string().url(), - accountId: z.string().uuid(), - paymentType: PaymentType, - lineItems: z.array( - z.object({ - id: z.string(), - quantity: z.number().int().positive(), - usageType: LineItemUsageType.optional(), - }), - ), - trialDays: z.number().optional(), - customerId: z.string().optional(), - customerEmail: z.string().email().optional(), - }) - .refine( - (schema) => { - if (schema.paymentType === 'one-time' && schema.trialDays) { - return false; - } - }, - { - message: 'Trial days are only allowed for recurring payments', - path: ['trialDays'], - }, - ); +export const CreateBillingCheckoutSchema = z.object({ + returnUrl: z.string().url(), + accountId: z.string().uuid(), + plan: PlanSchema, + trialDays: z.number().optional(), + customerId: z.string().optional(), + customerEmail: z.string().email().optional(), +}); diff --git a/packages/billing/src/services/billing-strategy-provider.service.ts b/packages/billing/src/services/billing-strategy-provider.service.ts index fd2513178..4e02b4411 100644 --- a/packages/billing/src/services/billing-strategy-provider.service.ts +++ b/packages/billing/src/services/billing-strategy-provider.service.ts @@ -6,7 +6,7 @@ import { CreateBillingPortalSessionSchema, RetrieveCheckoutSessionSchema, } from '../schema'; -import { ReportBillingUsageSchema } from '../schema/report-billing-usage.schema'; +import { ReportBillingUsageSchema } from '../schema'; export abstract class BillingStrategyProviderService { abstract createBillingPortalSession( diff --git a/packages/stripe/src/services/create-stripe-checkout.ts b/packages/stripe/src/services/create-stripe-checkout.ts index 10028bbd5..a25ec2b9b 100644 --- a/packages/stripe/src/services/create-stripe-checkout.ts +++ b/packages/stripe/src/services/create-stripe-checkout.ts @@ -24,7 +24,7 @@ export async function createStripeCheckout( // docs: https://stripe.com/docs/billing/subscriptions/build-subscription const mode: Stripe.Checkout.SessionCreateParams.Mode = - params.paymentType === 'recurring' ? 'subscription' : 'payment'; + params.plan.paymentType === 'recurring' ? 'subscription' : 'payment'; // this should only be set if the mode is 'subscription' const subscriptionData: @@ -54,8 +54,8 @@ export async function createStripeCheckout( customer_email: params.customerEmail, }; - const lineItems = params.lineItems.map((item) => { - if (item.usageType === 'metered') { + const lineItems = params.plan.lineItems.map((item) => { + if (item.type === 'metered') { return { price: item.id, }; @@ -63,7 +63,7 @@ export async function createStripeCheckout( return { price: item.id, - quantity: item.quantity, + quantity: 1, }; }); diff --git a/packages/supabase/src/database.types.ts b/packages/supabase/src/database.types.ts index 0730ceddf..4ffac81e7 100644 --- a/packages/supabase/src/database.types.ts +++ b/packages/supabase/src/database.types.ts @@ -4,1070 +4,1069 @@ export type Json = | boolean | null | { [key: string]: Json | undefined } - | Json[] + | Json[]; export type Database = { graphql_public: { Tables: { - [_ in never]: never - } + [_ in never]: never; + }; Views: { - [_ in never]: never - } + [_ in never]: never; + }; Functions: { graphql: { Args: { - operationName?: string - query?: string - variables?: Json - extensions?: Json - } - Returns: Json - } - } + operationName?: string; + query?: string; + variables?: Json; + extensions?: Json; + }; + Returns: Json; + }; + }; Enums: { - [_ in never]: never - } + [_ in never]: never; + }; CompositeTypes: { - [_ in never]: never - } - } + [_ in never]: never; + }; + }; public: { Tables: { account_roles: { Row: { - account_id: string - id: number - role: string - } + account_id: string; + id: number; + role: string; + }; Insert: { - account_id: string - id?: number - role: string - } + account_id: string; + id?: number; + role: string; + }; Update: { - account_id?: string - id?: number - role?: string - } + account_id?: string; + id?: number; + role?: string; + }; Relationships: [ { - foreignKeyName: "account_roles_account_id_fkey" - columns: ["account_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: 'account_roles_account_id_fkey'; + columns: ['account_id']; + isOneToOne: false; + referencedRelation: 'accounts'; + referencedColumns: ['id']; }, { - foreignKeyName: "account_roles_account_id_fkey" - columns: ["account_id"] - isOneToOne: false - referencedRelation: "user_account_workspace" - referencedColumns: ["id"] + foreignKeyName: 'account_roles_account_id_fkey'; + columns: ['account_id']; + isOneToOne: false; + referencedRelation: 'user_account_workspace'; + referencedColumns: ['id']; }, { - foreignKeyName: "account_roles_account_id_fkey" - columns: ["account_id"] - isOneToOne: false - referencedRelation: "user_accounts" - referencedColumns: ["id"] + foreignKeyName: 'account_roles_account_id_fkey'; + columns: ['account_id']; + isOneToOne: false; + referencedRelation: 'user_accounts'; + referencedColumns: ['id']; }, { - foreignKeyName: "account_roles_role_fkey" - columns: ["role"] - isOneToOne: false - referencedRelation: "roles" - referencedColumns: ["name"] + foreignKeyName: 'account_roles_role_fkey'; + columns: ['role']; + isOneToOne: false; + referencedRelation: 'roles'; + referencedColumns: ['name']; }, - ] - } + ]; + }; accounts: { Row: { - created_at: string | null - created_by: string | null - email: string | null - id: string - is_personal_account: boolean - name: string - picture_url: string | null - primary_owner_user_id: string - slug: string | null - updated_at: string | null - updated_by: string | null - } + created_at: string | null; + created_by: string | null; + email: string | null; + id: string; + is_personal_account: boolean; + name: string; + picture_url: string | null; + primary_owner_user_id: string; + slug: string | null; + updated_at: string | null; + updated_by: string | null; + }; Insert: { - created_at?: string | null - created_by?: string | null - email?: string | null - id?: string - is_personal_account?: boolean - name: string - picture_url?: string | null - primary_owner_user_id?: string - slug?: string | null - updated_at?: string | null - updated_by?: string | null - } + created_at?: string | null; + created_by?: string | null; + email?: string | null; + id?: string; + is_personal_account?: boolean; + name: string; + picture_url?: string | null; + primary_owner_user_id?: string; + slug?: string | null; + updated_at?: string | null; + updated_by?: string | null; + }; Update: { - created_at?: string | null - created_by?: string | null - email?: string | null - id?: string - is_personal_account?: boolean - name?: string - picture_url?: string | null - primary_owner_user_id?: string - slug?: string | null - updated_at?: string | null - updated_by?: string | null - } + created_at?: string | null; + created_by?: string | null; + email?: string | null; + id?: string; + is_personal_account?: boolean; + name?: string; + picture_url?: string | null; + primary_owner_user_id?: string; + slug?: string | null; + updated_at?: string | null; + updated_by?: string | null; + }; Relationships: [ { - foreignKeyName: "accounts_created_by_fkey" - columns: ["created_by"] - isOneToOne: false - referencedRelation: "users" - referencedColumns: ["id"] + foreignKeyName: 'accounts_created_by_fkey'; + columns: ['created_by']; + isOneToOne: false; + referencedRelation: 'users'; + referencedColumns: ['id']; }, { - foreignKeyName: "accounts_primary_owner_user_id_fkey" - columns: ["primary_owner_user_id"] - isOneToOne: false - referencedRelation: "users" - referencedColumns: ["id"] + foreignKeyName: 'accounts_primary_owner_user_id_fkey'; + columns: ['primary_owner_user_id']; + isOneToOne: false; + referencedRelation: 'users'; + referencedColumns: ['id']; }, { - foreignKeyName: "accounts_updated_by_fkey" - columns: ["updated_by"] - isOneToOne: false - referencedRelation: "users" - referencedColumns: ["id"] + foreignKeyName: 'accounts_updated_by_fkey'; + columns: ['updated_by']; + isOneToOne: false; + referencedRelation: 'users'; + referencedColumns: ['id']; }, - ] - } + ]; + }; accounts_memberships: { Row: { - account_id: string - account_role: string - created_at: string - created_by: string | null - updated_at: string - updated_by: string | null - user_id: string - } + account_id: string; + account_role: string; + created_at: string; + created_by: string | null; + updated_at: string; + updated_by: string | null; + user_id: string; + }; Insert: { - account_id: string - account_role: string - created_at?: string - created_by?: string | null - updated_at?: string - updated_by?: string | null - user_id: string - } + account_id: string; + account_role: string; + created_at?: string; + created_by?: string | null; + updated_at?: string; + updated_by?: string | null; + user_id: string; + }; Update: { - account_id?: string - account_role?: string - created_at?: string - created_by?: string | null - updated_at?: string - updated_by?: string | null - user_id?: string - } + account_id?: string; + account_role?: string; + created_at?: string; + created_by?: string | null; + updated_at?: string; + updated_by?: string | null; + user_id?: string; + }; Relationships: [ { - foreignKeyName: "accounts_memberships_account_id_fkey" - columns: ["account_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: 'accounts_memberships_account_id_fkey'; + columns: ['account_id']; + isOneToOne: false; + referencedRelation: 'accounts'; + referencedColumns: ['id']; }, { - foreignKeyName: "accounts_memberships_account_id_fkey" - columns: ["account_id"] - isOneToOne: false - referencedRelation: "user_account_workspace" - referencedColumns: ["id"] + foreignKeyName: 'accounts_memberships_account_id_fkey'; + columns: ['account_id']; + isOneToOne: false; + referencedRelation: 'user_account_workspace'; + referencedColumns: ['id']; }, { - foreignKeyName: "accounts_memberships_account_id_fkey" - columns: ["account_id"] - isOneToOne: false - referencedRelation: "user_accounts" - referencedColumns: ["id"] + foreignKeyName: 'accounts_memberships_account_id_fkey'; + columns: ['account_id']; + isOneToOne: false; + referencedRelation: 'user_accounts'; + referencedColumns: ['id']; }, { - foreignKeyName: "accounts_memberships_account_role_fkey" - columns: ["account_role"] - isOneToOne: false - referencedRelation: "roles" - referencedColumns: ["name"] + foreignKeyName: 'accounts_memberships_account_role_fkey'; + columns: ['account_role']; + isOneToOne: false; + referencedRelation: 'roles'; + referencedColumns: ['name']; }, { - foreignKeyName: "accounts_memberships_created_by_fkey" - columns: ["created_by"] - isOneToOne: false - referencedRelation: "users" - referencedColumns: ["id"] + foreignKeyName: 'accounts_memberships_created_by_fkey'; + columns: ['created_by']; + isOneToOne: false; + referencedRelation: 'users'; + referencedColumns: ['id']; }, { - foreignKeyName: "accounts_memberships_updated_by_fkey" - columns: ["updated_by"] - isOneToOne: false - referencedRelation: "users" - referencedColumns: ["id"] + foreignKeyName: 'accounts_memberships_updated_by_fkey'; + columns: ['updated_by']; + isOneToOne: false; + referencedRelation: 'users'; + referencedColumns: ['id']; }, { - foreignKeyName: "accounts_memberships_user_id_fkey" - columns: ["user_id"] - isOneToOne: false - referencedRelation: "users" - referencedColumns: ["id"] + foreignKeyName: 'accounts_memberships_user_id_fkey'; + columns: ['user_id']; + isOneToOne: false; + referencedRelation: 'users'; + referencedColumns: ['id']; }, - ] - } + ]; + }; billing_customers: { Row: { - account_id: string - customer_id: string - email: string | null - id: number - provider: Database["public"]["Enums"]["billing_provider"] - } + account_id: string; + customer_id: string; + email: string | null; + id: number; + provider: Database['public']['Enums']['billing_provider']; + }; Insert: { - account_id: string - customer_id: string - email?: string | null - id?: number - provider: Database["public"]["Enums"]["billing_provider"] - } + account_id: string; + customer_id: string; + email?: string | null; + id?: number; + provider: Database['public']['Enums']['billing_provider']; + }; Update: { - account_id?: string - customer_id?: string - email?: string | null - id?: number - provider?: Database["public"]["Enums"]["billing_provider"] - } + account_id?: string; + customer_id?: string; + email?: string | null; + id?: number; + provider?: Database['public']['Enums']['billing_provider']; + }; Relationships: [ { - foreignKeyName: "billing_customers_account_id_fkey" - columns: ["account_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: 'billing_customers_account_id_fkey'; + columns: ['account_id']; + isOneToOne: false; + referencedRelation: 'accounts'; + referencedColumns: ['id']; }, { - foreignKeyName: "billing_customers_account_id_fkey" - columns: ["account_id"] - isOneToOne: false - referencedRelation: "user_account_workspace" - referencedColumns: ["id"] + foreignKeyName: 'billing_customers_account_id_fkey'; + columns: ['account_id']; + isOneToOne: false; + referencedRelation: 'user_account_workspace'; + referencedColumns: ['id']; }, { - foreignKeyName: "billing_customers_account_id_fkey" - columns: ["account_id"] - isOneToOne: false - referencedRelation: "user_accounts" - referencedColumns: ["id"] + foreignKeyName: 'billing_customers_account_id_fkey'; + columns: ['account_id']; + isOneToOne: false; + referencedRelation: 'user_accounts'; + referencedColumns: ['id']; }, - ] - } + ]; + }; config: { Row: { - billing_provider: Database["public"]["Enums"]["billing_provider"] - enable_account_billing: boolean - enable_organization_accounts: boolean - enable_organization_billing: boolean - } + billing_provider: Database['public']['Enums']['billing_provider']; + enable_account_billing: boolean; + enable_organization_accounts: boolean; + enable_organization_billing: boolean; + }; Insert: { - billing_provider?: Database["public"]["Enums"]["billing_provider"] - enable_account_billing?: boolean - enable_organization_accounts?: boolean - enable_organization_billing?: boolean - } + billing_provider?: Database['public']['Enums']['billing_provider']; + enable_account_billing?: boolean; + enable_organization_accounts?: boolean; + enable_organization_billing?: boolean; + }; Update: { - billing_provider?: Database["public"]["Enums"]["billing_provider"] - enable_account_billing?: boolean - enable_organization_accounts?: boolean - enable_organization_billing?: boolean - } - Relationships: [] - } + billing_provider?: Database['public']['Enums']['billing_provider']; + enable_account_billing?: boolean; + enable_organization_accounts?: boolean; + enable_organization_billing?: boolean; + }; + Relationships: []; + }; invitations: { Row: { - account_id: string - created_at: string - email: string - expires_at: string - id: number - invite_token: string - invited_by: string - role: string - updated_at: string - } + account_id: string; + created_at: string; + email: string; + expires_at: string; + id: number; + invite_token: string; + invited_by: string; + role: string; + updated_at: string; + }; Insert: { - account_id: string - created_at?: string - email: string - expires_at?: string - id?: number - invite_token: string - invited_by: string - role: string - updated_at?: string - } + account_id: string; + created_at?: string; + email: string; + expires_at?: string; + id?: number; + invite_token: string; + invited_by: string; + role: string; + updated_at?: string; + }; Update: { - account_id?: string - created_at?: string - email?: string - expires_at?: string - id?: number - invite_token?: string - invited_by?: string - role?: string - updated_at?: string - } + account_id?: string; + created_at?: string; + email?: string; + expires_at?: string; + id?: number; + invite_token?: string; + invited_by?: string; + role?: string; + updated_at?: string; + }; Relationships: [ { - foreignKeyName: "invitations_account_id_fkey" - columns: ["account_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: 'invitations_account_id_fkey'; + columns: ['account_id']; + isOneToOne: false; + referencedRelation: 'accounts'; + referencedColumns: ['id']; }, { - foreignKeyName: "invitations_account_id_fkey" - columns: ["account_id"] - isOneToOne: false - referencedRelation: "user_account_workspace" - referencedColumns: ["id"] + foreignKeyName: 'invitations_account_id_fkey'; + columns: ['account_id']; + isOneToOne: false; + referencedRelation: 'user_account_workspace'; + referencedColumns: ['id']; }, { - foreignKeyName: "invitations_account_id_fkey" - columns: ["account_id"] - isOneToOne: false - referencedRelation: "user_accounts" - referencedColumns: ["id"] + foreignKeyName: 'invitations_account_id_fkey'; + columns: ['account_id']; + isOneToOne: false; + referencedRelation: 'user_accounts'; + referencedColumns: ['id']; }, { - foreignKeyName: "invitations_invited_by_fkey" - columns: ["invited_by"] - isOneToOne: false - referencedRelation: "users" - referencedColumns: ["id"] + foreignKeyName: 'invitations_invited_by_fkey'; + columns: ['invited_by']; + isOneToOne: false; + referencedRelation: 'users'; + referencedColumns: ['id']; }, { - foreignKeyName: "invitations_role_fkey" - columns: ["role"] - isOneToOne: false - referencedRelation: "roles" - referencedColumns: ["name"] + foreignKeyName: 'invitations_role_fkey'; + columns: ['role']; + isOneToOne: false; + referencedRelation: 'roles'; + referencedColumns: ['name']; }, - ] - } + ]; + }; role_permissions: { Row: { - id: number - permission: Database["public"]["Enums"]["app_permissions"] - role: string - } + id: number; + permission: Database['public']['Enums']['app_permissions']; + role: string; + }; Insert: { - id?: number - permission: Database["public"]["Enums"]["app_permissions"] - role: string - } + id?: number; + permission: Database['public']['Enums']['app_permissions']; + role: string; + }; Update: { - id?: number - permission?: Database["public"]["Enums"]["app_permissions"] - role?: string - } + id?: number; + permission?: Database['public']['Enums']['app_permissions']; + role?: string; + }; Relationships: [ { - foreignKeyName: "role_permissions_role_fkey" - columns: ["role"] - isOneToOne: false - referencedRelation: "roles" - referencedColumns: ["name"] + foreignKeyName: 'role_permissions_role_fkey'; + columns: ['role']; + isOneToOne: false; + referencedRelation: 'roles'; + referencedColumns: ['name']; }, - ] - } + ]; + }; roles: { Row: { - account_id: string | null - hierarchy_level: number - is_custom: boolean - name: string - } + account_id: string | null; + hierarchy_level: number; + is_custom: boolean; + name: string; + }; Insert: { - account_id?: string | null - hierarchy_level: number - is_custom?: boolean - name: string - } + account_id?: string | null; + hierarchy_level: number; + is_custom?: boolean; + name: string; + }; Update: { - account_id?: string | null - hierarchy_level?: number - is_custom?: boolean - name?: string - } + account_id?: string | null; + hierarchy_level?: number; + is_custom?: boolean; + name?: string; + }; Relationships: [ { - foreignKeyName: "roles_account_id_fkey" - columns: ["account_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: 'roles_account_id_fkey'; + columns: ['account_id']; + isOneToOne: false; + referencedRelation: 'accounts'; + referencedColumns: ['id']; }, { - foreignKeyName: "roles_account_id_fkey" - columns: ["account_id"] - isOneToOne: false - referencedRelation: "user_account_workspace" - referencedColumns: ["id"] + foreignKeyName: 'roles_account_id_fkey'; + columns: ['account_id']; + isOneToOne: false; + referencedRelation: 'user_account_workspace'; + referencedColumns: ['id']; }, { - foreignKeyName: "roles_account_id_fkey" - columns: ["account_id"] - isOneToOne: false - referencedRelation: "user_accounts" - referencedColumns: ["id"] + foreignKeyName: 'roles_account_id_fkey'; + columns: ['account_id']; + isOneToOne: false; + referencedRelation: 'user_accounts'; + referencedColumns: ['id']; }, - ] - } + ]; + }; subscriptions: { Row: { - account_id: string - active: boolean - billing_customer_id: number - billing_provider: Database["public"]["Enums"]["billing_provider"] - cancel_at_period_end: boolean - created_at: string - currency: string - id: string - interval: string - interval_count: number - period_ends_at: string | null - period_starts_at: string | null - price_amount: number | null - product_id: string - status: Database["public"]["Enums"]["subscription_status"] - trial_ends_at: string | null - trial_starts_at: string | null - updated_at: string - variant_id: string - } + account_id: string; + active: boolean; + billing_customer_id: number; + billing_provider: Database['public']['Enums']['billing_provider']; + cancel_at_period_end: boolean; + created_at: string; + currency: string; + id: string; + interval: string; + interval_count: number; + period_ends_at: string | null; + period_starts_at: string | null; + price_amount: number | null; + product_id: string; + status: Database['public']['Enums']['subscription_status']; + trial_ends_at: string | null; + trial_starts_at: string | null; + updated_at: string; + variant_id: string; + }; Insert: { - account_id: string - active: boolean - billing_customer_id: number - billing_provider: Database["public"]["Enums"]["billing_provider"] - cancel_at_period_end: boolean - created_at?: string - currency: string - id: string - interval: string - interval_count: number - period_ends_at?: string | null - period_starts_at?: string | null - price_amount?: number | null - product_id: string - status: Database["public"]["Enums"]["subscription_status"] - trial_ends_at?: string | null - trial_starts_at?: string | null - updated_at?: string - variant_id: string - } + account_id: string; + active: boolean; + billing_customer_id: number; + billing_provider: Database['public']['Enums']['billing_provider']; + cancel_at_period_end: boolean; + created_at?: string; + currency: string; + id: string; + interval: string; + interval_count: number; + period_ends_at?: string | null; + period_starts_at?: string | null; + price_amount?: number | null; + product_id: string; + status: Database['public']['Enums']['subscription_status']; + trial_ends_at?: string | null; + trial_starts_at?: string | null; + updated_at?: string; + variant_id: string; + }; Update: { - account_id?: string - active?: boolean - billing_customer_id?: number - billing_provider?: Database["public"]["Enums"]["billing_provider"] - cancel_at_period_end?: boolean - created_at?: string - currency?: string - id?: string - interval?: string - interval_count?: number - period_ends_at?: string | null - period_starts_at?: string | null - price_amount?: number | null - product_id?: string - status?: Database["public"]["Enums"]["subscription_status"] - trial_ends_at?: string | null - trial_starts_at?: string | null - updated_at?: string - variant_id?: string - } + account_id?: string; + active?: boolean; + billing_customer_id?: number; + billing_provider?: Database['public']['Enums']['billing_provider']; + cancel_at_period_end?: boolean; + created_at?: string; + currency?: string; + id?: string; + interval?: string; + interval_count?: number; + period_ends_at?: string | null; + period_starts_at?: string | null; + price_amount?: number | null; + product_id?: string; + status?: Database['public']['Enums']['subscription_status']; + trial_ends_at?: string | null; + trial_starts_at?: string | null; + updated_at?: string; + variant_id?: string; + }; Relationships: [ { - foreignKeyName: "subscriptions_account_id_fkey" - columns: ["account_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: 'subscriptions_account_id_fkey'; + columns: ['account_id']; + isOneToOne: false; + referencedRelation: 'accounts'; + referencedColumns: ['id']; }, { - foreignKeyName: "subscriptions_account_id_fkey" - columns: ["account_id"] - isOneToOne: false - referencedRelation: "user_account_workspace" - referencedColumns: ["id"] + foreignKeyName: 'subscriptions_account_id_fkey'; + columns: ['account_id']; + isOneToOne: false; + referencedRelation: 'user_account_workspace'; + referencedColumns: ['id']; }, { - foreignKeyName: "subscriptions_account_id_fkey" - columns: ["account_id"] - isOneToOne: false - referencedRelation: "user_accounts" - referencedColumns: ["id"] + foreignKeyName: 'subscriptions_account_id_fkey'; + columns: ['account_id']; + isOneToOne: false; + referencedRelation: 'user_accounts'; + referencedColumns: ['id']; }, { - foreignKeyName: "subscriptions_billing_customer_id_fkey" - columns: ["billing_customer_id"] - isOneToOne: false - referencedRelation: "billing_customers" - referencedColumns: ["id"] + foreignKeyName: 'subscriptions_billing_customer_id_fkey'; + columns: ['billing_customer_id']; + isOneToOne: false; + referencedRelation: 'billing_customers'; + referencedColumns: ['id']; }, - ] - } - } + ]; + }; + }; Views: { user_account_workspace: { Row: { - id: string | null - name: string | null - picture_url: string | null + id: string | null; + name: string | null; + picture_url: string | null; subscription_status: - | Database["public"]["Enums"]["subscription_status"] - | null - } - Relationships: [] - } + | Database['public']['Enums']['subscription_status'] + | null; + }; + Relationships: []; + }; user_accounts: { Row: { - id: string | null - name: string | null - picture_url: string | null - role: string | null - slug: string | null - } + id: string | null; + name: string | null; + picture_url: string | null; + role: string | null; + slug: string | null; + }; Relationships: [ { - foreignKeyName: "accounts_memberships_account_role_fkey" - columns: ["role"] - isOneToOne: false - referencedRelation: "roles" - referencedColumns: ["name"] + foreignKeyName: 'accounts_memberships_account_role_fkey'; + columns: ['role']; + isOneToOne: false; + referencedRelation: 'roles'; + referencedColumns: ['name']; }, - ] - } - } + ]; + }; + }; Functions: { accept_invitation: { Args: { - token: string - user_id: string - } - Returns: undefined - } + token: string; + user_id: string; + }; + Returns: undefined; + }; add_invitations_to_account: { Args: { - account_slug: string - invitations: unknown[] - } - Returns: Database["public"]["Tables"]["invitations"]["Row"][] - } + account_slug: string; + invitations: unknown[]; + }; + Returns: Database['public']['Tables']['invitations']['Row'][]; + }; add_subscription: { Args: { - account_id: string - subscription_id: string - active: boolean - status: Database["public"]["Enums"]["subscription_status"] - billing_provider: Database["public"]["Enums"]["billing_provider"] - product_id: string - variant_id: string - price_amount: number - cancel_at_period_end: boolean - currency: string - interval: string - interval_count: number - period_starts_at: string - period_ends_at: string - trial_starts_at: string - trial_ends_at: string - customer_id: string - } + account_id: string; + subscription_id: string; + active: boolean; + status: Database['public']['Enums']['subscription_status']; + billing_provider: Database['public']['Enums']['billing_provider']; + product_id: string; + variant_id: string; + price_amount: number; + cancel_at_period_end: boolean; + currency: string; + interval: string; + interval_count: number; + period_starts_at: string; + period_ends_at: string; + trial_starts_at: string; + trial_ends_at: string; + customer_id: string; + }; Returns: { - account_id: string - active: boolean - billing_customer_id: number - billing_provider: Database["public"]["Enums"]["billing_provider"] - cancel_at_period_end: boolean - created_at: string - currency: string - id: string - interval: string - interval_count: number - period_ends_at: string | null - period_starts_at: string | null - price_amount: number | null - product_id: string - status: Database["public"]["Enums"]["subscription_status"] - trial_ends_at: string | null - trial_starts_at: string | null - updated_at: string - variant_id: string - } - } + account_id: string; + active: boolean; + billing_customer_id: number; + billing_provider: Database['public']['Enums']['billing_provider']; + cancel_at_period_end: boolean; + created_at: string; + currency: string; + id: string; + interval: string; + interval_count: number; + period_ends_at: string | null; + period_starts_at: string | null; + price_amount: number | null; + product_id: string; + status: Database['public']['Enums']['subscription_status']; + trial_ends_at: string | null; + trial_starts_at: string | null; + updated_at: string; + variant_id: string; + }; + }; create_account: { Args: { - account_name: string - } + account_name: string; + }; Returns: { - created_at: string | null - created_by: string | null - email: string | null - id: string - is_personal_account: boolean - name: string - picture_url: string | null - primary_owner_user_id: string - slug: string | null - updated_at: string | null - updated_by: string | null - } - } + created_at: string | null; + created_by: string | null; + email: string | null; + id: string; + is_personal_account: boolean; + name: string; + picture_url: string | null; + primary_owner_user_id: string; + slug: string | null; + updated_at: string | null; + updated_by: string | null; + }; + }; create_invitation: { Args: { - account_id: string - email: string - role: string - } + account_id: string; + email: string; + role: string; + }; Returns: { - account_id: string - created_at: string - email: string - expires_at: string - id: number - invite_token: string - invited_by: string - role: string - updated_at: string - } - } + account_id: string; + created_at: string; + email: string; + expires_at: string; + id: number; + invite_token: string; + invited_by: string; + role: string; + updated_at: string; + }; + }; get_account_invitations: { Args: { - account_slug: string - } + account_slug: string; + }; Returns: { - id: number - email: string - account_id: string - invited_by: string - role: string - created_at: string - updated_at: string - expires_at: string - inviter_name: string - inviter_email: string - }[] - } + id: number; + email: string; + account_id: string; + invited_by: string; + role: string; + created_at: string; + updated_at: string; + expires_at: string; + inviter_name: string; + inviter_email: string; + }[]; + }; get_account_members: { Args: { - account_slug: string - } + account_slug: string; + }; Returns: { - id: string - user_id: string - account_id: string - role: string - role_hierarchy_level: number - primary_owner_user_id: string - name: string - email: string - picture_url: string - created_at: string - updated_at: string - }[] - } + id: string; + user_id: string; + account_id: string; + role: string; + role_hierarchy_level: number; + primary_owner_user_id: string; + name: string; + email: string; + picture_url: string; + created_at: string; + updated_at: string; + }[]; + }; get_config: { - Args: Record - Returns: Json - } + Args: Record; + Returns: Json; + }; get_user_accounts: { - Args: Record + Args: Record; Returns: { - created_at: string | null - created_by: string | null - email: string | null - id: string - is_personal_account: boolean - name: string - picture_url: string | null - primary_owner_user_id: string - slug: string | null - updated_at: string | null - updated_by: string | null - }[] - } + created_at: string | null; + created_by: string | null; + email: string | null; + id: string; + is_personal_account: boolean; + name: string; + picture_url: string | null; + primary_owner_user_id: string; + slug: string | null; + updated_at: string | null; + updated_by: string | null; + }[]; + }; has_more_elevated_role: { Args: { - target_user_id: string - target_account_id: string - role_name: string - } - Returns: boolean - } + target_user_id: string; + target_account_id: string; + role_name: string; + }; + Returns: boolean; + }; has_permission: { Args: { - user_id: string - account_id: string - permission_name: Database["public"]["Enums"]["app_permissions"] - } - Returns: boolean - } + user_id: string; + account_id: string; + permission_name: Database['public']['Enums']['app_permissions']; + }; + Returns: boolean; + }; has_role_on_account: { Args: { - account_id: string - account_role?: string - } - Returns: boolean - } + account_id: string; + account_role?: string; + }; + Returns: boolean; + }; is_account_owner: { Args: { - account_id: string - } - Returns: boolean - } + account_id: string; + }; + Returns: boolean; + }; is_set: { Args: { - field_name: string - } - Returns: boolean - } + field_name: string; + }; + Returns: boolean; + }; is_team_member: { Args: { - account_id: string - user_id: string - } - Returns: boolean - } + account_id: string; + user_id: string; + }; + Returns: boolean; + }; organization_account_workspace: { Args: { - account_slug: string - } + account_slug: string; + }; Returns: { - id: string - name: string - picture_url: string - slug: string - role: string - role_hierarchy_level: number - primary_owner_user_id: string - subscription_status: Database["public"]["Enums"]["subscription_status"] - permissions: Database["public"]["Enums"]["app_permissions"][] - }[] - } + id: string; + name: string; + picture_url: string; + slug: string; + role: string; + role_hierarchy_level: number; + primary_owner_user_id: string; + subscription_status: Database['public']['Enums']['subscription_status']; + permissions: Database['public']['Enums']['app_permissions'][]; + }[]; + }; transfer_team_account_ownership: { Args: { - target_account_id: string - new_owner_id: string - } - Returns: undefined - } + target_account_id: string; + new_owner_id: string; + }; + Returns: undefined; + }; unaccent: { Args: { - "": string - } - Returns: string - } + '': string; + }; + Returns: string; + }; unaccent_init: { Args: { - "": unknown - } - Returns: unknown - } - } + '': unknown; + }; + Returns: unknown; + }; + }; Enums: { app_permissions: - | "roles.manage" - | "billing.manage" - | "settings.manage" - | "members.manage" - | "invites.manage" - billing_provider: "stripe" | "lemon-squeezy" | "paddle" + | 'roles.manage' + | 'billing.manage' + | 'settings.manage' + | 'members.manage' + | 'invites.manage'; + billing_provider: 'stripe' | 'lemon-squeezy' | 'paddle'; subscription_status: - | "active" - | "trialing" - | "past_due" - | "canceled" - | "unpaid" - | "incomplete" - | "incomplete_expired" - | "paused" - } + | 'active' + | 'trialing' + | 'past_due' + | 'canceled' + | 'unpaid' + | 'incomplete' + | 'incomplete_expired' + | 'paused'; + }; CompositeTypes: { - [_ in never]: never - } - } + [_ in never]: never; + }; + }; storage: { Tables: { buckets: { Row: { - allowed_mime_types: string[] | null - avif_autodetection: boolean | null - created_at: string | null - file_size_limit: number | null - id: string - name: string - owner: string | null - owner_id: string | null - public: boolean | null - updated_at: string | null - } + allowed_mime_types: string[] | null; + avif_autodetection: boolean | null; + created_at: string | null; + file_size_limit: number | null; + id: string; + name: string; + owner: string | null; + owner_id: string | null; + public: boolean | null; + updated_at: string | null; + }; Insert: { - allowed_mime_types?: string[] | null - avif_autodetection?: boolean | null - created_at?: string | null - file_size_limit?: number | null - id: string - name: string - owner?: string | null - owner_id?: string | null - public?: boolean | null - updated_at?: string | null - } + allowed_mime_types?: string[] | null; + avif_autodetection?: boolean | null; + created_at?: string | null; + file_size_limit?: number | null; + id: string; + name: string; + owner?: string | null; + owner_id?: string | null; + public?: boolean | null; + updated_at?: string | null; + }; Update: { - allowed_mime_types?: string[] | null - avif_autodetection?: boolean | null - created_at?: string | null - file_size_limit?: number | null - id?: string - name?: string - owner?: string | null - owner_id?: string | null - public?: boolean | null - updated_at?: string | null - } - Relationships: [] - } + allowed_mime_types?: string[] | null; + avif_autodetection?: boolean | null; + created_at?: string | null; + file_size_limit?: number | null; + id?: string; + name?: string; + owner?: string | null; + owner_id?: string | null; + public?: boolean | null; + updated_at?: string | null; + }; + Relationships: []; + }; migrations: { Row: { - executed_at: string | null - hash: string - id: number - name: string - } + executed_at: string | null; + hash: string; + id: number; + name: string; + }; Insert: { - executed_at?: string | null - hash: string - id: number - name: string - } + executed_at?: string | null; + hash: string; + id: number; + name: string; + }; Update: { - executed_at?: string | null - hash?: string - id?: number - name?: string - } - Relationships: [] - } + executed_at?: string | null; + hash?: string; + id?: number; + name?: string; + }; + Relationships: []; + }; objects: { Row: { - bucket_id: string | null - created_at: string | null - id: string - last_accessed_at: string | null - metadata: Json | null - name: string | null - owner: string | null - owner_id: string | null - path_tokens: string[] | null - updated_at: string | null - version: string | null - } + bucket_id: string | null; + created_at: string | null; + id: string; + last_accessed_at: string | null; + metadata: Json | null; + name: string | null; + owner: string | null; + owner_id: string | null; + path_tokens: string[] | null; + updated_at: string | null; + version: string | null; + }; Insert: { - bucket_id?: string | null - created_at?: string | null - id?: string - last_accessed_at?: string | null - metadata?: Json | null - name?: string | null - owner?: string | null - owner_id?: string | null - path_tokens?: string[] | null - updated_at?: string | null - version?: string | null - } + bucket_id?: string | null; + created_at?: string | null; + id?: string; + last_accessed_at?: string | null; + metadata?: Json | null; + name?: string | null; + owner?: string | null; + owner_id?: string | null; + path_tokens?: string[] | null; + updated_at?: string | null; + version?: string | null; + }; Update: { - bucket_id?: string | null - created_at?: string | null - id?: string - last_accessed_at?: string | null - metadata?: Json | null - name?: string | null - owner?: string | null - owner_id?: string | null - path_tokens?: string[] | null - updated_at?: string | null - version?: string | null - } + bucket_id?: string | null; + created_at?: string | null; + id?: string; + last_accessed_at?: string | null; + metadata?: Json | null; + name?: string | null; + owner?: string | null; + owner_id?: string | null; + path_tokens?: string[] | null; + updated_at?: string | null; + version?: string | null; + }; Relationships: [ { - foreignKeyName: "objects_bucketId_fkey" - columns: ["bucket_id"] - isOneToOne: false - referencedRelation: "buckets" - referencedColumns: ["id"] + foreignKeyName: 'objects_bucketId_fkey'; + columns: ['bucket_id']; + isOneToOne: false; + referencedRelation: 'buckets'; + referencedColumns: ['id']; }, - ] - } - } + ]; + }; + }; Views: { - [_ in never]: never - } + [_ in never]: never; + }; Functions: { can_insert_object: { Args: { - bucketid: string - name: string - owner: string - metadata: Json - } - Returns: undefined - } + bucketid: string; + name: string; + owner: string; + metadata: Json; + }; + Returns: undefined; + }; extension: { Args: { - name: string - } - Returns: string - } + name: string; + }; + Returns: string; + }; filename: { Args: { - name: string - } - Returns: string - } + name: string; + }; + Returns: string; + }; foldername: { Args: { - name: string - } - Returns: string[] - } + name: string; + }; + Returns: string[]; + }; get_size_by_bucket: { - Args: Record + Args: Record; Returns: { - size: number - bucket_id: string - }[] - } + size: number; + bucket_id: string; + }[]; + }; search: { Args: { - prefix: string - bucketname: string - limits?: number - levels?: number - offsets?: number - search?: string - sortcolumn?: string - sortorder?: string - } + prefix: string; + bucketname: string; + limits?: number; + levels?: number; + offsets?: number; + search?: string; + sortcolumn?: string; + sortorder?: string; + }; Returns: { - name: string - id: string - updated_at: string - created_at: string - last_accessed_at: string - metadata: Json - }[] - } - } + name: string; + id: string; + updated_at: string; + created_at: string; + last_accessed_at: string; + metadata: Json; + }[]; + }; + }; Enums: { - [_ in never]: never - } + [_ in never]: never; + }; CompositeTypes: { - [_ in never]: never - } - } -} + [_ in never]: never; + }; + }; +}; -type PublicSchema = Database[Extract] +type PublicSchema = Database[Extract]; export type Tables< PublicTableNameOrOptions extends - | keyof (PublicSchema["Tables"] & PublicSchema["Views"]) + | keyof (PublicSchema['Tables'] & PublicSchema['Views']) | { schema: keyof Database }, TableName extends PublicTableNameOrOptions extends { schema: keyof Database } - ? keyof (Database[PublicTableNameOrOptions["schema"]]["Tables"] & - Database[PublicTableNameOrOptions["schema"]]["Views"]) + ? keyof (Database[PublicTableNameOrOptions['schema']]['Tables'] & + Database[PublicTableNameOrOptions['schema']]['Views']) : never = never, > = PublicTableNameOrOptions extends { schema: keyof Database } - ? (Database[PublicTableNameOrOptions["schema"]]["Tables"] & - Database[PublicTableNameOrOptions["schema"]]["Views"])[TableName] extends { - Row: infer R + ? (Database[PublicTableNameOrOptions['schema']]['Tables'] & + Database[PublicTableNameOrOptions['schema']]['Views'])[TableName] extends { + Row: infer R; } ? R : never - : PublicTableNameOrOptions extends keyof (PublicSchema["Tables"] & - PublicSchema["Views"]) - ? (PublicSchema["Tables"] & - PublicSchema["Views"])[PublicTableNameOrOptions] extends { - Row: infer R + : PublicTableNameOrOptions extends keyof (PublicSchema['Tables'] & + PublicSchema['Views']) + ? (PublicSchema['Tables'] & + PublicSchema['Views'])[PublicTableNameOrOptions] extends { + Row: infer R; } ? R : never - : never + : never; export type TablesInsert< PublicTableNameOrOptions extends - | keyof PublicSchema["Tables"] + | keyof PublicSchema['Tables'] | { schema: keyof Database }, TableName extends PublicTableNameOrOptions extends { schema: keyof Database } - ? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"] + ? keyof Database[PublicTableNameOrOptions['schema']]['Tables'] : never = never, > = PublicTableNameOrOptions extends { schema: keyof Database } - ? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends { - Insert: infer I + ? Database[PublicTableNameOrOptions['schema']]['Tables'][TableName] extends { + Insert: infer I; } ? I : never - : PublicTableNameOrOptions extends keyof PublicSchema["Tables"] - ? PublicSchema["Tables"][PublicTableNameOrOptions] extends { - Insert: infer I + : PublicTableNameOrOptions extends keyof PublicSchema['Tables'] + ? PublicSchema['Tables'][PublicTableNameOrOptions] extends { + Insert: infer I; } ? I : never - : never + : never; export type TablesUpdate< PublicTableNameOrOptions extends - | keyof PublicSchema["Tables"] + | keyof PublicSchema['Tables'] | { schema: keyof Database }, TableName extends PublicTableNameOrOptions extends { schema: keyof Database } - ? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"] + ? keyof Database[PublicTableNameOrOptions['schema']]['Tables'] : never = never, > = PublicTableNameOrOptions extends { schema: keyof Database } - ? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends { - Update: infer U + ? Database[PublicTableNameOrOptions['schema']]['Tables'][TableName] extends { + Update: infer U; } ? U : never - : PublicTableNameOrOptions extends keyof PublicSchema["Tables"] - ? PublicSchema["Tables"][PublicTableNameOrOptions] extends { - Update: infer U + : PublicTableNameOrOptions extends keyof PublicSchema['Tables'] + ? PublicSchema['Tables'][PublicTableNameOrOptions] extends { + Update: infer U; } ? U : never - : never + : never; export type Enums< PublicEnumNameOrOptions extends - | keyof PublicSchema["Enums"] + | keyof PublicSchema['Enums'] | { schema: keyof Database }, EnumName extends PublicEnumNameOrOptions extends { schema: keyof Database } - ? keyof Database[PublicEnumNameOrOptions["schema"]]["Enums"] + ? keyof Database[PublicEnumNameOrOptions['schema']]['Enums'] : never = never, > = PublicEnumNameOrOptions extends { schema: keyof Database } - ? Database[PublicEnumNameOrOptions["schema"]]["Enums"][EnumName] - : PublicEnumNameOrOptions extends keyof PublicSchema["Enums"] - ? PublicSchema["Enums"][PublicEnumNameOrOptions] - : never - + ? Database[PublicEnumNameOrOptions['schema']]['Enums'][EnumName] + : PublicEnumNameOrOptions extends keyof PublicSchema['Enums'] + ? PublicSchema['Enums'][PublicEnumNameOrOptions] + : never;