Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions src/app/api/admin/setup-polar-webhook/route.ts
Original file line number Diff line number Diff line change
@@ -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 });
}
}
166 changes: 166 additions & 0 deletions src/app/api/admin/trigger-webhook-by-email/route.ts
Original file line number Diff line number Diff line change
@@ -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 });
}
}