diff --git a/src/app/api/admin/setup-polar-webhook/route.ts b/src/app/api/admin/setup-polar-webhook/route.ts new file mode 100644 index 0000000..ba72732 --- /dev/null +++ b/src/app/api/admin/setup-polar-webhook/route.ts @@ -0,0 +1,99 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { polar } from '@/lib/polar'; + +/** + * Admin endpoint to set up webhook endpoint in Polar + * This configures Polar to send webhook events to our application + */ +export async function POST(request: NextRequest) { + try { + const { adminKey } = await request.json(); + + // Simple admin key check for security + if (adminKey !== process.env.ADMIN_RECOVERY_KEY) { + return NextResponse.json( + { error: 'Unauthorized - Invalid admin key' }, + { status: 401 } + ); + } + + console.log('🔄 Admin setting up Polar webhook endpoint...'); + + const webhookUrl = `${process.env.NEXT_PUBLIC_URL}/api/webhooks/polar`; + console.log(`📡 Webhook URL: ${webhookUrl}`); + + // List existing webhook endpoints + console.log('🔍 Checking for existing webhook endpoints...'); + const existingWebhooks = await polar.webhooks.list({ + limit: 100 + }); + + const webhooksList = []; + for await (const webhook of existingWebhooks) { + webhooksList.push(webhook); + } + + console.log(`📋 Found ${webhooksList.length} existing webhook(s)`); + + // Check if we already have a webhook for our URL + const existingWebhook = webhooksList.find(webhook => webhook.url === webhookUrl); + + if (existingWebhook) { + console.log('✅ Webhook already exists for our URL'); + return NextResponse.json({ + success: true, + message: 'Webhook already configured', + webhook: { + id: existingWebhook.id, + url: existingWebhook.url, + events: existingWebhook.events, + hasSecret: !!existingWebhook.secret + }, + existing: true + }); + } + + // Create new webhook endpoint + console.log('🚀 Creating new webhook endpoint...'); + + const webhook = await polar.webhooks.create({ + url: webhookUrl, + secret: process.env.POLAR_WEBHOOK_SECRET, + events: [ + 'subscription.created', + 'subscription.updated', + 'subscription.canceled', + 'subscription.uncanceled', + 'subscription.revoked', + 'customer.created', + 'customer.updated', + 'checkout.created', + 'checkout.updated', + 'order.created' + ] + }); + + console.log('✅ Webhook endpoint created successfully!'); + + return NextResponse.json({ + success: true, + message: 'Webhook endpoint created successfully', + webhook: { + id: webhook.id, + url: webhook.url, + events: webhook.events, + hasSecret: !!webhook.secret + }, + existing: false + }); + + } catch (error) { + console.error('❌ Failed to set up Polar webhook:', error); + + return NextResponse.json({ + success: false, + error: 'Failed to set up webhook', + details: error instanceof Error ? error.message : String(error) + }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/admin/trigger-webhook-by-email/route.ts b/src/app/api/admin/trigger-webhook-by-email/route.ts new file mode 100644 index 0000000..d634760 --- /dev/null +++ b/src/app/api/admin/trigger-webhook-by-email/route.ts @@ -0,0 +1,166 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { polar } from '@/lib/polar'; +import { db } from '@/db/connection'; +import { user } from '@/db/schema/auth'; +import { eq } from 'drizzle-orm'; + +/** + * Admin endpoint to manually trigger webhook processing for a user by email + * Used for recovery when webhooks fail + */ +export async function POST(request: NextRequest) { + try { + const { userEmail, adminKey } = await request.json(); + + // Simple admin key check for security + if (adminKey !== process.env.ADMIN_RECOVERY_KEY) { + return NextResponse.json( + { error: 'Unauthorized - Invalid admin key' }, + { status: 401 } + ); + } + + if (!userEmail) { + return NextResponse.json( + { error: 'Email required' }, + { status: 400 } + ); + } + + console.log(`🔄 Admin triggering webhook processing for email: ${userEmail}`); + + // 1. Find the user in our database + const [dbUser] = await db + .select() + .from(user) + .where(eq(user.email, userEmail)) + .limit(1); + + if (!dbUser) { + return NextResponse.json({ + success: false, + error: 'User not found in database' + }); + } + + console.log(`✅ Found user in database: ${dbUser.id}`); + + // 2. Find the customer in Polar + const customersResponse = await polar.customers.list({ + email: userEmail, + limit: 10 + }); + + const customers = customersResponse.items || []; + if (customers.length === 0) { + return NextResponse.json({ + success: false, + error: 'No customer found in Polar with this email' + }); + } + + const customer = customers[0]; + console.log(`✅ Found Polar customer: ${customer.id}`); + + // 3. Get active subscriptions for this customer + const subscriptionsResponse = await polar.subscriptions.list({ + customerId: customer.id, + limit: 10 + }); + + const polarSubscriptions = subscriptionsResponse.items || []; + const activeSubscriptions = polarSubscriptions.filter(sub => sub.status === 'active'); + + if (activeSubscriptions.length === 0) { + return NextResponse.json({ + success: false, + error: 'No active subscriptions found in Polar for this customer', + allSubscriptions: polarSubscriptions.map(sub => ({ + id: sub.id, + status: sub.status, + productId: sub.productId + })) + }); + } + + const subscription = activeSubscriptions[0]; + console.log(`✅ Found active subscription: ${subscription.id}`); + + // 4. Manually process the subscription.created webhook + const webhookUrl = `${process.env.NEXT_PUBLIC_URL}/api/webhooks/polar`; + + // Create a subscription.created webhook event payload + const webhookPayload = { + id: `admin_manual_${Date.now()}`, + type: 'subscription.created', + data: { + id: subscription.id, + customer_id: customer.id, + product_id: subscription.productId, + price_id: subscription.priceId, + status: subscription.status, + current_period_start: subscription.currentPeriodStart, + current_period_end: subscription.currentPeriodEnd, + trial_start: subscription.trialStart, + trial_end: subscription.trialEnd, + metadata: { + userId: dbUser.id, + source: 'admin_manual_trigger', + triggerReason: 'webhook_recovery', + triggerTime: new Date().toISOString() + } + } + }; + + console.log(`🚀 Admin triggering webhook with payload for subscription: ${subscription.id}`); + + // 5. Call our own webhook handler directly + const webhookResponse = await fetch(webhookUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'polar-signature': 'manual-trigger', + 'user-agent': 'ConvertIQ-Admin-Recovery' + }, + body: JSON.stringify(webhookPayload) + }); + + const webhookResult = await webhookResponse.text(); + + if (webhookResponse.ok) { + console.log(`✅ Admin webhook processing successful for ${userEmail}`); + return NextResponse.json({ + success: true, + message: 'Admin webhook triggered successfully', + user: { + id: dbUser.id, + email: dbUser.email, + name: dbUser.name + }, + subscription: { + polarId: subscription.id, + customerId: customer.id, + status: subscription.status, + productId: subscription.productId, + priceId: subscription.priceId + }, + webhookResult: webhookResult + }); + } else { + console.error(`❌ Admin webhook processing failed for ${userEmail}:`, webhookResult); + return NextResponse.json({ + success: false, + error: 'Webhook processing failed', + details: webhookResult + }, { status: 500 }); + } + + } catch (error) { + console.error('❌ Admin webhook trigger error:', error); + return NextResponse.json({ + success: false, + error: 'Failed to trigger webhook', + details: error instanceof Error ? error.message : String(error) + }, { status: 500 }); + } +} \ No newline at end of file