From 1e23ee2783234fa4e644199cdc3498036b7d9300 Mon Sep 17 00:00:00 2001 From: giancarlo Date: Sun, 7 Apr 2024 17:34:47 +0800 Subject: [PATCH] Replace 'base' line item type with 'flat' This commit updates all instances of 'base' line item type to 'flat'. It modifies the BillingIntervalSchema, the validation rules for one-time plans, and the function to get the primary line item for a plan. Furthermore, it adjusts the display and filtering of line items in the pricing table component and the plan picker component. The SQL migration script and the sample billing configuration are also updated to reflect this change. --- apps/web/config/billing.sample.config.ts | 40 +++++-------- .../billing/core/src/create-billing-schema.ts | 32 ++++++++--- .../gateway/src/components/plan-picker.tsx | 4 +- .../gateway/src/components/pricing-table.tsx | 56 ++++++++++++++----- packages/next/README.md | 4 +- supabase/migrations/20221215192558_schema.sql | 2 +- 6 files changed, 87 insertions(+), 51 deletions(-) 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' );