feat: stripe integration

This commit is contained in:
Vasily Zubarev
2025-04-24 15:27:44 +02:00
parent 38a5c0f814
commit abd5ad8403
31 changed files with 559 additions and 112 deletions

View File

@@ -1,12 +1,6 @@
import { stripeClient } from "@better-auth/stripe/client"
import { createAuthClient } from "better-auth/client"
import { emailOTPClient } from "better-auth/client/plugins"
export const authClient = createAuthClient({
plugins: [
emailOTPClient(),
stripeClient({
subscription: true,
}),
],
plugins: [emailOTPClient()],
})

View File

@@ -1,7 +1,5 @@
import config from "@/lib/config"
import { createUserDefaults } from "@/models/defaults"
import { getSelfHostedUser, getUserByEmail, getUserById } from "@/models/users"
import { stripe } from "@better-auth/stripe"
import { User } from "@prisma/client"
import { betterAuth } from "better-auth"
import { prismaAdapter } from "better-auth/adapters/prisma"
@@ -12,7 +10,6 @@ import { headers } from "next/headers"
import { redirect } from "next/navigation"
import { prisma } from "./db"
import { resend, sendOTPCodeEmail } from "./email"
import { isStripeEnabled, stripeClient } from "./stripe"
export type UserProfile = {
id: string
@@ -21,7 +18,7 @@ export type UserProfile = {
avatar?: string
storageUsed: number
storageLimit: number
tokenBalance: number
aiBalance: number
}
export const auth = betterAuth({
@@ -47,15 +44,6 @@ export const auth = betterAuth({
generateId: false,
cookiePrefix: "taxhacker",
},
databaseHooks: {
user: {
create: {
after: async (user) => {
await createUserDefaults(user.id)
},
},
},
},
plugins: [
emailOTP({
disableSignUp: config.auth.disableSignup,
@@ -69,13 +57,6 @@ export const auth = betterAuth({
await sendOTPCodeEmail({ email, otp })
},
}),
isStripeEnabled(stripeClient)
? stripe({
stripeClient: stripeClient!,
stripeWebhookSecret: config.stripe.webhookSecret,
createCustomerOnSignUp: true,
})
: { id: "stripe", endpoints: {} },
nextCookies(), // make sure this is the last plugin in the array
],
})
@@ -113,3 +94,10 @@ export async function getCurrentUser(): Promise<User> {
// No session or user found
redirect(config.auth.loginUrl)
}
export function isSubscriptionExpired(user: User) {
if (config.selfHosted.isEnabled) {
return false
}
return user.membershipExpiresAt && user.membershipExpiresAt < new Date()
}

View File

@@ -36,6 +36,7 @@ const config = {
},
ai: {
openaiApiKey: env.OPENAI_API_KEY,
modelName: "gpt-4o-mini",
},
auth: {
secret: env.BETTER_AUTH_SECRET,
@@ -45,6 +46,8 @@ const config = {
stripe: {
secretKey: env.STRIPE_SECRET_KEY,
webhookSecret: env.STRIPE_WEBHOOK_SECRET,
paymentSuccessUrl: `${env.BASE_URL}/cloud/payment/success?session_id={CHECKOUT_SESSION_ID}`,
paymentCancelUrl: `${env.BASE_URL}/cloud`,
},
email: {
apiKey: env.RESEND_API_KEY,

View File

@@ -7,7 +7,50 @@ export const stripeClient: Stripe | null = config.stripe.secretKey
})
: null
// Type guard to check if Stripe is initialized
export const isStripeEnabled = (client: Stripe | null): client is Stripe => {
return client !== null
export type Plan = {
code: string
name: string
description: string
benefits: string[]
price: string
stripePriceId: string
limits: {
storage: number
ai: number
}
isAvailable: boolean
}
export const PLANS: Record<string, Plan> = {
unlimited: {
code: "unlimited",
name: "Unlimited",
description: "Special unlimited plan",
benefits: ["Unlimited storage", "Unlimited AI analysis", "Unlimited everything"],
price: "",
stripePriceId: "",
limits: {
storage: -1,
ai: -1,
},
isAvailable: false,
},
early: {
code: "early",
name: "Early Adopter",
description: "Special plan for our early users",
benefits: [
"512 Mb of storage",
"1000 AI file analysis",
"Unlimited transactions",
"Unlimited fields, categories and projects",
],
price: "€50/year",
stripePriceId: "price_1RGzKUPKOUEUzVB3hVyo2n57",
limits: {
storage: 512 * 1024 * 1024,
ai: 1000,
},
isAvailable: true,
},
}