Skip to content

Commit f5b9b6b

Browse files
authored
feat: store ads clickid when creating an account (calcom#25763)
* feat: store ads clickid when creating an account * fix: type check * fix: google campaignId not being added to stripe * remove tracking in stripe app
1 parent b664857 commit f5b9b6b

7 files changed

Lines changed: 79 additions & 19 deletions

File tree

.env.example

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -509,4 +509,7 @@ SEED_PLATFORM_OAUTH_CLIENT_SECRET=
509509
ENABLE_ASYNC_TASKER="false" # set to "true" to enable
510510
TRIGGER_SECRET_KEY=
511511
TRIGGER_API_URL=https://api.trigger.dev
512-
TRIGGER_DEV_PROJECT_REF=
512+
TRIGGER_DEV_PROJECT_REF=
513+
514+
GOOGLE_ADS_ENABLED=1 # To enable Google Ads tracking (gclid)
515+
LINKEDIN_ADS_ENABLED=1 # To enable LinkedIn Ads tracking (li_fat_id)

apps/web/lib/settings/license-key/new/getServerSideProps.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import type { GetServerSidePropsContext } from "next";
22

33
import { getServerSession } from "@calcom/feature-auth/lib/getServerSession";
44
import { getOptions } from "@calcom/feature-auth/lib/next-auth-options";
5+
import { getTrackingFromCookies } from "@calcom/lib/tracking";
56

67
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
78
const session = await getServerSession({
89
req: context.req,
910
authOptions: getOptions({
1011
getDubId: () => context.req.cookies.dub_id || context.req.cookies.dclid,
12+
getTrackingData: () => getTrackingFromCookies(context.req.cookies),
1113
}),
1214
});
1315
// Disable this check if we ever make this self serve.

apps/web/pages/api/auth/[...nextauth].ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,17 @@ import type { NextApiRequest, NextApiResponse } from "next";
22
import NextAuth from "next-auth";
33

44
import { getOptions } from "@calcom/features/auth/lib/next-auth-options";
5+
import { getTrackingFromCookies } from "@calcom/lib/tracking";
56

67
// pass req to NextAuth: https://github.com/nextauthjs/next-auth/discussions/469
78
const handler = (req: NextApiRequest, res: NextApiResponse) =>
8-
NextAuth(req, res, getOptions({ getDubId: () => req.cookies.dub_id || req.cookies.dclid }));
9+
NextAuth(
10+
req,
11+
res,
12+
getOptions({
13+
getDubId: () => req.cookies.dub_id || req.cookies.dclid,
14+
getTrackingData: () => getTrackingFromCookies(req.cookies),
15+
})
16+
);
917

1018
export default handler;

packages/app-store/stripepayment/api/subscription.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { getPremiumMonthlyPlanPriceId } from "@calcom/app-store/stripepayment/li
55
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
66
import { checkPremiumUsername } from "@calcom/features/ee/common/lib/checkPremiumUsername";
77
import { WEBAPP_URL } from "@calcom/lib/constants";
8-
import { getTrackingFromCookies } from "@calcom/lib/tracking";
98
import prisma from "@calcom/prisma";
109
import type { Prisma } from "@calcom/prisma/client";
1110

@@ -40,8 +39,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
4039
return;
4140
}
4241

43-
const tracking = getTrackingFromCookies(req.cookies);
44-
4542
const return_url = `${WEBAPP_URL}/api/integrations/stripepayment/paymentCallback?checkoutSessionId={CHECKOUT_SESSION_ID}&callbackUrl=${callbackUrl}`;
4643
const createSessionParams: Stripe.Checkout.SessionCreateParams = {
4744
mode: "subscription",
@@ -58,8 +55,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
5855
metadata: {
5956
userId: userId.toString(),
6057
intentUsername,
61-
...(tracking?.googleAds?.gclid && { gclid: tracking.googleAds.gclid, campaignId: tracking.googleAds.campaignId }),
62-
...(tracking?.linkedInAds?.liFatId && { liFatId: tracking.linkedInAds.liFatId, linkedInCampaignId: tracking.linkedInAds?.campaignId }),
6358
},
6459
};
6560

