wsb-bot / WSW /src /app /api /webhook /sellapp /route.ts
APRK01
Configure cloud database and production pages
c35213b
import { NextResponse } from 'next/server';
import { supabase } from '@/lib/db';
import crypto from 'crypto';
// The webhook secret from your Sell.app developer dashboard
const SELLAPP_SECRET = process.env.SELLAPP_WEBHOOK_SECRET || 'dummy_secret';
export async function POST(request: Request) {
try {
const rawBody = await request.text();
const headersList = request.headers;
// Sell.app uses the standard 'x-sellapp-signature' header.
const signature = headersList.get('x-sellapp-signature');
if (!signature) {
return NextResponse.json({ error: 'Missing signature' }, { status: 401 });
}
// Verify Sell.app Signature
const hmac = crypto.createHmac('sha256', SELLAPP_SECRET);
hmac.update(rawBody);
const calculatedSignature = hmac.digest('hex');
if (signature !== calculatedSignature && process.env.NODE_ENV === 'production') {
return NextResponse.json({ error: 'Invalid signature' }, { status: 403 });
}
const data = JSON.parse(rawBody);
// Safety check: is it an order completion?
if (data.event !== 'order:paid' && data.event !== 'order:completed') {
return NextResponse.json({ received: true });
}
const order = data.data;
const additionalInfo = order.additional_information || [];
// We search through the additional_information array for the "discord_id" custom field.
const discordField = additionalInfo.find((field: any) => field.name?.toLowerCase() === 'discord_id');
const discordId = discordField ? discordField.value : null;
if (!discordId) {
console.error("Webhook Error: Discord ID was not provided in custom fields.");
return NextResponse.json({ error: 'Missing Discord ID' }, { status: 400 });
}
// Sell.app typically returns total in major units (e.g., 25.00 instead of 2500)
const price = parseFloat(order.total_amount || order.total);
let expiresAt: string | null = null;
const now = new Date();
if (price === 3) {
now.setDate(now.getDate() + 7);
expiresAt = now.toISOString();
} else if (price === 7) {
now.setDate(now.getDate() + 30);
expiresAt = now.toISOString();
} else if (price === 15) {
now.setFullYear(now.getFullYear() + 1);
expiresAt = now.toISOString();
} else if (price === 25) {
expiresAt = null;
} else {
console.warn(`Unrecognized price tier: $${price}. Applying lifetime by default.`);
expiresAt = null;
}
// Save to database using Supabase
const { error: upsertError } = await supabase
.from('vip_users')
.upsert({
discord_id: discordId,
expires_at: expiresAt,
purchased_at: new Date().toISOString()
}, {
onConflict: 'discord_id'
});
if (upsertError) throw upsertError;
console.log(`[SELLAPP WEBHOOK] Successfully unlocked VIP for Discord ID: ${discordId}`);
return NextResponse.json({ success: true });
} catch (error) {
console.error('Webhook processing failed:', error);
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
}
}