Refactor and improve billing module

The billing module has been refined and enhanced to include deeper validation and detailing of billing plans and products. The checkout session creation process was revised to handle more complex scenarios, incorporating better parsing and validation. Additional validations were added for the plan and product schemas, improving product details extraction, and rearranging of module exports was made for better organization. The code refactor allows easier future modifications and upgrades for recurring and one-time payments with nuanced product configurations.
This commit is contained in:
giancarlo
2024-03-27 21:06:34 +08:00
parent 7579ee9a2c
commit c3a4a05b22
22 changed files with 578 additions and 225 deletions

View File

@@ -54,12 +54,13 @@ export function PersonalAccountCheckoutForm() {
<PlanPicker
pending={pending}
config={billingConfig}
onSubmit={({ planId }) => {
onSubmit={({ planId, productId }) => {
startTransition(async () => {
try {
const { checkoutToken } =
await createPersonalAccountCheckoutSession({
planId,
productId,
});
setCheckoutToken(checkoutToken);

View File

@@ -4,7 +4,7 @@ import { redirect } from 'next/navigation';
import { z } from 'zod';
import { getProductPlanPairFromId } from '@kit/billing';
import { getLineItemsFromPlanId } from '@kit/billing';
import { getBillingGatewayProvider } from '@kit/billing-gateway';
import { Logger } from '@kit/shared/logger';
import { requireAuth } from '@kit/supabase/require-auth';
@@ -22,6 +22,7 @@ import pathsConfig from '~/config/paths.config';
*/
export async function createPersonalAccountCheckoutSession(params: {
planId: string;
productId: string;
}) {
const client = getSupabaseServerActionClient();
const { data, error } = await requireAuth(client);
@@ -30,21 +31,22 @@ export async function createPersonalAccountCheckoutSession(params: {
throw new Error('Authentication required');
}
const planId = z.string().min(1).parse(params.planId);
const { planId, productId } = z
.object({
planId: z.string().min(1),
productId: z.string().min(1),
})
.parse(params);
Logger.info(
{
planId,
productId,
},
`Creating checkout session for plan ID`,
);
const service = await getBillingGatewayProvider(client);
const productPlanPairFromId = getProductPlanPairFromId(billingConfig, planId);
if (!productPlanPairFromId) {
throw new Error('Product not found');
}
// in the case of personal accounts
// the account ID is the same as the user ID
@@ -57,16 +59,21 @@ export async function createPersonalAccountCheckoutSession(params: {
// (eg. if the account has been billed before)
const customerId = await getCustomerIdFromAccountId(accountId);
// retrieve the product and plan from the billing configuration
const { product, plan } = productPlanPairFromId;
const product = billingConfig.products.find((item) => item.id === productId);
if (!product) {
throw new Error('Product not found');
}
const { lineItems, trialDays } = getLineItemsFromPlanId(product, planId);
// call the payment gateway to create the checkout session
const { checkoutToken } = await service.createCheckoutSession({
paymentType: product.paymentType,
lineItems,
returnUrl,
accountId,
planId,
trialPeriodDays: plan.trialPeriodDays,
trialDays,
paymentType: product.paymentType,
customerEmail: data.user.email,
customerId,
});