packages/features/auth/lib/next-auth-options.ts

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import GoogleProvider from "next-auth/providers/google";
1212
import { updateProfilePhotoGoogle } from "@calcom/app-store/_utils/oauth/updateProfilePhotoGoogle";
1313
import GoogleCalendarService from "@calcom/app-store/googlecalendar/lib/CalendarService";
1414
import { LicenseKeySingleton } from "@calcom/ee/common/server/LicenseKeyService";
15+
import { getBillingProviderService } from "@calcom/features/ee/billing/di/containers/Billing";
1516
import { CredentialRepository } from "@calcom/features/credentials/repositories/CredentialRepository";
17+
import type { TrackingData } from "@calcom/lib/tracking";
1618
import { DeploymentRepository } from "@calcom/features/ee/deployment/repositories/DeploymentRepository";
1719
import createUsersAndConnectToOrg from "@calcom/features/ee/dsync/lib/users/createUsersAndConnectToOrg";
1820
import ImpersonationProvider from "@calcom/features/ee/impersonation/lib/ImpersonationProvider";
@@ -456,9 +458,12 @@ const mapIdentityProvider = (providerName: string) => {
456458

457459
export const getOptions = ({
458460
getDubId,
461+
getTrackingData,
459462
}: {
460463
/** so we can extract the Dub cookie in both pages and app routers */
461464
getDubId: () => string | undefined;
465+
/** Ad tracking data for Stripe customer metadata */
466+
getTrackingData: () => TrackingData;
462467
}): AuthOptions => ({
463468
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
464469
// @ts-ignore
@@ -596,14 +601,14 @@ export const getOptions = ({
596601
org:
597602
profileOrg && !profileOrg.isPlatform
598603
? {
599-
id: profileOrg.id,
600-
name: profileOrg.name,
601-
slug: profileOrg.slug ?? profileOrg.requestedSlug ?? "",
602-
logoUrl: profileOrg.logoUrl,
603-
fullDomain: getOrgFullOrigin(profileOrg.slug ?? profileOrg.requestedSlug ?? ""),
604-
domainSuffix: subdomainSuffix(),
605-
role: orgRole as MembershipRole, // It can't be undefined if we have a profileOrg
606-
}
604+
id: profileOrg.id,
605+
name: profileOrg.name,
606+
slug: profileOrg.slug ?? profileOrg.requestedSlug ?? "",
607+
logoUrl: profileOrg.logoUrl,
608+
fullDomain: getOrgFullOrigin(profileOrg.slug ?? profileOrg.requestedSlug ?? ""),
609+
domainSuffix: subdomainSuffix(),
610+
role: orgRole as MembershipRole, // It can't be undefined if we have a profileOrg
611+
}
607612
: null,
608613
} as JWT;
609614
};
@@ -1066,11 +1071,12 @@ export const getOptions = ({
10661071
const { orgUsername, orgId } = await checkIfUserShouldBelongToOrg(idP, user.email);
10671072

10681073
try {
1074+
const newUsername = orgId ? slugify(orgUsername) : usernameSlug(user.name);
10691075
const newUser = await prisma.user.create({
10701076
data: {
10711077
// Slugify the incoming name and append a few random characters to
10721078
// prevent conflicts for users with the same name.
1073-
username: orgId ? slugify(orgUsername) : usernameSlug(user.name),
1079+
username: newUsername,
10741080
emailVerified: new Date(Date.now()),
10751081
name: user.name,
10761082
...(user.image && { avatarUrl: user.image }),
@@ -1094,6 +1100,40 @@ export const getOptions = ({
10941100
);
10951101
await calcomAdapter.linkAccount(linkAccountNewUserData);
10961102

1103+
waitUntil(
1104+
(async () => {
1105+
try {
1106+
const tracking = getTrackingData();
1107+
const billingService = getBillingProviderService();
1108+
const customer = await billingService.createCustomer({
1109+
email: newUser.email,
1110+
metadata: {
1111+
email: newUser.email,
1112+
username: newUser.username ?? newUsername,
1113+
...(tracking.googleAds?.gclid && {
1114+
gclid: tracking.googleAds.gclid,
1115+
campaignId: tracking.googleAds.campaignId,
1116+
}),
1117+
...(tracking.linkedInAds?.liFatId && {
1118+
liFatId: tracking.linkedInAds.liFatId,
1119+
linkedInCampaignId: tracking.linkedInAds.campaignId,
1120+
}),
1121+
},
1122+
});
1123+
await prisma.user.update({
1124+
where: { id: newUser.id },
1125+
data: {
1126+
metadata: {
1127+
stripeCustomerId: customer.stripeCustomerId,
1128+
},
1129+
},
1130+
});
1131+
} catch (err) {
1132+
log.error("Failed to create Stripe customer with tracking", err);
1133+
}
1134+
})()
1135+
);
1136+
10971137
if (account.twoFactorEnabled) {
10981138
return loginWithTotp(newUser.email);
10991139
} else {

packages/features/auth/signup/handlers/calcomHandler.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { HttpError } from "@calcom/lib/http-error";
1616
import logger from "@calcom/lib/logger";
1717
import type { CustomNextApiHandler } from "@calcom/lib/server/username";
1818
import { usernameHandler } from "@calcom/lib/server/username";
19+
import { getTrackingFromCookies } from "@calcom/lib/tracking";
1920
import { prisma } from "@calcom/prisma";
2021
import { CreationSource } from "@calcom/prisma/enums";
2122
import { IdentityProvider } from "@calcom/prisma/enums";
@@ -105,13 +106,24 @@ const handler: CustomNextApiHandler = async (body, usernameStatus) => {
105106
username = usernameAndEmailValidation.username;
106107
}
107108

108-
// Create the customer in Stripe
109+
// Create the customer in Stripe with ad tracking metadata
110+
const cookieStore = await cookies();
111+
const cookiesObj = Object.fromEntries(cookieStore.getAll().map((c) => [c.name, c.value]));
112+
const tracking = getTrackingFromCookies(cookiesObj);
109113

110114
const customer = await billingService.createCustomer({
111115
email,
112116
metadata: {
113117
email /* Stripe customer email can be changed, so we add this to keep track of which email was used to signup */,
114118
username,
119+
...(tracking.googleAds?.gclid && {
120+
gclid: tracking.googleAds.gclid,
121+
campaignId: tracking.googleAds.campaignId,
122+
}),
123+
...(tracking.linkedInAds?.liFatId && {
124+
liFatId: tracking.linkedInAds.liFatId,
125+
linkedInCampaignId: tracking.linkedInAds.campaignId,
126+
}),
115127
},
116128
});
117129

packages/lib/tracking/server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@ export function getTrackingFromCookies(cookies?: NextApiRequest["cookies"]): Tra
2424
if (process.env.GOOGLE_ADS_ENABLED === "1" && cookies.gclid) {
2525
tracking.googleAds = {
2626
gclid: cookies.gclid,
27-
...(cookies.gad_campaignid && { campaignId: cookies.gad_campaignid }),
27+
...(cookies.gad_campaignId && { campaignId: cookies.gad_campaignId }),
2828
};
2929
}
3030

3131
if (process.env.LINKEDIN_ADS_ENABLED === "1" && cookies.li_fat_id) {
3232
tracking.linkedInAds = {
3333
liFatId: cookies.li_fat_id,
34-
...(cookies.li_campaignid && { campaignId: cookies.li_campaignid }),
34+
...(cookies.li_campaignId && { campaignId: cookies.li_campaignId }),
3535
};
3636
}
3737

0 commit comments

Comments
 (0)