diff --git a/apps/web/config/billing.sample.config.ts b/apps/web/config/billing.sample.config.ts index af26d4eb1..ebc105f62 100644 --- a/apps/web/config/billing.sample.config.ts +++ b/apps/web/config/billing.sample.config.ts @@ -32,7 +32,7 @@ export default createBillingSchema({ id: '324643', name: 'Base', cost: 999.99, - type: 'base', + type: 'flat', }, ], }, @@ -53,33 +53,23 @@ export default createBillingSchema({ interval: 'month', lineItems: [ { - id: '55476', - name: 'Base', + id: '324646', + name: 'Addon 2', cost: 9.99, - type: 'base', - }, - { - id: '324644', - name: 'Addon 1', - cost: 99.99, type: 'metered', - unit: 'GB', + unit: 'GBs', tiers: [ + { + upTo: 5, + cost: 0, + }, { upTo: 10, - cost: 0.99, - }, - { - upTo: 100, - cost: 0.49, - }, - { - upTo: 1000, - cost: 0.29, + cost: 6.99, }, { upTo: 'unlimited', - cost: 0.19, + cost: 0.49, }, ], }, @@ -115,7 +105,7 @@ export default createBillingSchema({ id: 'price_1NNwYHI1i3VnbZTqI2UzaHIe1', name: 'Base', cost: 99.99, - type: 'base', + type: 'flat', }, ], }, @@ -140,7 +130,7 @@ export default createBillingSchema({ id: 'price_1NNwYHI1i3VnbZTqI2UzaHIe2', name: 'Base', cost: 19.99, - type: 'base', + type: 'flat', }, ], }, @@ -154,7 +144,7 @@ export default createBillingSchema({ id: 'price_1NNwYHI1i3VnbZTqI2UzaHIe3', name: 'Base', cost: 199.99, - type: 'base', + type: 'flat', }, ], }, @@ -183,7 +173,7 @@ export default createBillingSchema({ id: 'price_1NNwYHI1i3VnbZTqI2UzaHIe4', name: 'Base', cost: 29.99, - type: 'base', + type: 'flat', }, ], }, @@ -197,7 +187,7 @@ export default createBillingSchema({ id: 'price_1NNwYHI1i3VnbZTqI2UzaHIe5', name: 'Base', cost: 299.99, - type: 'base', + type: 'flat', }, ], }, diff --git a/packages/billing/core/src/create-billing-schema.ts b/packages/billing/core/src/create-billing-schema.ts index 521f5f497..34ba4805f 100644 --- a/packages/billing/core/src/create-billing-schema.ts +++ b/packages/billing/core/src/create-billing-schema.ts @@ -1,7 +1,7 @@ import { z } from 'zod'; const BillingIntervalSchema = z.enum(['month', 'year']); -const LineItemTypeSchema = z.enum(['base', 'per-seat', 'metered']); +const LineItemTypeSchema = z.enum(['flat', 'per-seat', 'metered']); export const BillingProviderSchema = z.enum([ 'stripe', @@ -122,15 +122,17 @@ export const PlanSchema = z .refine( (data) => { if (data.paymentType === 'one-time') { - const baseItems = data.lineItems.filter((item) => item.type !== 'base'); + const nonFlatLineItems = data.lineItems.filter( + (item) => item.type !== 'flat', + ); - return baseItems.length === 0; + return nonFlatLineItems.length === 0; } return true; }, { - message: 'One-time plans must not have non-base line items', + message: 'One-time plans must not have non-flat line items', path: ['paymentType', 'lineItems'], }, ); @@ -266,7 +268,17 @@ export function getPlanIntervals(config: z.infer) { return Array.from(new Set(intervals)); } -export function getBaseLineItem( +/** + * @name getPrimaryLineItem + * @description Get the primary line item for a plan + * By default, the primary line item is the first line item in the plan for Lemon Squeezy + * For other providers, the primary line item is the first flat line item in the plan. If there are no flat line items, + * the first line item is returned. + * + * @param config + * @param planId + */ +export function getPrimaryLineItem( config: z.infer, planId: string, ) { @@ -278,11 +290,15 @@ export function getBaseLineItem( return plan.lineItems[0]; } - const item = plan.lineItems.find((item) => item.type === 'base'); + const flatLineItem = plan.lineItems.find( + (item) => item.type === 'flat', + ); - if (item) { - return item; + if (flatLineItem) { + return flatLineItem; } + + return plan.lineItems[0]; } } } diff --git a/packages/billing/gateway/src/components/plan-picker.tsx b/packages/billing/gateway/src/components/plan-picker.tsx index 5325bd064..3a6eae2c4 100644 --- a/packages/billing/gateway/src/components/plan-picker.tsx +++ b/packages/billing/gateway/src/components/plan-picker.tsx @@ -11,8 +11,8 @@ import { z } from 'zod'; import { BillingConfig, LineItemSchema, - getBaseLineItem, getPlanIntervals, + getPrimaryLineItem, getProductPlanPair, } from '@kit/billing'; import { formatCurrency } from '@kit/shared/utils'; @@ -214,7 +214,7 @@ export function PlanPicker( return null; } - const baseLineItem = getBaseLineItem( + const baseLineItem = getPrimaryLineItem( props.config, plan.id, ); diff --git a/packages/billing/gateway/src/components/pricing-table.tsx b/packages/billing/gateway/src/components/pricing-table.tsx index 1cd60222f..27f81bc98 100644 --- a/packages/billing/gateway/src/components/pricing-table.tsx +++ b/packages/billing/gateway/src/components/pricing-table.tsx @@ -10,8 +10,8 @@ import { z } from 'zod'; import { BillingConfig, LineItemSchema, - getBaseLineItem, getPlanIntervals, + getPrimaryLineItem, } from '@kit/billing'; import { formatCurrency } from '@kit/shared/utils'; import { Badge } from '@kit/ui/badge'; @@ -79,9 +79,9 @@ export function PricingTable({ return null; } - const baseLineItem = getBaseLineItem(config, plan.id); + const primaryLineItem = getPrimaryLineItem(config, plan.id); - if (!baseLineItem) { + if (!primaryLineItem) { throw new Error(`Base line item was not found`); } @@ -94,7 +94,7 @@ export function PricingTable({ selectable key={plan.id} plan={plan} - baseLineItem={baseLineItem} + primaryLineItem={primaryLineItem} product={product} paths={paths} displayPlanDetails={displayPlanDetails} @@ -118,10 +118,7 @@ function PricingItem( selectable: boolean; - baseLineItem: { - id: string; - cost: number; - }; + primaryLineItem: z.infer; plan: { id: string; @@ -149,10 +146,10 @@ function PricingItem( ) { const highlighted = props.product.highlighted ?? false; - // we want to exclude the base plan from the list of line items - // since we are displaying the base plan separately as the main price + // we want to exclude the primary plan from the list of line items + // since we are displaying the primary line item separately as the main price const lineItemsToDisplay = props.plan.lineItems.filter((item) => { - return item.type !== 'base'; + return item.id !== props.primaryLineItem.id; }); return ( @@ -171,7 +168,12 @@ function PricingItem(
- {props.product.name} + + + @@ -209,13 +211,13 @@ function PricingItem(
- {formatCurrency(props.product.currency, props.baseLineItem.cost)} + {formatCurrency(props.product.currency, props.primaryLineItem.cost)} + + + / + + + + + + + + + + +
diff --git a/packages/next/README.md b/packages/next/README.md index f8b49ea57..32919ca1d 100644 --- a/packages/next/README.md +++ b/packages/next/README.md @@ -78,4 +78,6 @@ export const POST = enhanceRouteHandler(({ request, body, user }) => { id: z.number() }), }); -``` \ No newline at end of file +``` + +When using a Captcha, the consumer will pass an header `x-captcha-token` with the captcha token. \ No newline at end of file diff --git a/supabase/migrations/20221215192558_schema.sql b/supabase/migrations/20221215192558_schema.sql index f3efa2952..e17528c35 100644 --- a/supabase/migrations/20221215192558_schema.sql +++ b/supabase/migrations/20221215192558_schema.sql @@ -123,7 +123,7 @@ create type public.billing_provider as ENUM( - You can add more types as needed. */ create type public.subscription_item_type as ENUM( - 'base', + 'flat', 'per_seat', 'metered' );