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:
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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');
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user