Skip to content

Commit

Permalink
fix(billing): a user can add payment while upgrading their plan
Browse files Browse the repository at this point in the history
  • Loading branch information
mcstepp committed Sep 20, 2024
1 parent 2d99e07 commit 602835e
Show file tree
Hide file tree
Showing 6 changed files with 17,655 additions and 14,890 deletions.
19 changes: 13 additions & 6 deletions apps/dashboard/app/(app)/settings/billing/plans/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,18 @@ export const ChangePlanButton: React.FC<Props> = ({ workspace, newPlan, label })
},
});

const handleClick = () => {
const hasPaymentMethod = !!workspace.stripeCustomerId;
if (!hasPaymentMethod && newPlan === "pro") {
return router.push(`/settings/billing/stripe?new_plan=${newPlan}`)
}

changePlan.mutateAsync({
workspaceId: workspace.id,
plan: newPlan === "free" ? "free" : "pro",
});
};

const isSamePlan = workspace.plan === newPlan;
return (
<Dialog open={open} onOpenChange={setOpen}>
Expand Down Expand Up @@ -93,12 +105,7 @@ export const ChangePlanButton: React.FC<Props> = ({ workspace, newPlan, label })
<Button
className="col-span-1"
variant="primary"
onClick={() =>
changePlan.mutateAsync({
workspaceId: workspace.id,
plan: newPlan === "free" ? "free" : "pro",
})
}
onClick={handleClick}
>
{changePlan.isLoading ? <Loading /> : "Switch"}
</Button>
Expand Down
16 changes: 14 additions & 2 deletions apps/dashboard/app/(app)/settings/billing/stripe/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@ import { headers } from "next/headers";
import { redirect } from "next/navigation";
import Stripe from "stripe";

export default async function StripeRedirect() {
type Props = {
searchParams: {
new_plan: "free" | "pro" | undefined;
};
};

export default async function StripeRedirect(props: Props) {
const { new_plan } = props.searchParams;
const tenantId = getTenantId();
if (!tenantId) {
return redirect("/auth/sign-in");
Expand Down Expand Up @@ -53,7 +60,12 @@ export default async function StripeRedirect() {
const baseUrl = process.env.VERCEL_URL ? "https://app.unkey.com" : "http://localhost:3000";

// do not use `new URL(...).searchParams` here, because it will escape the curly braces and stripe will not replace them with the session id
const successUrl = `${baseUrl}/settings/billing/stripe/success?session_id={CHECKOUT_SESSION_ID}`;
let successUrl = `${baseUrl}/settings/billing/stripe/success?session_id={CHECKOUT_SESSION_ID}`;

// if they're coming from the change plan flow, pass along the new plan param
if (new_plan && new_plan !== ws.plan) {
successUrl += `&new_plan=${new_plan}`;
}

const cancelUrl = headers().get("referer") ?? baseUrl;
const session = await stripe.checkout.sessions.create({
Expand Down
18 changes: 16 additions & 2 deletions apps/dashboard/app/(app)/settings/billing/stripe/success/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@ import { Code } from "@/components/ui/code";
import { getTenantId } from "@/lib/auth";
import { db, eq, schema } from "@/lib/db";
import { stripeEnv } from "@/lib/env";
import { PostHogClient } from "@/lib/posthog";
import { currentUser } from "@clerk/nextjs";
import { redirect } from "next/navigation";
import Stripe from "stripe";

type Props = {
searchParams: {
session_id: string;
new_plan: "free" | "pro" | undefined;
};
};

export default async function StripeSuccess(props: Props) {
const { session_id, new_plan } = props.searchParams;
const tenantId = getTenantId();
if (!tenantId) {
return redirect("/auth/sign-in");
Expand Down Expand Up @@ -44,13 +47,13 @@ export default async function StripeSuccess(props: Props) {
typescript: true,
});

const session = await stripe.checkout.sessions.retrieve(props.searchParams.session_id);
const session = await stripe.checkout.sessions.retrieve(session_id);
if (!session) {
return (
<EmptyPlaceholder>
<EmptyPlaceholder.Title>Stripe session not found</EmptyPlaceholder.Title>
<EmptyPlaceholder.Description>
The Stripe session <Code>{props.searchParams.session_id}</Code> you are trying to access
The Stripe session <Code>{session_id}</Code> you are trying to access
does not exist. Please contact [email protected].
</EmptyPlaceholder.Description>
</EmptyPlaceholder>
Expand All @@ -69,14 +72,25 @@ export default async function StripeSuccess(props: Props) {
);
}

const isChangingPlan = new_plan && new_plan !== ws.plan;

await db
.update(schema.workspaces)
.set({
stripeCustomerId: customer.id,
stripeSubscriptionId: session.subscription as string,
trialEnds: null,
...(isChangingPlan ? {plan: new_plan} :{}),
})
.where(eq(schema.workspaces.id, ws.id));

if (isChangingPlan) {
PostHogClient.capture({
distinctId: tenantId,
event: 'plan_changed',
properties: { plan: new_plan, workspace: ws.id }
});
}

return redirect("/settings/billing");
}
29 changes: 29 additions & 0 deletions apps/dashboard/lib/posthog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { PostHog } from 'posthog-node'

class PostHogClientWrapper {
private static instance: PostHog | null = null;

private constructor() {}

public static getInstance(): PostHog {
if (!PostHogClientWrapper.instance) {
if (!process.env.NEXT_PUBLIC_POSTHOG_KEY || !process.env.NEXT_PUBLIC_POSTHOG_HOST) {
console.warn('PostHog key is missing. Analytics data will not be sent.');
// Return a mock client when the key is not present
PostHogClientWrapper.instance = {
capture: () => {},
// Add other methods from PostHog, implementing them as no-ops
} as unknown as PostHog;
} else {
PostHogClientWrapper.instance = new PostHog(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
flushAt: 1,
flushInterval: 0
});
}
}
return PostHogClientWrapper.instance;
}
}

export const PostHogClient = PostHogClientWrapper.getInstance();
1 change: 1 addition & 0 deletions apps/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
"postcss": "8.4.38",
"postcss-focus-visible": "^9.0.1",
"posthog-js": "^1.130.1",
"posthog-node": "^4.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.51.3",
Expand Down
Loading

0 comments on commit 602835e

Please sign in to comment.