Adjusted Per seat billing and added example to the sample schema

This commit is contained in:
giancarlo
2024-04-22 22:48:02 +08:00
parent b96d4cf855
commit 70da6ef1fa
19 changed files with 2190 additions and 2066 deletions

View File

@@ -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) {

View File

@@ -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'
}
/>
);
}

View File

@@ -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} />;
}
}
})}

View File

@@ -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>

View File

@@ -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')

View File

@@ -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);
}
}

View File

@@ -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,