Skip to content

Commit 4482692

Browse files
authored
Add Stripe checkout for Growth plan subscription (#8068)
1 parent 00dcc34 commit 4482692

File tree

14 files changed

+711
-49
lines changed

14 files changed

+711
-49
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 & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -81,33 +81,6 @@ export function reportOnboardingStarted() {
8181
posthog.capture("onboarding started");
8282
}
8383

84-
/**
85-
* ### Why do we need to report this event?
86-
* - To track the number of teams that select a paid plan during onboarding
87-
* - To know **which** plan was selected
88-
*
89-
* ### Who is responsible for this event?
90-
* @jnsdls
91-
*
92-
*/
93-
export function reportOnboardingPlanSelected(properties: {
94-
plan: Team["billingPlan"];
95-
}) {
96-
posthog.capture("onboarding plan selected", properties);
97-
}
98-
99-
/**
100-
* ### Why do we need to report this event?
101-
* - To track the number of teams that skip the plan-selection step during onboarding
102-
*
103-
* ### Who is responsible for this event?
104-
* @jnsdls
105-
*
106-
*/
107-
export function reportOnboardingPlanSelectionSkipped() {
108-
posthog.capture("onboarding plan selection skipped");
109-
}
110-
11184
/**
11285
* ### Why do we need to report this event?
11386
* - To track the number of teams that invite members during onboarding
@@ -161,6 +134,18 @@ export function reportOnboardingMembersUpsellPlanSelected(properties: {
161134
posthog.capture("onboarding members upsell plan selected", properties);
162135
}
163136

137+
/**
138+
* ### Why do we need to report this event?
139+
* - To track the number of teams that completed the team member step during onboarding
140+
*
141+
* ### Who is responsible for this event?
142+
* @jnsdls
143+
*
144+
*/
145+
export function reportTeamMemberStepCompleted() {
146+
posthog.capture("onboarding members completed");
147+
}
148+
164149
/**
165150
* ### Why do we need to report this event?
166151
* - To track the number of teams that completed onboarding

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)