save stripe session id and add manage subscription button in settings

This commit is contained in:
msukkari 2025-02-11 19:06:40 -08:00
parent 33ae585327
commit e7f8f51c05
8 changed files with 79 additions and 67 deletions

View file

@ -1,2 +0,0 @@
-- AlterTable
ALTER TABLE "Org" ADD COLUMN "subscriptionId" TEXT;

View file

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Org" ADD COLUMN "stripeSessionId" TEXT;

View file

@ -105,20 +105,20 @@ model Invite {
} }
model Org { model Org {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
name String name String
domain String @unique domain String @unique
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
members UserToOrg[] members UserToOrg[]
connections Connection[] connections Connection[]
repos Repo[] repos Repo[]
secrets Secret[] secrets Secret[]
subscriptionId String? stripeSessionId String?
/// List of pending invites to this organization /// List of pending invites to this organization
invites Invite[] invites Invite[]
} }
enum OrgRole { enum OrgRole {

View file

@ -98,7 +98,7 @@ export const checkIfOrgDomainExists = async (domain: string): Promise<boolean> =
return !!org; return !!org;
} }
export const createOrg = async (name: string, domain: string, subscriptionId?: string): Promise<{ id: number } | ServiceError> => { export const createOrg = async (name: string, domain: string, stripeSessionId?: string): Promise<{ id: number } | ServiceError> => {
const session = await auth(); const session = await auth();
if (!session) { if (!session) {
return notAuthenticated(); return notAuthenticated();
@ -119,7 +119,7 @@ export const createOrg = async (name: string, domain: string, subscriptionId?: s
data: { data: {
name, name,
domain, domain,
subscriptionId, stripeSessionId,
members: { members: {
create: { create: {
userId: session.user.id, userId: session.user.id,
@ -423,3 +423,29 @@ export async function fetchStripeSession(sessionId: string) {
const stripeSession = await stripe.checkout.sessions.retrieve(sessionId); const stripeSession = await stripe.checkout.sessions.retrieve(sessionId);
return stripeSession; return stripeSession;
} }
export async function createCustomerPortalSession() {
const orgId = await getCurrentUserOrg();
if (isServiceError(orgId)) {
return orgId;
}
const org = await prisma.org.findUnique({
where: {
id: orgId,
},
});
if (!org || !org.stripeSessionId) {
return notFound();
}
const origin = (await headers()).get('origin')
const stripeSession = await fetchStripeSession(org.stripeSessionId);
const portalSession = await stripe.billingPortal.sessions.create({
customer: stripeSession.customer as string,
return_url: `${origin}/settings/billing`,
});
return portalSession;
}

View file

@ -35,14 +35,12 @@ export default async function OnboardComplete({ searchParams }: OnboardCompleteP
} }
const stripeSession = await fetchStripeSession(sessionId); const stripeSession = await fetchStripeSession(sessionId);
const stripeSubscription = stripeSession.subscription; if(stripeSession.payment_status !== "paid") {
if(stripeSession.payment_status !== "paid" || !stripeSubscription) {
console.error("Invalid stripe session"); console.error("Invalid stripe session");
return <ErrorPage />; return <ErrorPage />;
} }
const subscriptionId = stripeSubscription as string; const res = await createOrg(orgName, orgDomain, stripeSession.id);
const res = await createOrg(orgName, orgDomain, subscriptionId);
if (isServiceError(res)) { if (isServiceError(res)) {
console.error("Failed to create org"); console.error("Failed to create org");
return <ErrorPage />; return <ErrorPage />;

View file

@ -1,46 +0,0 @@
"use client"
import { useState } from "react"
import { useRouter } from "next/navigation"
import { isServiceError } from "@/lib/utils"
import { useToast } from "@/components/hooks/use-toast"
import { Button } from "@/components/ui/button"
import { createCheckoutSession } from "../../../actions"
export function CheckoutButton() {
const [isLoading, setIsLoading] = useState(false)
const router = useRouter()
const { toast } = useToast()
const handleCheckoutSession = async () => {
setIsLoading(true)
try {
const session = await createCheckoutSession()
if (isServiceError(session)) {
console.log("Failed to create checkout session: ", session)
toast({
title: "Error",
description: "Failed to create checkout session. Please try again.",
variant: "destructive",
})
} else {
router.push(session.url!)
}
} catch (error) {
console.error("Error creating checkout session:", error)
toast({
title: "Error",
description: "An unexpected error occurred. Please try again.",
variant: "destructive",
})
} finally {
setIsLoading(false)
}
}
return (
<Button onClick={handleCheckoutSession} disabled={isLoading}>
{isLoading ? "Checkout out..." : "Checkout"}
</Button>
)
}

View file

@ -0,0 +1,34 @@
"use client"
import { useState } from "react"
import { useRouter } from "next/navigation"
import { isServiceError } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { createCustomerPortalSession } from "../../../actions"
export function ManageSubscriptionButton() {
const [isLoading, setIsLoading] = useState(false)
const router = useRouter()
const redirectToCustomerPortal = async () => {
setIsLoading(true)
try {
const session = await createCustomerPortalSession()
if (isServiceError(session)) {
console.log("Failed to create portal session: ", session)
} else {
router.push(session.url!)
}
} catch (error) {
console.error("Error creating portal session:", error)
} finally {
setIsLoading(false)
}
}
return (
<Button onClick={redirectToCustomerPortal} disabled={isLoading}>
{isLoading ? "Creating customer portal..." : "Manage Subscription"}
</Button>
)
}

View file

@ -1,12 +1,12 @@
'use server' 'use server'
import { CheckoutButton } from "./checkoutButton" import { ManageSubscriptionButton } from "./manageSubscriptionButton"
export default async function BillingPage() { export default async function BillingPage() {
return ( return (
<div> <div>
<h1>Billing</h1> <h1>Billing</h1>
<CheckoutButton /> <ManageSubscriptionButton />
</div> </div>
); );
} }