Skip to content

Commit

Permalink
feat: fix billing
Browse files Browse the repository at this point in the history
  • Loading branch information
benjaminshafii committed Dec 4, 2024
1 parent b258224 commit 4314b55
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 144 deletions.
18 changes: 2 additions & 16 deletions web/app/api/webhook/handlers/payment-intent-succeeded.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,10 @@
import { createWebhookHandler } from '../handler-factory';
import { CustomerData, WebhookEvent, WebhookHandlerResponse } from "../types";
import { CustomerData, } from "../types";
import { db, UserUsageTable } from "@/drizzle/schema";
import { eq, sql } from "drizzle-orm";
import { sql } from "drizzle-orm";
import { updateUserSubscriptionData } from "../utils";
import Stripe from "stripe";
import { trackLoopsEvent } from '@/lib/services/loops';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: "2024-06-20",
});

async function getCustomerEmail(customerId: string): Promise<string> {
try {
const customer = await stripe.customers.retrieve(customerId) as Stripe.Customer;
return typeof customer === 'string' ? '' : customer.email || '';
} catch (error) {
console.error("Error retrieving customer email:", error);
return '';
}
}

async function handleTopUp(userId: string, tokens: number,) {
console.log("Handling top-up for user", userId, "with", tokens, "tokens");
Expand Down
1 change: 0 additions & 1 deletion web/app/api/webhook/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { handleInvoicePaid } from "./handlers/invoice-paid";
import { handlePaymentIntentSucceeded } from "./handlers/payment-intent-succeeded";
import { handleInvoicePaymentFailed } from "./handlers/invoice-payment-failed";
import { validateWebhookMetadata } from "@/srm.config";
import { WebhookEvent } from "./types";

const HANDLERS = {
"checkout.session.completed": handleCheckoutComplete,
Expand Down
141 changes: 75 additions & 66 deletions web/app/dashboard/pricing/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { auth } from "@clerk/nextjs/server";
import { headers } from "next/headers";
import { redirect } from "next/navigation";
import Stripe from "stripe";
import { PRODUCTS, PRICES } from "../../../srm.config";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: "2024-06-20",
Expand All @@ -22,29 +23,33 @@ const getUrls = () => {
export async function createOneTimePaymentCheckout() {
const { userId } = auth();
if (!userId) throw new Error("Not authenticated");

const metadata = {
userId,
type: PRODUCTS.Lifetime.metadata.type,
};

const { success, cancel, lifetime } = getUrls();

const session = await stripe.checkout.sessions.create({
mode: "payment",
payment_method_types: ["card"],
metadata: {
userId,
type: "lifetime",
},
line_items: [{
price_data: {
currency: "usd",
product_data: {
name: "Lifetime License",
metadata: {
type: "lifetime",
metadata,
line_items: [
{
price_data: {
currency: "usd",
product_data: {
name: PRODUCTS.Lifetime.name,
metadata: PRODUCTS.Lifetime.metadata,
},
unit_amount: PRICES.LIFETIME,
},
unit_amount: 19900, // $199.00
quantity: 1,
},
quantity: 1,
}],
],
payment_intent_data: {
metadata,
},
success_url: lifetime,
cancel_url: cancel,
allow_promotion_codes: true,
Expand All @@ -58,32 +63,38 @@ export async function createSubscriptionCheckout() {
if (!userId) throw new Error("Not authenticated");

const { success, cancel } = getUrls();
const metadata = {
userId,
type: PRODUCTS.HobbyMonthly.metadata.type,
plan: PRODUCTS.HobbyMonthly.metadata.plan,
};

const session = await stripe.checkout.sessions.create({
mode: "subscription",
payment_method_types: ["card"],
metadata: {
userId,
type: "subscription",
plan: "monthly",
metadata,
payment_intent_data: {
metadata,
},
line_items: [{
price_data: {
currency: "usd",
product_data: {
name: "Monthly Subscription",
metadata: {
type: "subscription",
plan: "monthly",
line_items: [
{
price_data: {
currency: "usd",
product_data: {
name: PRODUCTS.HobbyMonthly.name,
metadata: PRODUCTS.HobbyMonthly.metadata,
},
unit_amount: PRICES.MONTHLY,
recurring: {
interval: "month",
},
},
unit_amount: 1500, // $15.00
recurring: {
interval: "month",
},
quantity: 1,
},
quantity: 1,
}],
],
subscription_data: {
metadata,
},
success_url: success,
cancel_url: cancel,
allow_promotion_codes: true,
Expand All @@ -99,35 +110,34 @@ export async function createYearlySubscriptionCheckout() {
const { success, cancel } = getUrls();

const session = await stripe.checkout.sessions.create({
mode: "subscription",
mode: "subscription",
payment_method_types: ["card"],
metadata: {
userId,
type: "subscription",
plan: "yearly",
type: PRODUCTS.HobbyYearly.metadata.type,
plan: PRODUCTS.HobbyYearly.metadata.plan,
},
line_items: [{
price_data: {
currency: "usd",
product_data: {
name: "Yearly Subscription",
metadata: {
type: "subscription",
plan: "yearly",
line_items: [
{
price_data: {
currency: "usd",
product_data: {
name: PRODUCTS.HobbyYearly.name,
metadata: PRODUCTS.HobbyYearly.metadata,
},
unit_amount: PRICES.YEARLY,
recurring: {
interval: "year",
},
},
unit_amount: 9900, // $99.00
recurring: {
interval: "year",
},
quantity: 1,
},
quantity: 1,
}],
],
success_url: success,
cancel_url: cancel,
allow_promotion_codes: true,
subscription_data: {
trial_period_days: 7,
trial_period_days: PRODUCTS.HobbyYearly.prices.yearly.trialPeriodDays,
},
});

Expand All @@ -137,35 +147,34 @@ export async function createYearlySubscriptionCheckout() {
export async function createOneYearCheckout() {
const { userId } = auth();
if (!userId) throw new Error("Not authenticated");

const { success, cancel } = getUrls();

const session = await stripe.checkout.sessions.create({
mode: "payment",
payment_method_types: ["card"],
metadata: {
userId,
type: "lifetime",
plan: "one_year",
type: PRODUCTS.OneYear.metadata.type,
plan: PRODUCTS.OneYear.metadata.plan,
},
line_items: [{
price_data: {
currency: "usd",
product_data: {
name: "One Year License",
metadata: {
type: "lifetime",
plan: "one_year",
line_items: [
{
price_data: {
currency: "usd",
product_data: {
name: PRODUCTS.OneYear.name,
metadata: PRODUCTS.OneYear.metadata,
},
unit_amount: PRICES.ONE_YEAR,
},
unit_amount: 20000, // $200.00
quantity: 1,
},
quantity: 1,
}],
],
success_url: success,
cancel_url: cancel,
allow_promotion_codes: true,
});

redirect(session.url!);
}
}
61 changes: 0 additions & 61 deletions web/srm.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,67 +107,6 @@ export const getTargetUrl = () => {
}
return "localhost:3000";
};

// Stripe session creator helper
export const createStripeSession = async (
stripe: Stripe,
{
userId,
productKey,
successUrl,
cancelUrl,
}: {
userId: string;
productKey: keyof typeof PRODUCTS;
successUrl: string;
cancelUrl: string;
}
) => {
const product = PRODUCTS[productKey];
const priceKey = Object.keys(product.prices)[0];
const price = product.prices[priceKey];

const isSubscription = price.type === "recurring";

const session = await stripe.checkout.sessions.create({
mode: isSubscription ? "subscription" : "payment",
payment_method_types: ["card"],
metadata: {
userId,
...product.metadata,
},
line_items: [
{
price_data: {
currency: "usd",
product_data: {
name: product.name,
metadata: product.metadata,
},
unit_amount: price.amount,
...(isSubscription && {
recurring: {
interval: price.interval,
},
}),
},
quantity: 1,
},
],
success_url: successUrl,
cancel_url: cancelUrl,
allow_promotion_codes: true,
...(isSubscription &&
price.trialPeriodDays && {
subscription_data: {
trial_period_days: price.trialPeriodDays,
},
}),
});

return session;
};

// Webhook configuration
export const webhookConfig = {
events: [
Expand Down

0 comments on commit 4314b55

Please sign in to comment.