Adjusted Per seat billing and added example to the sample schema
This commit is contained in:
@@ -1,7 +1,13 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
enum LineItemType {
|
||||
Flat = 'flat',
|
||||
PerSeat = 'per_seat',
|
||||
Metered = 'metered',
|
||||
}
|
||||
|
||||
const BillingIntervalSchema = z.enum(['month', 'year']);
|
||||
const LineItemTypeSchema = z.enum(['flat', 'per-seat', 'metered']);
|
||||
const LineItemTypeSchema = z.nativeEnum(LineItemType);
|
||||
|
||||
export const BillingProviderSchema = z.enum([
|
||||
'stripe',
|
||||
@@ -83,8 +89,10 @@ export const PlanSchema = z
|
||||
lineItems: z.array(LineItemSchema).refine(
|
||||
(schema) => {
|
||||
const types = schema.map((item) => item.type);
|
||||
const perSeat = types.filter((type) => type === 'per-seat').length;
|
||||
const flat = types.filter((type) => type === 'flat').length;
|
||||
const perSeat = types.filter(
|
||||
(type) => type === LineItemType.PerSeat,
|
||||
).length;
|
||||
const flat = types.filter((type) => type === LineItemType.Flat).length;
|
||||
|
||||
return perSeat <= 1 && flat <= 1;
|
||||
},
|
||||
@@ -135,7 +143,7 @@ export const PlanSchema = z
|
||||
(data) => {
|
||||
if (data.paymentType === 'one-time') {
|
||||
const nonFlatLineItems = data.lineItems.filter(
|
||||
(item) => item.type !== 'flat',
|
||||
(item) => item.type !== LineItemType.Flat,
|
||||
);
|
||||
|
||||
return nonFlatLineItems.length === 0;
|
||||
@@ -314,7 +322,7 @@ export function getPrimaryLineItem(
|
||||
}
|
||||
|
||||
const flatLineItem = plan.lineItems.find(
|
||||
(item) => item.type === 'flat',
|
||||
(item) => item.type === LineItemType.Flat,
|
||||
);
|
||||
|
||||
if (flatLineItem) {
|
||||
|
||||
@@ -20,10 +20,14 @@ export function EmbeddedCheckout(
|
||||
);
|
||||
|
||||
return (
|
||||
<CheckoutComponent
|
||||
onClose={props.onClose}
|
||||
checkoutToken={props.checkoutToken}
|
||||
/>
|
||||
<>
|
||||
<CheckoutComponent
|
||||
onClose={props.onClose}
|
||||
checkoutToken={props.checkoutToken}
|
||||
/>
|
||||
|
||||
<BlurryBackdrop />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -98,3 +102,14 @@ function buildLazyComponent<
|
||||
|
||||
return memo(LazyComponent);
|
||||
}
|
||||
|
||||
function BlurryBackdrop() {
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
'bg-background/30 fixed left-0 top-0 w-full backdrop-blur-sm' +
|
||||
' !m-0 h-full'
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ export function LineItemDetails(
|
||||
);
|
||||
|
||||
const FlatFee = () => (
|
||||
<div key={item.id} className={'flex flex-col'}>
|
||||
<div className={'flex flex-col'}>
|
||||
<div className={className}>
|
||||
<span className={'flex items-center space-x-1'}>
|
||||
<span className={'flex items-center space-x-1.5'}>
|
||||
@@ -115,8 +115,8 @@ export function LineItemDetails(
|
||||
);
|
||||
|
||||
const PerSeat = () => (
|
||||
<div className={'flex flex-col'}>
|
||||
<div key={item.id} className={className}>
|
||||
<div key={index} className={'flex flex-col'}>
|
||||
<div className={className}>
|
||||
<span className={'flex items-center space-x-1.5'}>
|
||||
<PlusSquare className={'w-4'} />
|
||||
|
||||
@@ -141,7 +141,7 @@ export function LineItemDetails(
|
||||
);
|
||||
|
||||
const Metered = () => (
|
||||
<div key={item.id} className={'flex flex-col'}>
|
||||
<div key={index} className={'flex flex-col'}>
|
||||
<div className={className}>
|
||||
<span className={'flex items-center space-x-1'}>
|
||||
<span className={'flex items-center space-x-1.5'}>
|
||||
@@ -179,13 +179,13 @@ export function LineItemDetails(
|
||||
|
||||
switch (item.type) {
|
||||
case 'flat':
|
||||
return <FlatFee />;
|
||||
return <FlatFee key={item.id} />;
|
||||
|
||||
case 'per-seat':
|
||||
return <PerSeat />;
|
||||
case 'per_seat':
|
||||
return <PerSeat key={item.id} />;
|
||||
|
||||
case 'metered': {
|
||||
return <Metered />;
|
||||
return <Metered key={item.id} />;
|
||||
}
|
||||
}
|
||||
})}
|
||||
|
||||
@@ -232,7 +232,7 @@ function PricingItem(
|
||||
`animate-in slide-in-from-left-4 fade-in text-sm capitalize`,
|
||||
)}
|
||||
>
|
||||
<If condition={props.primaryLineItem.type === 'per-seat'}>
|
||||
<If condition={props.primaryLineItem.type === 'per_seat'}>
|
||||
<Trans i18nKey={'billing:perTeamMember'} />
|
||||
</If>
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ export class BillingEventHandlerService {
|
||||
|
||||
// Handle the subscription deleted event
|
||||
// here we delete the subscription from the database
|
||||
logger.info(ctx, 'Processing subscription deleted event');
|
||||
logger.info(ctx, 'Processing subscription deleted event...');
|
||||
|
||||
const { error } = await client
|
||||
.from('subscriptions')
|
||||
|
||||
@@ -127,16 +127,4 @@ export class BillingGatewayService {
|
||||
|
||||
return strategy.updateSubscription(payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a plan by the specified plan ID.
|
||||
* @param planId
|
||||
*/
|
||||
async getPlanById(planId: string) {
|
||||
const strategy = await BillingGatewayFactoryService.GetProviderStrategy(
|
||||
this.provider,
|
||||
);
|
||||
|
||||
return strategy.getPlanById(planId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,12 +261,12 @@ export class StripeWebhookHandlerService
|
||||
}
|
||||
|
||||
private handleSubscriptionDeletedEvent(
|
||||
subscription: Stripe.CustomerSubscriptionDeletedEvent,
|
||||
event: Stripe.CustomerSubscriptionDeletedEvent,
|
||||
onSubscriptionDeletedCallback: (subscriptionId: string) => Promise<unknown>,
|
||||
) {
|
||||
// Here we don't need to do anything, so we just return the callback
|
||||
|
||||
return onSubscriptionDeletedCallback(subscription.id);
|
||||
return onSubscriptionDeletedCallback(event.data.object.id);
|
||||
}
|
||||
|
||||
private buildSubscriptionPayload<
|
||||
@@ -298,6 +298,7 @@ export class StripeWebhookHandlerService
|
||||
id: item.id,
|
||||
quantity,
|
||||
subscription_id: params.id,
|
||||
subscription_item_id: item.id,
|
||||
product_id: item.price?.product as string,
|
||||
variant_id: variantId,
|
||||
price_amount: item.price?.unit_amount,
|
||||
|
||||
@@ -192,7 +192,7 @@ function useGetColumns(
|
||||
permissions={permissions}
|
||||
member={row.original}
|
||||
currentUserId={params.currentUserId}
|
||||
accountId={params.currentAccountId}
|
||||
currentTeamAccountId={params.currentAccountId}
|
||||
currentRoleHierarchy={params.currentRoleHierarchy}
|
||||
/>
|
||||
),
|
||||
@@ -206,12 +206,13 @@ function ActionsDropdown({
|
||||
permissions,
|
||||
member,
|
||||
currentUserId,
|
||||
currentTeamAccountId,
|
||||
currentRoleHierarchy,
|
||||
}: {
|
||||
permissions: Permissions;
|
||||
member: Members[0];
|
||||
currentUserId: string;
|
||||
accountId: string;
|
||||
currentTeamAccountId: string;
|
||||
currentRoleHierarchy: number;
|
||||
}) {
|
||||
const [isRemoving, setIsRemoving] = useState(false);
|
||||
@@ -275,7 +276,7 @@ function ActionsDropdown({
|
||||
<RemoveMemberDialog
|
||||
isOpen
|
||||
setIsOpen={setIsRemoving}
|
||||
accountId={member.id}
|
||||
teamAccountId={currentTeamAccountId}
|
||||
userId={member.user_id}
|
||||
/>
|
||||
</If>
|
||||
@@ -286,7 +287,7 @@ function ActionsDropdown({
|
||||
setIsOpen={setIsUpdatingRole}
|
||||
userId={member.user_id}
|
||||
userRole={member.role}
|
||||
teamAccountId={member.account_id}
|
||||
teamAccountId={currentTeamAccountId}
|
||||
userRoleHierarchy={currentRoleHierarchy}
|
||||
/>
|
||||
</If>
|
||||
|
||||
@@ -19,9 +19,9 @@ import { removeMemberFromAccountAction } from '../../server/actions/team-members
|
||||
export const RemoveMemberDialog: React.FC<{
|
||||
isOpen: boolean;
|
||||
setIsOpen: (isOpen: boolean) => void;
|
||||
accountId: string;
|
||||
teamAccountId: string;
|
||||
userId: string;
|
||||
}> = ({ isOpen, setIsOpen, accountId, userId }) => {
|
||||
}> = ({ isOpen, setIsOpen, teamAccountId, userId }) => {
|
||||
return (
|
||||
<AlertDialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<AlertDialogContent>
|
||||
@@ -37,7 +37,7 @@ export const RemoveMemberDialog: React.FC<{
|
||||
|
||||
<RemoveMemberForm
|
||||
setIsOpen={setIsOpen}
|
||||
accountId={accountId}
|
||||
accountId={teamAccountId}
|
||||
userId={userId}
|
||||
/>
|
||||
</AlertDialogContent>
|
||||
|
||||
@@ -28,7 +28,7 @@ export class AccountPerSeatBillingService {
|
||||
id,
|
||||
subscription_items !inner (
|
||||
quantity,
|
||||
id: variant_id,
|
||||
id,
|
||||
type
|
||||
)
|
||||
`,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user