mirror of
https://github.com/marcogll/TaxHacker_s23.git
synced 2026-01-13 13:25:18 +00:00
feat: stripe integration
This commit is contained in:
@@ -2,12 +2,12 @@
|
||||
|
||||
import { createUserDefaults, isDatabaseEmpty } from "@/models/defaults"
|
||||
import { updateSettings } from "@/models/settings"
|
||||
import { createSelfHostedUser } from "@/models/users"
|
||||
import { getOrCreateSelfHostedUser } from "@/models/users"
|
||||
import { revalidatePath } from "next/cache"
|
||||
import { redirect } from "next/navigation"
|
||||
|
||||
export async function selfHostedGetStartedAction(formData: FormData) {
|
||||
const user = await createSelfHostedUser()
|
||||
const user = await getOrCreateSelfHostedUser()
|
||||
|
||||
if (await isDatabaseEmpty(user.id)) {
|
||||
await createUserDefaults(user.id)
|
||||
|
||||
43
app/(auth)/cloud/page.tsx
Normal file
43
app/(auth)/cloud/page.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { PricingCard } from "@/components/auth/pricing-card"
|
||||
import { Card, CardContent, CardTitle } from "@/components/ui/card"
|
||||
import { ColoredText } from "@/components/ui/colored-text"
|
||||
import config from "@/lib/config"
|
||||
import { PLANS } from "@/lib/stripe"
|
||||
import Link from "next/link"
|
||||
import { redirect } from "next/navigation"
|
||||
|
||||
export default async function ChoosePlanPage() {
|
||||
if (config.selfHosted.isEnabled) {
|
||||
redirect(config.selfHosted.redirectUrl)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<Card className="w-full max-w-4xl mx-auto p-8 flex flex-col items-center justify-center gap-8">
|
||||
<CardTitle className="text-4xl font-bold text-center">
|
||||
<ColoredText>Choose your Cloud Edition plan</ColoredText>
|
||||
</CardTitle>
|
||||
<CardContent className="w-full">
|
||||
{config.auth.disableSignup ? (
|
||||
<div className="text-center text-md text-muted-foreground">
|
||||
Creating new account is disabled for now. Please use the self-hosted version.
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-wrap justify-center gap-8">
|
||||
{Object.values(PLANS)
|
||||
.filter((plan) => plan.isAvailable)
|
||||
.map((plan) => (
|
||||
<PricingCard key={plan.code} plan={plan} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
<div className="text-center text-muted-foreground">
|
||||
<Link href="mailto:me@vas3k.com" className="hover:text-primary transition-colors">
|
||||
Contact us for custom plans
|
||||
</Link>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
74
app/(auth)/cloud/payment/success/page.tsx
Normal file
74
app/(auth)/cloud/payment/success/page.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import { LoginForm } from "@/components/auth/login-form"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardTitle } from "@/components/ui/card"
|
||||
import { ColoredText } from "@/components/ui/colored-text"
|
||||
import config from "@/lib/config"
|
||||
import { PLANS, stripeClient } from "@/lib/stripe"
|
||||
import { createUserDefaults, isDatabaseEmpty } from "@/models/defaults"
|
||||
import { getOrCreateCloudUser } from "@/models/users"
|
||||
import { Cake, Ghost } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
import { redirect } from "next/navigation"
|
||||
import Stripe from "stripe"
|
||||
|
||||
export default async function CloudPaymentSuccessPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<{ session_id: string }>
|
||||
}) {
|
||||
const { session_id: sessionId } = await searchParams
|
||||
|
||||
if (!stripeClient || !sessionId) {
|
||||
redirect(config.auth.loginUrl)
|
||||
}
|
||||
|
||||
const session = await stripeClient.checkout.sessions.retrieve(sessionId)
|
||||
|
||||
if (session.mode === "subscription" && session.status === "complete") {
|
||||
const subscription = (await stripeClient.subscriptions.retrieve(
|
||||
session.subscription as string
|
||||
)) as Stripe.Subscription
|
||||
|
||||
const plan = Object.values(PLANS).find((p) => p.stripePriceId === subscription.items.data[0].price.id)
|
||||
const email = session.customer_details?.email || session.customer_email || ""
|
||||
const user = await getOrCreateCloudUser(email, {
|
||||
email: email,
|
||||
name: session.customer_details?.name || session.customer_details?.email || session.customer_email || "",
|
||||
stripeCustomerId: session.customer as string,
|
||||
membershipPlan: plan?.code,
|
||||
membershipExpiresAt: new Date(subscription.items.data[0].current_period_end * 1000),
|
||||
storageLimit: plan?.limits.storage,
|
||||
aiBalance: plan?.limits.ai,
|
||||
})
|
||||
|
||||
if (await isDatabaseEmpty(user.id)) {
|
||||
await createUserDefaults(user.id)
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="w-full max-w-xl mx-auto p-8 flex flex-col items-center justify-center gap-4">
|
||||
<Cake className="w-36 h-36" />
|
||||
<CardTitle className="text-3xl font-bold ">
|
||||
<ColoredText>Payment Successful</ColoredText>
|
||||
</CardTitle>
|
||||
<CardDescription className="text-center text-xl">You can login to your account now</CardDescription>
|
||||
<CardContent className="w-full">
|
||||
<LoginForm defaultEmail={user.email} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Card className="w-full max-w-xl mx-auto p-8 flex flex-col items-center justify-center gap-4">
|
||||
<Ghost className="w-36 h-36" />
|
||||
<CardTitle className="text-3xl font-bold ">Payment Failed</CardTitle>
|
||||
<CardDescription className="text-center text-xl">Please try again...</CardDescription>
|
||||
<CardFooter>
|
||||
<Button asChild>
|
||||
<Link href="/">Go Home</Link>
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import SignupForm from "@/components/auth/signup-form"
|
||||
import { Card, CardContent, CardTitle } from "@/components/ui/card"
|
||||
import { ColoredText } from "@/components/ui/colored-text"
|
||||
import config from "@/lib/config"
|
||||
import Image from "next/image"
|
||||
import { redirect } from "next/navigation"
|
||||
|
||||
export default async function LoginPage() {
|
||||
if (config.selfHosted.isEnabled) {
|
||||
redirect(config.selfHosted.redirectUrl)
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="w-full max-w-xl mx-auto p-8 flex flex-col items-center justify-center gap-4">
|
||||
<Image src="/logo/512.png" alt="Logo" width={144} height={144} className="w-36 h-36" />
|
||||
<CardTitle className="text-3xl font-bold ">
|
||||
<ColoredText>TaxHacker: Cloud Edition</ColoredText>
|
||||
</CardTitle>
|
||||
<CardContent className="w-full">
|
||||
{config.auth.disableSignup ? (
|
||||
<div className="text-center text-md text-muted-foreground">
|
||||
Creating new account is disabled for now. Please use the self-hosted version.
|
||||
</div>
|
||||
) : (
|
||||
<SignupForm />
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user