Update UI style and enhance billing services
Several changes have been made in this commit. Firstly, updates have been made to the site-header-account-section and the pricing-table components to enhance UI aesthetics. Secondly, billing services have been significantly improved. A new method for retrieving plan information by an ID has been introduced. This method is available for all strategy services, including Stripe and Lemon-Squeezy. Furthermore, the way context and logging are handled during the billing process has been streamlined for better readability and efficiency.
This commit is contained in:
@@ -61,7 +61,7 @@ function AuthButtons() {
|
||||
<ModeToggle />
|
||||
|
||||
<Link href={pathsConfig.auth.signIn}>
|
||||
<Button variant={'ghost'}>
|
||||
<Button className={'rounded-full'} variant={'ghost'}>
|
||||
<Trans i18nKey={'auth:signIn'} />
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
@@ -51,4 +51,11 @@ export abstract class BillingStrategyProviderService {
|
||||
): Promise<{
|
||||
success: boolean;
|
||||
}>;
|
||||
|
||||
abstract getPlanById(planId: string): Promise<{
|
||||
id: string;
|
||||
name: string;
|
||||
interval: string;
|
||||
amount: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ function PricingItem(
|
||||
className={cn(
|
||||
props.className,
|
||||
`s-full flex flex-1 grow flex-col items-stretch justify-between space-y-8 self-stretch
|
||||
rounded-lg p-6 ring-2 lg:w-4/12 xl:max-w-[22rem] xl:p-8`,
|
||||
rounded-lg p-6 ring-2 lg:w-4/12 xl:max-w-[19rem]`,
|
||||
{
|
||||
['ring-primary']: highlighted,
|
||||
['dark:shadow-primary/30 shadow-none ring-transparent dark:shadow-sm']:
|
||||
|
||||
@@ -127,4 +127,16 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
cancelSubscription,
|
||||
createUsageRecord,
|
||||
getCheckout,
|
||||
getVariant,
|
||||
updateSubscriptionItem,
|
||||
} from '@lemonsqueezy/lemonsqueezy.js';
|
||||
import { z } from 'zod';
|
||||
@@ -25,18 +26,19 @@ import { createLemonSqueezyCheckout } from './create-lemon-squeezy-checkout';
|
||||
export class LemonSqueezyBillingStrategyService
|
||||
implements BillingStrategyProviderService
|
||||
{
|
||||
private readonly namespace = 'billing.lemon-squeezy';
|
||||
|
||||
async createCheckoutSession(
|
||||
params: z.infer<typeof CreateBillingCheckoutSchema>,
|
||||
) {
|
||||
const logger = await getLogger();
|
||||
|
||||
logger.info(
|
||||
{
|
||||
name: 'billing.lemon-squeezy',
|
||||
...params,
|
||||
},
|
||||
'Creating checkout session...',
|
||||
);
|
||||
const ctx = {
|
||||
name: this.namespace,
|
||||
...params,
|
||||
};
|
||||
|
||||
logger.info(ctx, 'Creating checkout session...');
|
||||
|
||||
const { data: response, error } = await createLemonSqueezyCheckout(params);
|
||||
|
||||
@@ -45,9 +47,7 @@ export class LemonSqueezyBillingStrategyService
|
||||
|
||||
logger.error(
|
||||
{
|
||||
name: 'billing.lemon-squeezy',
|
||||
customerId: params.customerId,
|
||||
accountId: params.accountId,
|
||||
...ctx,
|
||||
error: error?.message,
|
||||
},
|
||||
'Failed to create checkout session',
|
||||
@@ -56,14 +56,7 @@ export class LemonSqueezyBillingStrategyService
|
||||
throw new Error('Failed to create checkout session');
|
||||
}
|
||||
|
||||
logger.info(
|
||||
{
|
||||
name: 'billing.lemon-squeezy',
|
||||
customerId: params.customerId,
|
||||
accountId: params.accountId,
|
||||
},
|
||||
'Checkout session created successfully',
|
||||
);
|
||||
logger.info(ctx, 'Checkout session created successfully');
|
||||
|
||||
return {
|
||||
checkoutToken: response.data.attributes.url,
|
||||
@@ -75,13 +68,12 @@ export class LemonSqueezyBillingStrategyService
|
||||
) {
|
||||
const logger = await getLogger();
|
||||
|
||||
logger.info(
|
||||
{
|
||||
name: 'billing.lemon-squeezy',
|
||||
customerId: params.customerId,
|
||||
},
|
||||
'Creating billing portal session...',
|
||||
);
|
||||
const ctx = {
|
||||
name: this.namespace,
|
||||
...params,
|
||||
};
|
||||
|
||||
logger.info(ctx, 'Creating billing portal session...');
|
||||
|
||||
const { data, error } =
|
||||
await createLemonSqueezyBillingPortalSession(params);
|
||||
@@ -89,8 +81,7 @@ export class LemonSqueezyBillingStrategyService
|
||||
if (error ?? !data) {
|
||||
logger.error(
|
||||
{
|
||||
name: 'billing.lemon-squeezy',
|
||||
customerId: params.customerId,
|
||||
...ctx,
|
||||
error: error?.message,
|
||||
},
|
||||
'Failed to create billing portal session',
|
||||
@@ -99,13 +90,7 @@ export class LemonSqueezyBillingStrategyService
|
||||
throw new Error('Failed to create billing portal session');
|
||||
}
|
||||
|
||||
logger.info(
|
||||
{
|
||||
name: 'billing.lemon-squeezy',
|
||||
customerId: params.customerId,
|
||||
},
|
||||
'Billing portal session created successfully',
|
||||
);
|
||||
logger.info(ctx, 'Billing portal session created successfully');
|
||||
|
||||
return { url: data };
|
||||
}
|
||||
@@ -115,13 +100,12 @@ export class LemonSqueezyBillingStrategyService
|
||||
) {
|
||||
const logger = await getLogger();
|
||||
|
||||
logger.info(
|
||||
{
|
||||
name: 'billing.lemon-squeezy',
|
||||
subscriptionId: params.subscriptionId,
|
||||
},
|
||||
'Cancelling subscription...',
|
||||
);
|
||||
const ctx = {
|
||||
name: this.namespace,
|
||||
subscriptionId: params.subscriptionId,
|
||||
};
|
||||
|
||||
logger.info(ctx, 'Cancelling subscription...');
|
||||
|
||||
try {
|
||||
const { error } = await cancelSubscription(params.subscriptionId);
|
||||
@@ -129,8 +113,7 @@ export class LemonSqueezyBillingStrategyService
|
||||
if (error) {
|
||||
logger.error(
|
||||
{
|
||||
name: 'billing.lemon-squeezy',
|
||||
subscriptionId: params.subscriptionId,
|
||||
...ctx,
|
||||
error: error.message,
|
||||
},
|
||||
'Failed to cancel subscription',
|
||||
@@ -139,20 +122,13 @@ export class LemonSqueezyBillingStrategyService
|
||||
throw error;
|
||||
}
|
||||
|
||||
logger.info(
|
||||
{
|
||||
name: 'billing.lemon-squeezy',
|
||||
subscriptionId: params.subscriptionId,
|
||||
},
|
||||
'Subscription cancelled successfully',
|
||||
);
|
||||
logger.info(ctx, 'Subscription cancelled successfully');
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
{
|
||||
name: 'billing.lemon-squeezy',
|
||||
subscriptionId: params.subscriptionId,
|
||||
...ctx,
|
||||
error: (error as Error)?.message,
|
||||
},
|
||||
'Failed to cancel subscription',
|
||||
@@ -167,21 +143,19 @@ export class LemonSqueezyBillingStrategyService
|
||||
) {
|
||||
const logger = await getLogger();
|
||||
|
||||
logger.info(
|
||||
{
|
||||
name: 'billing.lemon-squeezy',
|
||||
sessionId: params.sessionId,
|
||||
},
|
||||
'Retrieving checkout session...',
|
||||
);
|
||||
const ctx = {
|
||||
name: this.namespace,
|
||||
sessionId: params.sessionId,
|
||||
};
|
||||
|
||||
logger.info(ctx, 'Retrieving checkout session...');
|
||||
|
||||
const { data: session, error } = await getCheckout(params.sessionId);
|
||||
|
||||
if (error ?? !session?.data) {
|
||||
logger.error(
|
||||
{
|
||||
name: 'billing.lemon-squeezy',
|
||||
sessionId: params.sessionId,
|
||||
...ctx,
|
||||
error: error?.message,
|
||||
},
|
||||
'Failed to retrieve checkout session',
|
||||
@@ -190,13 +164,7 @@ export class LemonSqueezyBillingStrategyService
|
||||
throw new Error('Failed to retrieve checkout session');
|
||||
}
|
||||
|
||||
logger.info(
|
||||
{
|
||||
name: 'billing.lemon-squeezy',
|
||||
sessionId: params.sessionId,
|
||||
},
|
||||
'Checkout session retrieved successfully',
|
||||
);
|
||||
logger.info(ctx, 'Checkout session retrieved successfully');
|
||||
|
||||
const { id, attributes } = session.data;
|
||||
|
||||
@@ -213,13 +181,12 @@ export class LemonSqueezyBillingStrategyService
|
||||
async reportUsage(params: z.infer<typeof ReportBillingUsageSchema>) {
|
||||
const logger = await getLogger();
|
||||
|
||||
logger.info(
|
||||
{
|
||||
name: 'billing.lemon-squeezy',
|
||||
subscriptionItemId: params.subscriptionItemId,
|
||||
},
|
||||
'Reporting usage...',
|
||||
);
|
||||
const ctx = {
|
||||
name: this.namespace,
|
||||
subscriptionItemId: params.subscriptionItemId,
|
||||
};
|
||||
|
||||
logger.info(ctx, 'Reporting usage...');
|
||||
|
||||
const { error } = await createUsageRecord({
|
||||
quantity: params.usage.quantity,
|
||||
@@ -230,8 +197,7 @@ export class LemonSqueezyBillingStrategyService
|
||||
if (error) {
|
||||
logger.error(
|
||||
{
|
||||
name: 'billing.lemon-squeezy',
|
||||
subscriptionItemId: params.subscriptionItemId,
|
||||
...ctx,
|
||||
error,
|
||||
},
|
||||
'Failed to report usage',
|
||||
@@ -240,13 +206,7 @@ export class LemonSqueezyBillingStrategyService
|
||||
throw new Error('Failed to report usage');
|
||||
}
|
||||
|
||||
logger.info(
|
||||
{
|
||||
name: 'billing.lemon-squeezy',
|
||||
subscriptionItemId: params.subscriptionItemId,
|
||||
},
|
||||
'Usage reported successfully',
|
||||
);
|
||||
logger.info(ctx, 'Usage reported successfully');
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
@@ -257,7 +217,7 @@ export class LemonSqueezyBillingStrategyService
|
||||
const logger = await getLogger();
|
||||
|
||||
const ctx = {
|
||||
name: 'billing.lemon-squeezy',
|
||||
name: this.namespace,
|
||||
...params,
|
||||
};
|
||||
|
||||
@@ -276,11 +236,58 @@ export class LemonSqueezyBillingStrategyService
|
||||
'Failed to update subscription',
|
||||
);
|
||||
|
||||
throw error;
|
||||
throw new Error('Failed to update subscription');
|
||||
}
|
||||
|
||||
logger.info(ctx, 'Subscription updated successfully');
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
async getPlanById(planId: string) {
|
||||
const logger = await getLogger();
|
||||
|
||||
const ctx = {
|
||||
name: this.namespace,
|
||||
planId,
|
||||
};
|
||||
|
||||
logger.info(ctx, 'Retrieving plan by ID...');
|
||||
|
||||
const { error, data } = await getVariant(planId);
|
||||
|
||||
if (error) {
|
||||
logger.error(
|
||||
{
|
||||
...ctx,
|
||||
error,
|
||||
},
|
||||
'Failed to retrieve plan by ID',
|
||||
);
|
||||
|
||||
throw new Error('Failed to retrieve plan by ID');
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
logger.error(
|
||||
{
|
||||
...ctx,
|
||||
},
|
||||
'Plan not found',
|
||||
);
|
||||
|
||||
throw new Error('Plan not found');
|
||||
}
|
||||
|
||||
logger.info(ctx, 'Plan retrieved successfully');
|
||||
|
||||
const attrs = data.data.attributes;
|
||||
|
||||
return {
|
||||
id: data.data.id,
|
||||
name: attrs.name,
|
||||
interval: attrs.interval ?? '',
|
||||
amount: attrs.price,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,6 +218,36 @@ export class StripeBillingStrategyService
|
||||
}
|
||||
}
|
||||
|
||||
async getPlanById(planId: string) {
|
||||
const logger = await getLogger();
|
||||
|
||||
const ctx = {
|
||||
name: this.namespace,
|
||||
planId,
|
||||
};
|
||||
|
||||
logger.info(ctx, 'Retrieving plan by id...');
|
||||
|
||||
const stripe = await this.stripeProvider();
|
||||
|
||||
try {
|
||||
const plan = await stripe.plans.retrieve(planId);
|
||||
|
||||
logger.info(ctx, 'Plan retrieved successfully');
|
||||
|
||||
return {
|
||||
id: plan.id,
|
||||
name: plan.nickname ?? '',
|
||||
amount: plan.amount ?? 0,
|
||||
interval: plan.interval,
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error({ ...ctx, error }, 'Failed to retrieve plan');
|
||||
|
||||
throw new Error('Failed to retrieve plan');
|
||||
}
|
||||
}
|
||||
|
||||
private async stripeProvider(): Promise<Stripe> {
|
||||
return createStripeClient();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user