Files
blackcanyontickets/src/pages/api/stripe/onboarding-url.ts
dzinesco 26a87d0d00 feat: Complete platform enhancement with multi-tenant architecture
Major additions:
- Territory manager system with application workflow
- Custom pricing and page builder with Craft.js
- Enhanced Stripe Connect onboarding
- CodeReadr QR scanning integration
- Kiosk mode for venue sales
- Super admin dashboard and analytics
- MCP integration for AI-powered operations

Infrastructure improvements:
- Centralized API client and routing system
- Enhanced authentication with organization context
- Comprehensive theme management system
- Advanced event management with custom tabs
- Performance monitoring and accessibility features

Database schema updates:
- Territory management tables
- Custom pages and pricing structures
- Kiosk PIN system
- Enhanced organization profiles
- CodeReadr integration tables

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-12 18:21:40 -06:00

281 lines
9.2 KiB
TypeScript

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' }
});
}
};