diff --git a/apps/dashboard/src/@/actions/stripe-actions.ts b/apps/dashboard/src/@/actions/stripe-actions.ts index 8cfcb20eb23..acb6761a463 100644 --- a/apps/dashboard/src/@/actions/stripe-actions.ts +++ b/apps/dashboard/src/@/actions/stripe-actions.ts @@ -1,6 +1,6 @@ import "server-only"; -import { headers } from "next/headers"; +import { cookies, headers } from "next/headers"; import Stripe from "stripe"; import type { Team } from "@/api/team/get-team"; import { @@ -88,6 +88,9 @@ export async function fetchClientSecret(team: Team) { throw new Error("No customer ID found"); } + // try to get the gclid cookie + const gclid = (await cookies()).get("gclid")?.value; + // Create Checkout Sessions from body params. const session = await stripe.checkout.sessions.create({ ui_mode: "embedded", @@ -117,6 +120,8 @@ export async function fetchClientSecret(team: Team) { missing_payment_method: "cancel", }, }, + // if gclid exists, set it as a metadata field so we can attribute the conversion later + metadata: gclid ? { gclid } : undefined, }, }); diff --git a/apps/dashboard/src/middleware.ts b/apps/dashboard/src/middleware.ts index 185b5b77c60..6dac00f4a9f 100644 --- a/apps/dashboard/src/middleware.ts +++ b/apps/dashboard/src/middleware.ts @@ -7,6 +7,20 @@ import { defineDashboardChain } from "@/lib/defineDashboardChain"; import { isLoginRequired } from "@/utils/auth"; import { LAST_VISITED_TEAM_PAGE_PATH } from "./app/(app)/team/components/last-visited-page/consts"; +type CookiesToSet = Record< + string, + | [string] + | [ + string, + { + httpOnly: boolean; + sameSite?: "lax" | "strict" | "none"; + secure: boolean; + maxAge: number; + }, + ] +>; + // ignore assets, api - only intercept page routes export const config = { matcher: [ @@ -24,12 +38,12 @@ export const config = { }; export async function middleware(request: NextRequest) { - const { pathname } = request.nextUrl; + const { pathname, searchParams } = request.nextUrl; // nebula subdomain handling const paths = pathname.slice(1).split("/"); - let cookiesToSet: Record | undefined; + let cookiesToSet: CookiesToSet = {}; const activeAccount = request.cookies.get(COOKIE_ACTIVE_ACCOUNT)?.value; const authCookie = activeAccount @@ -56,12 +70,27 @@ export async function middleware(request: NextRequest) { cookiesToSet = {}; } - cookiesToSet[key] = value; + cookiesToSet[key] = [value]; } } } } + // handle gclid (if it exists in search params) + const gclid = searchParams.get("gclid"); + if (gclid) { + cookiesToSet.gclid = [ + gclid, + { + // TODO: define conversion window, for now 7d should do fine + maxAge: 7 * 24 * 60 * 60, + httpOnly: false, + sameSite: "lax", + secure: true, + }, + ]; + } + // logged in paths if (isLoginRequired(pathname)) { // check if the user is logged in (has a valid auth cookie) @@ -146,7 +175,7 @@ export async function middleware(request: NextRequest) { if (cookiesToSet) { const defaultResponse = NextResponse.next(); for (const entry of Object.entries(cookiesToSet)) { - defaultResponse.cookies.set(entry[0], entry[1]); + defaultResponse.cookies.set(entry[0], entry[1][0], entry[1][1]); } return defaultResponse; @@ -162,7 +191,7 @@ function isPossibleAddressOrENSName(address: string) { function rewrite( request: NextRequest, relativePath: string, - cookiesToSet: Record | undefined, + cookiesToSet: CookiesToSet, ) { const url = request.nextUrl.clone(); url.pathname = relativePath; @@ -170,7 +199,7 @@ function rewrite( if (cookiesToSet) { for (const entry of Object.entries(cookiesToSet)) { - res.cookies.set(entry[0], entry[1]); + res.cookies.set(entry[0], entry[1][0], entry[1][1]); } } @@ -184,7 +213,7 @@ function redirect( | { searchParams?: string; permanent?: boolean; - cookiesToSet?: Record | undefined; + cookiesToSet?: CookiesToSet; } | undefined, ) { @@ -196,7 +225,7 @@ function redirect( if (options?.cookiesToSet) { for (const entry of Object.entries(options.cookiesToSet)) { - res.cookies.set(entry[0], entry[1]); + res.cookies.set(entry[0], entry[1][0], entry[1][1]); } }