Remove billing and checkout redirect buttons and related services

Deleted the billing-redirect-button, checkout-redirect-button, and embedded-stripe-checkout components. Additionally, removed the shadcn directory, which encompassed billing-related icons. This change streamlines the subscription settings interface and organizes the system's payment management. This update is a stepping stone towards improving the billing system's overall architecture.
This commit is contained in:
giancarlo
2024-03-25 11:39:41 +08:00
parent 78c704e54d
commit cb8b23e8c0
123 changed files with 1674 additions and 3071 deletions

View File

@@ -16,20 +16,20 @@
"peerDependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"zod": "^3.22.4"
},
"dependencies": {
"zod": "^3.22.4",
"@kit/ui": "0.1.0",
"@kit/stripe": "0.1.0",
"@kit/billing": "0.1.0",
"@kit/supabase": "^0.1.0",
"@kit/shared": "^0.1.0",
"lucide-react": "^0.361.0"
},
"devDependencies": {
"@kit/prettier-config": "0.1.0",
"@kit/eslint-config": "0.2.0",
"@kit/tailwind-config": "0.1.0",
"@kit/tsconfig": "0.1.0"
"@kit/tsconfig": "0.1.0",
"@supabase/supabase-js": "^2.39.8"
},
"eslintConfig": {
"root": true,

View File

@@ -22,6 +22,7 @@ import {
RadioGroupItemLabel,
} from '@kit/ui/radio-group';
import { Trans } from '@kit/ui/trans';
import { cn } from '@kit/ui/utils';
export function PlanPicker(
props: React.PropsWithChildren<{
@@ -81,29 +82,39 @@ export function PlanPicker(
<FormControl>
<RadioGroup name={field.name} value={field.value}>
{intervals.map((interval) => {
return (
<div
key={interval}
className={'flex items-center space-x-2'}
>
<RadioGroupItem
id={interval}
value={interval}
onClick={() => {
form.setValue('interval', interval);
}}
/>
<div className={'flex space-x-2.5'}>
{intervals.map((interval) => {
const selected = field.value === interval;
<span className={'text-sm font-bold'}>
<Trans
i18nKey={`common.billingInterval.${interval}`}
defaults={interval}
return (
<label
key={interval}
className={cn(
'hover:bg-muted flex items-center space-x-2 rounded-md border border-transparent px-4 py-2',
{
['border-border']: selected,
['hover:bg-muted']: !selected,
},
)}
>
<RadioGroupItem
id={interval}
value={interval}
onClick={() => {
form.setValue('planId', '');
form.setValue('interval', interval);
}}
/>
</span>
</div>
);
})}
<span className={'text-sm font-bold'}>
<Trans
i18nKey={`common:billingInterval.${interval}`}
/>
</span>
</label>
);
})}
</div>
</RadioGroup>
</FormControl>
<FormMessage />
@@ -130,7 +141,10 @@ export function PlanPicker(
}
return (
<RadioGroupItemLabel key={variant.id}>
<RadioGroupItemLabel
selected={field.value === variant.id}
key={variant.id}
>
<RadioGroupItem
id={variant.id}
value={variant.id}
@@ -144,9 +158,7 @@ export function PlanPicker(
>
<Label
htmlFor={variant.id}
className={
'flex flex-col justify-center space-y-1.5'
}
className={'flex flex-col justify-center space-y-2'}
>
<span className="font-bold">{item.name}</span>

View File

@@ -1,2 +1,3 @@
export * from './billing-gateway-service';
export * from './gateway-provider-factory';
export * from './services/billing-gateway/billing-gateway.service';
export * from './services/billing-gateway/billing-gateway-provider-factory';
export * from './services/billing-event-handler/billing-gateway-provider-factory';

View File

@@ -0,0 +1,106 @@
import { SupabaseClient } from '@supabase/supabase-js';
import { BillingWebhookHandlerService } from '@kit/billing';
import { Logger } from '@kit/shared/logger';
import { Database } from '@kit/supabase/database';
export class BillingEventHandlerService {
constructor(
private readonly client: SupabaseClient<Database>,
private readonly strategy: BillingWebhookHandlerService,
) {}
async handleWebhookEvent(request: Request) {
const event = await this.strategy.verifyWebhookSignature(request);
if (!event) {
throw new Error('Invalid signature');
}
return this.strategy.handleWebhookEvent(event, {
onSubscriptionDeleted: async (subscriptionId: string) => {
// Handle the subscription deleted event
// here we delete the subscription from the database
Logger.info(
{
namespace: 'billing',
subscriptionId,
},
'Processing subscription deleted event',
);
const { error } = await this.client
.from('subscriptions')
.delete()
.match({ id: subscriptionId });
if (error) {
throw new Error('Failed to delete subscription');
}
Logger.info(
{
namespace: 'billing',
subscriptionId,
},
'Successfully deleted subscription',
);
},
onSubscriptionUpdated: async (subscription) => {
const ctx = {
namespace: 'billing',
subscriptionId: subscription.id,
provider: subscription.billing_provider,
accountId: subscription.account_id,
};
Logger.info(ctx, 'Processing subscription updated event');
// Handle the subscription updated event
// here we update the subscription in the database
const { error } = await this.client
.from('subscriptions')
.update(subscription)
.match({ id: subscription.id });
if (error) {
Logger.error(
{
error,
...ctx,
},
'Failed to update subscription',
);
throw new Error('Failed to update subscription');
}
Logger.info(ctx, 'Successfully updated subscription');
},
onCheckoutSessionCompleted: async (subscription) => {
// Handle the checkout session completed event
// here we add the subscription to the database
const ctx = {
namespace: 'billing',
subscriptionId: subscription.id,
provider: subscription.billing_provider,
accountId: subscription.account_id,
};
Logger.info(ctx, 'Processing checkout session completed event...');
const { error } = await this.client.rpc('add_subscription', {
subscription,
});
if (error) {
Logger.error(ctx, 'Failed to add subscription');
throw new Error('Failed to add subscription');
}
Logger.info(ctx, 'Successfully added subscription');
},
});
}
}

View File

@@ -0,0 +1,28 @@
import { z } from 'zod';
import { BillingProvider, BillingWebhookHandlerService } from '@kit/billing';
export class BillingEventHandlerFactoryService {
static async GetProviderStrategy(
provider: z.infer<typeof BillingProvider>,
): Promise<BillingWebhookHandlerService> {
switch (provider) {
case 'stripe': {
const { StripeWebhookHandlerService } = await import('@kit/stripe');
return new StripeWebhookHandlerService();
}
case 'paddle': {
throw new Error('Paddle is not supported yet');
}
case 'lemon-squeezy': {
throw new Error('Lemon Squeezy is not supported yet');
}
default:
throw new Error(`Unsupported billing provider: ${provider as string}`);
}
}
}

View File

@@ -0,0 +1,20 @@
import { Database } from '@kit/supabase/database';
import { getSupabaseServerActionClient } from '@kit/supabase/server-actions-client';
import { BillingEventHandlerService } from './billing-event-handler.service';
import { BillingEventHandlerFactoryService } from './billing-gateway-factory.service';
/**
* @description This function retrieves the billing provider from the database and returns a
* new instance of the `BillingGatewayService` class. This class is used to interact with the server actions
* defined in the host application.
*/
export async function getBillingEventHandlerService(
client: ReturnType<typeof getSupabaseServerActionClient>,
provider: Database['public']['Enums']['billing_provider'],
) {
const strategy =
await BillingEventHandlerFactoryService.GetProviderStrategy(provider);
return new BillingEventHandlerService(client, strategy);
}

View File

@@ -1,15 +1,13 @@
import { getSupabaseServerActionClient } from '@kit/supabase/server-actions-client';
import { BillingGatewayService } from './billing-gateway-service';
import { BillingGatewayService } from './billing-gateway.service';
/**
* @description This function retrieves the billing provider from the database and returns a
* new instance of the `BillingGatewayService` class. This class is used to interact with the server actions
* defined in the host application.
* @param {ReturnType<typeof getSupabaseServerActionClient>} client - The Supabase server action client.
*
*/
export async function getGatewayProvider(
export async function getBillingGatewayProvider(
client: ReturnType<typeof getSupabaseServerActionClient>,
) {
const provider = await getBillingProvider(client);