Skip to content

Commit 4391513

Browse files
committed
Add Stripe checkout for Growth plan subscription
1 parent 0e3f346 commit 4391513

File tree

14 files changed

+705
-24
lines changed

14 files changed

+705
-24
lines changed

apps/dashboard/.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,5 @@ ANALYTICS_SERVICE_URL=""
6464

6565
# required for billing parts of the dashboard (team -> settings -> billing / invoices)
6666
STRIPE_SECRET_KEY=""
67+
GROWTH_PLAN_SKU=""
68+
PAYMENT_METHOD_CONFIGURATION=""

apps/dashboard/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
"@radix-ui/react-tooltip": "1.2.7",
2222
"@sentry/nextjs": "9.34.0",
2323
"@shazow/whatsabi": "0.22.2",
24+
"@stripe/react-stripe-js": "4.0.2",
25+
"@stripe/stripe-js": "7.9.0",
2426
"@tanstack/react-query": "5.81.5",
2527
"@tanstack/react-table": "^8.21.3",
2628
"@thirdweb-dev/api": "workspace:*",

apps/dashboard/src/@/actions/stripe-actions.ts

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import "server-only";
22

3+
import { headers } from "next/headers";
34
import Stripe from "stripe";
45
import type { Team } from "@/api/team/get-team";
5-
import { STRIPE_SECRET_KEY } from "@/constants/server-envs";
6+
import {
7+
GROWTH_PLAN_SKU,
8+
PAYMENT_METHOD_CONFIGURATION,
9+
STRIPE_SECRET_KEY,
10+
} from "@/constants/server-envs";
611

712
let existingStripe: Stripe | undefined;
813

@@ -72,3 +77,59 @@ export async function getStripeBalance(customerId: string) {
7277
// Stripe returns a positive balance for credits, so we need to divide by -100 to get the actual balance (as long as the balance is not 0)
7378
return customer.balance === 0 ? 0 : customer.balance / -100;
7479
}
80+
81+
export async function fetchClientSecret(team: Team) {
82+
"use server";
83+
const origin = (await headers()).get("origin");
84+
const stripe = getStripe();
85+
const customerId = team.stripeCustomerId;
86+
87+
if (!customerId) {
88+
throw new Error("No customer ID found");
89+
}
90+
91+
// Create Checkout Sessions from body params.
92+
const session = await stripe.checkout.sessions.create({
93+
ui_mode: "embedded",
94+
line_items: [
95+
{
96+
// Provide the exact Price ID (for example, price_1234) of
97+
// the product you want to sell
98+
price: GROWTH_PLAN_SKU,
99+
quantity: 1,
100+
},
101+
],
102+
mode: "subscription",
103+
104+
return_url: `${origin}/get-started/team/${team.slug}/select-plan?session_id={CHECKOUT_SESSION_ID}`,
105+
automatic_tax: { enabled: true },
106+
allow_promotion_codes: true,
107+
customer: customerId,
108+
customer_update: {
109+
address: "auto",
110+
},
111+
payment_method_collection: "always",
112+
payment_method_configuration: PAYMENT_METHOD_CONFIGURATION,
113+
subscription_data: {
114+
trial_period_days: 14,
115+
trial_settings: {
116+
end_behavior: {
117+
missing_payment_method: "cancel",
118+
},
119+
},
120+
},
121+
});
122+
123+
if (!session.client_secret) {
124+
throw new Error("No client secret found");
125+
}
126+
127+
return session.client_secret;
128+
}
129+
130+
export async function getStripeSessionById(sessionId: string) {
131+
const session = await getStripe().checkout.sessions.retrieve(sessionId, {
132+
expand: ["line_items", "payment_intent"],
133+
});
134+
return session;
135+
}

apps/dashboard/src/@/analytics/report.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,18 @@ export function reportOnboardingMembersUpsellPlanSelected(properties: {
161161
posthog.capture("onboarding members upsell plan selected", properties);
162162
}
163163

164+
/**
165+
* ### Why do we need to report this event?
166+
* - To track the number of teams that completed the team member step during onboarding
167+
*
168+
* ### Who is responsible for this event?
169+
* @jnsdls
170+
*
171+
*/
172+
export function reportTeamMemberStepCompleted() {
173+
posthog.capture("onboarding members completed");
174+
}
175+
164176
/**
165177
* ### Why do we need to report this event?
166178
* - To track the number of teams that completed onboarding

apps/dashboard/src/@/components/project/create-project-modal/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ const createProjectFormSchema = z.object({
160160

161161
type CreateProjectFormSchema = z.infer<typeof createProjectFormSchema>;
162162

163-
function CreateProjectForm(props: {
163+
export function CreateProjectForm(props: {
164164
createProject: (param: Partial<Project>) => Promise<{
165165
project: Project;
166166
secret: string;
@@ -419,7 +419,7 @@ function DomainsAlert(props: {
419419
);
420420
}
421421

422-
function CreatedProjectDetails(props: {
422+
export function CreatedProjectDetails(props: {
423423
project: Project;
424424
secret: string;
425425
onComplete: () => void;

apps/dashboard/src/@/constants/server-envs.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,24 @@ if (REDIS_URL) {
9191
REDIS_URL,
9292
);
9393
}
94+
95+
export const GROWTH_PLAN_SKU = process.env.GROWTH_PLAN_SKU || "";
96+
97+
if (GROWTH_PLAN_SKU) {
98+
experimental_taintUniqueValue(
99+
"Do not pass GROWTH_PLAN_SKU to the client",
100+
process,
101+
GROWTH_PLAN_SKU,
102+
);
103+
}
104+
105+
export const PAYMENT_METHOD_CONFIGURATION =
106+
process.env.PAYMENT_METHOD_CONFIGURATION || "";
107+
108+
if (PAYMENT_METHOD_CONFIGURATION) {
109+
experimental_taintUniqueValue(
110+
"Do not pass PAYMENT_METHOD_CONFIGURATION to the client",
111+
process,
112+
PAYMENT_METHOD_CONFIGURATION,
113+
);
114+
}

0 commit comments

Comments
 (0)