function getSupabaseConfig(env) { const url = env.SUPABASE_URL || ''; const publicKey = env.SUPABASE_ANON_KEY || env.SUPABASE_PUBLISHABLE_KEY || ''; const serviceKey = env.SUPABASE_SERVICE_ROLE_KEY || env.SUPABASE_SECRET_KEY || ''; if (!url || !publicKey || !serviceKey) { return null; } return { url: url.replace(/\/+$/, ''), publicKey, serviceKey }; } function getBearerToken(request) { const auth = request.headers.get('authorization') || ''; if (!auth.toLowerCase().startsWith('bearer ')) return ''; return auth.slice(7).trim(); } async function supabaseFetch(config, path, options = {}) { const isOpaqueSecretKey = config.serviceKey.startsWith('sb_secret_'); const authHeader = isOpaqueSecretKey ? {} : { authorization: `Bearer ${config.serviceKey}` }; const response = await fetch(`${config.url}${path}`, { ...options, headers: { apikey: config.serviceKey, ...authHeader, 'content-type': 'application/json', ...(options.headers || {}) } }); if (!response.ok) { const detail = await response.text(); throw new Error(`Supabase request failed ${response.status}: ${detail}`); } if (response.status === 204) return null; return response.json(); } async function getSupabaseUser(env, request) { const config = getSupabaseConfig(env); const token = getBearerToken(request); if (!config || !token) return null; const response = await fetch(`${config.url}/auth/v1/user`, { headers: { apikey: config.publicKey, authorization: `Bearer ${token}` } }); if (!response.ok) return null; return response.json(); } function normalizeEmail(value) { return String(value || '').trim().toLowerCase(); } function parsePartnerEmail(partnerDonationId, payload = {}) { const directEmail = normalizeEmail(payload.email || payload.user_email || payload.donor?.email || payload.donation?.email); if (directEmail.includes('@')) return directEmail; const raw = String(partnerDonationId || '').trim(); if (raw.includes('@')) { return normalizeEmail(raw.split('|')[0].split('::')[0]); } return ''; } async function getUserProfileByEmail(env, email) { const config = getSupabaseConfig(env); const normalizedEmail = normalizeEmail(email); if (!config || !normalizedEmail) return null; const rows = await supabaseFetch( config, `/rest/v1/user_profiles?email=eq.${encodeURIComponent(normalizedEmail)}&select=id,user_id,email,session_expiry&limit=1` ); return Array.isArray(rows) ? rows[0] || null : null; } async function upsertSoulwallSession(env, { email, userId = null, expiresAt, source, transactionId }) { const config = getSupabaseConfig(env); const normalizedEmail = normalizeEmail(email); if (!config || !normalizedEmail || !expiresAt) return null; const payload = { email: normalizedEmail, session_expiry: expiresAt, soulwall_source: source, soulwall_transaction_id: transactionId, updated_at: new Date().toISOString() }; if (userId) payload.user_id = userId; const rows = await supabaseFetch(config, '/rest/v1/user_profiles?on_conflict=email', { method: 'POST', headers: { prefer: 'resolution=merge-duplicates,return=representation' }, body: JSON.stringify(payload) }); return Array.isArray(rows) ? rows[0] || null : null; } async function createDonationIntent(env, { user, nonprofitSlug = '', provider = 'everyorg', externalId = '', partnerDonationId = '' }) { const config = getSupabaseConfig(env); const email = normalizeEmail(user?.email); if (!config || !user?.id || !email) return null; const uniqueIntentId = crypto.randomUUID(); const payload = { unique_intent_id: uniqueIntentId, partner_donation_id: partnerDonationId || uniqueIntentId, user_id: user.id, email, nonprofit_slug: nonprofitSlug, status: 'pending', provider, updated_at: new Date().toISOString() }; if (externalId) payload.external_id = externalId; const rows = await supabaseFetch(config, '/rest/v1/donation_intents', { method: 'POST', headers: { prefer: 'return=representation' }, body: JSON.stringify(payload) }); return Array.isArray(rows) ? rows[0] || null : null; } async function getDonationIntentById(env, uniqueIntentId) { const config = getSupabaseConfig(env); const id = String(uniqueIntentId || '').trim(); if (!config || !id) return null; const rows = await supabaseFetch( config, `/rest/v1/donation_intents?unique_intent_id=eq.${encodeURIComponent(id)}&select=*&limit=1` ); return Array.isArray(rows) ? rows[0] || null : null; } async function getDonationIntentByExternalId(env, externalId) { const config = getSupabaseConfig(env); const id = String(externalId || '').trim(); if (!config || !id) return null; const rows = await supabaseFetch( config, `/rest/v1/donation_intents?external_id=eq.${encodeURIComponent(id)}&select=*&limit=1` ); return Array.isArray(rows) ? rows[0] || null : null; } async function updateDonationIntentExternalId(env, { uniqueIntentId, provider, externalId }) { const config = getSupabaseConfig(env); const id = String(uniqueIntentId || '').trim(); const external = String(externalId || '').trim(); if (!config || !id || !external) return null; const payload = { external_id: external, updated_at: new Date().toISOString() }; if (provider) payload.provider = provider; const rows = await supabaseFetch( config, `/rest/v1/donation_intents?unique_intent_id=eq.${encodeURIComponent(id)}`, { method: 'PATCH', headers: { prefer: 'return=representation' }, body: JSON.stringify(payload) } ); return Array.isArray(rows) ? rows[0] || null : null; } async function markDonationIntentSucceeded(env, { uniqueIntentId, transactionId, accessExpiresAt, provider = '', externalId = '' }) { const config = getSupabaseConfig(env); const id = String(uniqueIntentId || '').trim(); if (!config || !id) return null; const payload = { status: 'succeeded', transaction_id: transactionId, access_expires_at: accessExpiresAt, updated_at: new Date().toISOString() }; if (provider) payload.provider = provider; if (externalId) payload.external_id = externalId; const rows = await supabaseFetch( config, `/rest/v1/donation_intents?unique_intent_id=eq.${encodeURIComponent(id)}`, { method: 'PATCH', headers: { prefer: 'return=representation' }, body: JSON.stringify(payload) } ); return Array.isArray(rows) ? rows[0] || null : null; } async function createHardshipRequest(env, { user, reason = '', note = '' }) { const config = getSupabaseConfig(env); const email = normalizeEmail(user?.email); if (!config || !user?.id || !email) return null; const rows = await supabaseFetch(config, '/rest/v1/hardship_requests', { method: 'POST', headers: { prefer: 'return=representation' }, body: JSON.stringify({ user_id: user.id, email, reason, note, status: 'pending', updated_at: new Date().toISOString() }) }); return Array.isArray(rows) ? rows[0] || null : null; } export { createDonationIntent, createHardshipRequest, getDonationIntentById, getDonationIntentByExternalId, getSupabaseConfig, getSupabaseUser, getUserProfileByEmail, markDonationIntentSucceeded, parsePartnerEmail, supabaseFetch, updateDonationIntentExternalId, upsertSoulwallSession };