export const prerender = false; import type { APIRoute } from 'astro'; import { stripe } from '../../../lib/stripe'; import { supabase, supabaseAdmin } from '../../../lib/supabase'; import { verifyAuth } from '../../../lib/auth'; import { logUserActivity } from '../../../lib/logger'; export const POST: APIRoute = async ({ request, url }) => { try { console.log('Starting hosted onboarding URL generation...'); // Verify authentication const authContext = await verifyAuth(request); if (!authContext) { console.error('Hosted onboarding failed: Unauthorized request'); return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401, headers: { 'Content-Type': 'application/json' } }); } const { user } = authContext; // Get user's organization ID first (using admin client to bypass RLS) const { data: userData, error: userError } = await (supabaseAdmin || supabase) .from('users') .select('organization_id') .eq('id', user.id) .single(); if (userError || !userData?.organization_id) { console.error('Hosted onboarding failed: Organization not found', { userId: user.id, userEmail: user.email, error: userError }); return new Response(JSON.stringify({ error: 'Organization not found' }), { status: 404, headers: { 'Content-Type': 'application/json' } }); } // Now get the organization details const { data: organization, error: orgError } = await (supabaseAdmin || supabase) .from('organizations') .select('*') .eq('id', userData.organization_id) .single(); if (orgError || !organization) { console.error('Hosted onboarding failed: Organization details not found', { userId: user.id, organizationId: userData.organization_id, error: orgError }); return new Response(JSON.stringify({ error: 'Organization details not found' }), { status: 404, headers: { 'Content-Type': 'application/json' } }); } // Check if Stripe is properly initialized if (!stripe) { console.error('Hosted onboarding failed: Stripe not configured', { userId: user.id, organizationId: organization.id, envVars: { hasSecretKey: !!process.env.STRIPE_SECRET_KEY, hasPublishableKey: !!process.env.PUBLIC_STRIPE_PUBLISHABLE_KEY } }); return new Response(JSON.stringify({ error: 'Stripe not configured' }), { status: 500, headers: { 'Content-Type': 'application/json' } }); } // Check if organization is approved for Stripe onboarding if (organization.account_status !== 'approved') { console.warn('Hosted onboarding blocked: Organization not approved', { userId: user.id, organizationId: organization.id, organizationName: organization.name, currentStatus: organization.account_status }); return new Response(JSON.stringify({ error: 'Organization must be approved before starting Stripe onboarding', account_status: organization.account_status }), { status: 403, headers: { 'Content-Type': 'application/json' } }); } let stripeAccountId = organization.stripe_account_id; // Create Stripe account if it doesn't exist if (!stripeAccountId) { console.log('Creating new Stripe Express account for hosted onboarding', { userId: user.id, organizationId: organization.id, organizationName: organization.name, businessType: organization.business_type, country: organization.country || 'US' }); const account = await stripe.accounts.create({ type: 'express', country: organization.country || 'US', email: user.email, business_type: organization.business_type === 'company' ? 'company' : 'individual', capabilities: { card_payments: { requested: true }, transfers: { requested: true } }, business_profile: { name: organization.name, url: organization.website_url || undefined, support_email: user.email, support_phone: organization.phone_number || undefined, mcc: '7922' // Event ticket agencies }, ...(organization.business_type === 'company' && { company: { name: organization.name, phone: organization.phone_number || undefined, ...(organization.address_line1 && { address: { line1: organization.address_line1, line2: organization.address_line2 || undefined, city: organization.city || undefined, state: organization.state || undefined, postal_code: organization.postal_code || undefined, country: organization.country || 'US' } }) } }), ...(organization.business_type === 'individual' && { individual: { email: user.email, phone: organization.phone_number || undefined, ...(organization.address_line1 && { address: { line1: organization.address_line1, line2: organization.address_line2 || undefined, city: organization.city || undefined, state: organization.state || undefined, postal_code: organization.postal_code || undefined, country: organization.country || 'US' } }) } }), settings: { payouts: { schedule: { interval: 'daily' } } } }); stripeAccountId = account.id; // Update organization with Stripe account ID const { error: updateError } = await (supabaseAdmin || supabase) .from('organizations') .update({ stripe_account_id: account.id, stripe_onboarding_status: 'in_progress', stripe_details_submitted: false, stripe_charges_enabled: false, stripe_payouts_enabled: false }) .eq('id', organization.id); if (updateError) { console.error('Failed to update organization with Stripe account ID', { organizationId: organization.id, stripeAccountId: account.id, error: updateError }); // Don't fail the request, just log the error } // Log the successful account creation await logUserActivity({ userId: user.id, action: 'stripe_account_created', resourceType: 'organization', resourceId: organization.id, details: { stripe_account_id: account.id, business_type: organization.business_type, country: organization.country || 'US', onboarding_type: 'hosted' } }); console.log('Stripe account created successfully for hosted onboarding', { userId: user.id, organizationId: organization.id, stripeAccountId: account.id }); } // Generate the hosted onboarding URL const baseUrl = url.origin; const returnUrl = `${baseUrl}/dashboard?stripe_onboarding=completed`; const refreshUrl = `${baseUrl}/onboarding/stripe`; console.log('Creating hosted onboarding account link', { stripeAccountId, returnUrl, refreshUrl }); const accountLink = await stripe.accountLinks.create({ account: stripeAccountId, refresh_url: refreshUrl, return_url: returnUrl, type: 'account_onboarding', collect: 'eventually_due' }); // Log the successful link generation await logUserActivity({ userId: user.id, action: 'stripe_hosted_onboarding_started', resourceType: 'organization', resourceId: organization.id, details: { stripe_account_id: stripeAccountId, return_url: returnUrl, refresh_url: refreshUrl } }); console.log('Hosted onboarding URL generated successfully', { userId: user.id, organizationId: organization.id, stripeAccountId, url: accountLink.url }); return new Response(JSON.stringify({ onboarding_url: accountLink.url, stripe_account_id: stripeAccountId, return_url: returnUrl, expires_at: accountLink.expires_at }), { status: 200, headers: { 'Content-Type': 'application/json' } }); } catch (error) { console.error('Hosted onboarding URL generation error:', error); console.error('Error details:', { message: error instanceof Error ? error.message : 'Unknown error', type: error?.constructor?.name, raw: error }); // Check if it's a Stripe error with specific details if (error && typeof error === 'object' && 'type' in error) { console.error('Stripe error type:', error.type); console.error('Stripe error code:', error.code); console.error('Stripe error param:', error.param); } return new Response(JSON.stringify({ error: 'Failed to generate onboarding URL', details: error instanceof Error ? error.message : 'Unknown error' }), { status: 500, headers: { 'Content-Type': 'application/json' } }); } };