@@ -14,6 +14,8 @@ import { POD } from '@pcd/pod'
1414import { PODData } from '@parcnet-js/podspec'
1515import { proveWalletOwnership } from './wallet-proof'
1616import { eventShops } from 'lib/components/event-schedule-new/zupass/event-shops-list'
17+ import { supabase } from 'common/supabaseClient'
18+ import { getPaidTicketsByEmail } from './pretix'
1719
1820const verifyPodSignature = ( podData : PODData ) : boolean => {
1921 try {
@@ -58,6 +60,54 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
5860 perksList . find ( perk => perk . coupon_collection === collection ) ||
5961 eventShops . find ( event => event . coupon_collection === collection )
6062
63+ // Zupass gating refers to third party event shops that use zupass to gate their event via our calendar
64+ const isZupassGating = eventShops . some ( event => event . coupon_collection === collection )
65+
66+ if ( isZupassGating && perk ?. zupass_disabled ) {
67+ console . warn ( 'ZUPASS SKIPPED, USING SUPABASE AUTH INSTEAD: ' , collection )
68+
69+ // Extract token from Authorization header
70+ const authHeader = req . headers . authorization
71+ if ( ! authHeader || ! authHeader . startsWith ( 'Bearer ' ) ) {
72+ return res . status ( 401 ) . json ( { error : 'Missing or invalid Authorization header' } )
73+ }
74+
75+ const token = authHeader . substring ( 7 ) // Remove 'Bearer ' prefix
76+
77+ // Verify supabase token
78+ const {
79+ data : { user } ,
80+ error : authError ,
81+ } = await supabase . auth . getUser ( token )
82+
83+ if ( authError || ! user ) {
84+ return res . status ( 401 ) . json ( { error : 'Invalid or expired Supabase token' } )
85+ }
86+
87+ const claimedBy = user . email
88+
89+ if ( ! claimedBy ) {
90+ return res . status ( 400 ) . json ( { error : 'User email is required' } )
91+ }
92+
93+ const tickets = await getPaidTicketsByEmail ( claimedBy )
94+
95+ const hasTickets = tickets && tickets . length > 0
96+
97+ if ( ! hasTickets ) {
98+ return res . status ( 400 ) . json ( { error : 'User does not have a paid ticket' } )
99+ }
100+
101+ const coupon = await claimSingleCoupon ( perk ! . zupass_proof_id ?? '' , collection , claimedBy )
102+
103+ return res . status ( 200 ) . json ( {
104+ coupon : coupon . coupon ,
105+ coupon_status : coupon . status ,
106+ collection,
107+ ticket_type : perk ! . zupass_proof_id ,
108+ } )
109+ }
110+
61111 if ( ! perk ) {
62112 return res . status ( 400 ) . json ( { error : 'Perk or gated event not found' } )
63113 }
@@ -130,11 +180,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
130180 }
131181
132182 const ticketId = pod . entries . ticketId ?. value ?. toString ( )
183+ const ticketOwner = pod . entries . attendeeEmail ?. value ?. toString ( )
133184
134- if ( ! ticketId ) {
135- return res . status ( 400 ) . json ( { error : 'Ticket ID is required' } )
185+ if ( ! ticketId || ! ticketOwner ) {
186+ return res . status ( 400 ) . json ( { error : 'Ticket ID and ticket owner are required' } )
136187 }
137188
189+ // If perk is same for everyone, just return it, no need to claim
138190 if ( perk . global_coupon ) {
139191 return res . status ( 200 ) . json ( {
140192 coupon : perk . global_coupon ,
@@ -144,7 +196,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
144196 } )
145197 }
146198
147- const coupon = await claimSingleCoupon ( perk . zupass_proof_id ?? '' , collection , ticketId )
199+ // If zupass gating, use ticket owner, otherwise use ticket ID. Why:
200+ // This is because the ticket id is zupass specific and not known to pretix - we are building a fallback claiming process if zupass goes down - that means we cannot claim by the ticket id found in the proof, we have to use something that works in either case (email)
201+ const claimedBy = isZupassGating ? ticketOwner : ticketId
202+ const coupon = await claimSingleCoupon ( perk . zupass_proof_id ?? '' , collection , claimedBy )
148203
149204 return res . status ( 200 ) . json ( {
150205 coupon : coupon . coupon ,
0 commit comments