11/**
22 * POST /api/attribution
33 *
4- * Automatic UTM-based referral attribution for new signups .
4+ * Automatic UTM-based referral attribution.
55 *
6- * Reads the `sim_utm` cookie (set by proxy on auth pages), verifies the user
7- * account was created after the cookie was set, matches a campaign by UTM
8- * specificity, and atomically inserts an attribution record + applies bonus credits.
6+ * Reads the `sim_utm` cookie (set by proxy on auth pages), matches a campaign
7+ * by UTM specificity, and atomically inserts an attribution record + applies
8+ * bonus credits.
99 *
1010 * Idempotent — the unique constraint on `userId` prevents double-attribution.
1111 */
1212
1313import { db } from '@sim/db'
14- import { referralAttribution , referralCampaigns , user , userStats } from '@sim/db/schema'
14+ import { referralAttribution , referralCampaigns , userStats } from '@sim/db/schema'
1515import { createLogger } from '@sim/logger'
1616import { eq } from 'drizzle-orm'
1717import { nanoid } from 'nanoid'
@@ -24,7 +24,6 @@ import { applyBonusCredits } from '@/lib/billing/credits/bonus'
2424const logger = createLogger ( 'AttributionAPI' )
2525
2626const COOKIE_NAME = 'sim_utm'
27- const CLOCK_DRIFT_TOLERANCE_MS = 60 * 1000
2827
2928const UtmCookieSchema = z . object ( {
3029 utm_source : z . string ( ) . optional ( ) ,
@@ -33,7 +32,7 @@ const UtmCookieSchema = z.object({
3332 utm_content : z . string ( ) . optional ( ) ,
3433 referrer_url : z . string ( ) . optional ( ) ,
3534 landing_page : z . string ( ) . optional ( ) ,
36- created_at : z . string ( ) . min ( 1 ) ,
35+ created_at : z . string ( ) . optional ( ) ,
3736} )
3837
3938/**
@@ -114,34 +113,6 @@ export async function POST() {
114113 return NextResponse . json ( { attributed : false , reason : 'invalid_cookie' } )
115114 }
116115
117- const cookieCreatedAt = Number ( utmData . created_at )
118- if ( ! Number . isFinite ( cookieCreatedAt ) ) {
119- logger . warn ( 'UTM cookie has invalid created_at timestamp' , { userId : session . user . id } )
120- cookieStore . delete ( COOKIE_NAME )
121- return NextResponse . json ( { attributed : false , reason : 'invalid_cookie' } )
122- }
123-
124- const userRows = await db
125- . select ( { createdAt : user . createdAt } )
126- . from ( user )
127- . where ( eq ( user . id , session . user . id ) )
128- . limit ( 1 )
129-
130- if ( userRows . length === 0 ) {
131- return NextResponse . json ( { error : 'User not found' } , { status : 404 } )
132- }
133-
134- const userCreatedAt = userRows [ 0 ] . createdAt . getTime ( )
135- if ( userCreatedAt < cookieCreatedAt - CLOCK_DRIFT_TOLERANCE_MS ) {
136- logger . info ( 'User account predates UTM cookie, skipping attribution' , {
137- userId : session . user . id ,
138- userCreatedAt : new Date ( userCreatedAt ) . toISOString ( ) ,
139- cookieCreatedAt : new Date ( cookieCreatedAt ) . toISOString ( ) ,
140- } )
141- cookieStore . delete ( COOKIE_NAME )
142- return NextResponse . json ( { attributed : false , reason : 'account_predates_cookie' } )
143- }
144-
145116 const matchedCampaign = await findMatchingCampaign ( utmData )
146117 if ( ! matchedCampaign ) {
147118 cookieStore . delete ( COOKIE_NAME )
0 commit comments