Skip to content

Commit 30f5cfc

Browse files
committed
integrate billing v2 init
1 parent 8734cb7 commit 30f5cfc

File tree

5 files changed

+111
-0
lines changed

5 files changed

+111
-0
lines changed

apps/dashboard/next.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ const SENTRY_OPTIONS: SentryBuildOptions = {
115115
};
116116

117117
const baseNextConfig: NextConfig = {
118+
serverExternalPackages: ["pino-pretty"],
118119
async headers() {
119120
return [
120121
{
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import "server-only";
2+
import { API_SERVER_URL, getAbsoluteUrlFromPath } from "@/constants/env";
3+
import { getAuthToken } from "../../app/api/lib/getAuthToken";
4+
5+
export async function getStripeCheckoutLink(slug: string, sku: string) {
6+
const token = await getAuthToken();
7+
8+
if (!token) {
9+
return {
10+
status: 401,
11+
link: null,
12+
};
13+
}
14+
15+
const res = await fetch(
16+
`${API_SERVER_URL}/v1/teams/${slug}/checkout/create-link`,
17+
{
18+
method: "POST",
19+
body: JSON.stringify({
20+
sku: decodeURIComponent(sku),
21+
redirectTo: getAbsoluteUrlFromPath(
22+
`/team/${slug}/~/settings/billing`,
23+
).toString(),
24+
}),
25+
headers: {
26+
"Content-Type": "application/json",
27+
Authorization: `Bearer ${token}`,
28+
},
29+
},
30+
);
31+
if (res.ok) {
32+
return {
33+
status: 200,
34+
link: (await res.json())?.result as string,
35+
} as const;
36+
}
37+
return {
38+
status: res.status,
39+
link: null,
40+
} as const;
41+
}

apps/dashboard/src/@/constants/env.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,14 @@ export const THIRDWEB_ENGINE_URL = process.env.THIRDWEB_ENGINE_URL;
3333
export const THIRDWEB_ACCESS_TOKEN = process.env.THIRDWEB_ACCESS_TOKEN;
3434
// Comma-separated list of chain IDs to disable faucet for.
3535
export const DISABLE_FAUCET_CHAIN_IDS = process.env.DISABLE_FAUCET_CHAIN_IDS;
36+
37+
export function getAbsoluteUrlFromPath(path: string) {
38+
const url = new URL(
39+
isProd
40+
? "https://thirdweb.com"
41+
: process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL || "https://thirdweb-dev.com",
42+
);
43+
44+
url.pathname = path;
45+
return url;
46+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Note: this path is meant specifically for subscribing to a plan or product, it renders no UI and instead will redirect the user to stripe UI to complete the subscription process.
2+
3+
Why have this at all? Because this way we can have a public link that will (after login complete) redirect the user to the correct subscrtiption page.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { getStripeCheckoutLink } from "@/api/team-billing";
2+
import { RedirectType, notFound, redirect } from "next/navigation";
3+
4+
interface PageParams {
5+
team_slug: string;
6+
sku: string;
7+
}
8+
9+
interface PageProps {
10+
params: Promise<PageParams>;
11+
}
12+
13+
export default async function TeamProductSkuSubscriptionRedirect(
14+
props: PageProps,
15+
) {
16+
const params = await props.params;
17+
// get the stripe checkout link for the team + sku from the API
18+
// this returns a status code and a link (if success)
19+
// 200: success
20+
// 400: invalid SKU
21+
// 401: user not authenticated
22+
// 403: user not allowed to subscribe (not admin)
23+
// 500: something random else went wrong
24+
const { link, status } = await getStripeCheckoutLink(
25+
params.team_slug,
26+
params.sku,
27+
);
28+
29+
console.log("sku", params.sku);
30+
31+
console.log("status", status);
32+
33+
if (link) {
34+
// we want to REPLACE so when the user navigates BACK the do not end up back here but on the previous page
35+
redirect(link, RedirectType.replace);
36+
}
37+
38+
switch (status) {
39+
case 400: {
40+
return <div>Invalid SKU: {params.sku}</div>;
41+
}
42+
case 401: {
43+
return <div>User not authenticated</div>;
44+
}
45+
case 403: {
46+
return <div>User not allowed to subscribe</div>;
47+
}
48+
49+
// default case
50+
default: {
51+
// todo handle this better
52+
notFound();
53+
}
54+
}
55+
}

0 commit comments

Comments
 (0)