mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-15 13:55:20 +00:00
save stripe session id and add manage subscription button in settings
This commit is contained in:
parent
33ae585327
commit
e7f8f51c05
8 changed files with 79 additions and 67 deletions
|
|
@ -1,2 +0,0 @@
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "Org" ADD COLUMN "subscriptionId" TEXT;
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Org" ADD COLUMN "stripeSessionId" TEXT;
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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 />;
|
||||||
|
|
|
||||||
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue