chore: something to get deployment running

This commit is contained in:
Rohith Gilla 2025-11-29 10:09:38 +05:30
parent 3b392f2f50
commit a2dbdfc805

View file

@ -1,28 +1,31 @@
import { NextRequest, NextResponse } from 'next/server'
import { db, customers, licenses } from '@/db'
import { eq } from 'drizzle-orm'
import { generateLicenseKey, calculateUpdatesUntil } from '@/lib/license'
import { Resend } from 'resend'
import { NextRequest, NextResponse } from "next/server";
import { db, customers, licenses } from "@/db";
import { eq } from "drizzle-orm";
import { generateLicenseKey, calculateUpdatesUntil } from "@/lib/license";
import { Resend } from "resend";
const resend = new Resend(process.env.RESEND_API_KEY)
const resend = new Resend(process.env.RESEND_API_KEY ?? "re_123");
// DodoPayments webhook event types
type DodoEventType = 'payment.completed' | 'payment.refunded' | 'payment.failed'
type DodoEventType =
| "payment.completed"
| "payment.refunded"
| "payment.failed";
interface DodoWebhookPayload {
event: DodoEventType
event: DodoEventType;
data: {
payment_id: string
product_id: string
payment_id: string;
product_id: string;
customer: {
email: string
name?: string
customer_id: string
}
amount: number
currency: string
metadata?: Record<string, string>
}
email: string;
name?: string;
customer_id: string;
};
amount: number;
currency: string;
metadata?: Record<string, string>;
};
}
// Verify DodoPayments webhook signature
@ -31,21 +34,21 @@ async function verifyWebhookSignature(
signature: string | null
): Promise<boolean> {
if (!signature || !process.env.DODO_WEBHOOK_SECRET) {
console.warn('Missing webhook signature or secret')
return false
console.warn("Missing webhook signature or secret");
return false;
}
// DodoPayments uses HMAC-SHA256 for webhook signatures
const crypto = await import('crypto')
const crypto = await import("crypto");
const expectedSignature = crypto
.createHmac('sha256', process.env.DODO_WEBHOOK_SECRET)
.createHmac("sha256", process.env.DODO_WEBHOOK_SECRET)
.update(payload)
.digest('hex')
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
)
);
}
// Send welcome email with license key
@ -56,20 +59,20 @@ async function sendWelcomeEmail(
updatesUntil: Date
) {
if (!process.env.RESEND_API_KEY) {
console.warn('RESEND_API_KEY not configured, skipping email')
return
console.warn("RESEND_API_KEY not configured, skipping email");
return;
}
try {
await resend.emails.send({
from: 'data-peek <hello@datapeek.app>',
from: "data-peek <hello@datapeek.app>",
to: email,
subject: 'Your data-peek Pro license 🎉',
subject: "Your data-peek Pro license 🎉",
html: `
<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 600px; margin: 0 auto;">
<h1 style="color: #22d3ee;">Welcome to data-peek Pro!</h1>
<p>Hi ${name || 'there'},</p>
<p>Hi ${name || "there"},</p>
<p>Thank you for purchasing data-peek Pro! Your license is ready to use.</p>
@ -97,36 +100,39 @@ async function sendWelcomeEmail(
<p>Happy querying!<br> The data-peek team</p>
</div>
`,
})
});
} catch (error) {
console.error('Failed to send welcome email:', error)
console.error("Failed to send welcome email:", error);
}
}
export async function POST(request: NextRequest) {
try {
const payload = await request.text()
const signature = request.headers.get('x-dodo-signature')
const payload = await request.text();
const signature = request.headers.get("x-dodo-signature");
// Verify signature in production
if (process.env.NODE_ENV === 'production') {
const isValid = await verifyWebhookSignature(payload, signature)
if (process.env.NODE_ENV === "production") {
const isValid = await verifyWebhookSignature(payload, signature);
if (!isValid) {
console.error('Invalid webhook signature')
return NextResponse.json({ error: 'Invalid signature' }, { status: 401 })
console.error("Invalid webhook signature");
return NextResponse.json(
{ error: "Invalid signature" },
{ status: 401 }
);
}
}
const event = JSON.parse(payload) as DodoWebhookPayload
const event = JSON.parse(payload) as DodoWebhookPayload;
switch (event.event) {
case 'payment.completed': {
const { data } = event
case "payment.completed": {
const { data } = event;
// Find or create customer
let customer = await db.query.customers.findFirst({
where: eq(customers.email, data.customer.email),
})
});
if (!customer) {
const [newCustomer] = await db
@ -136,31 +142,31 @@ export async function POST(request: NextRequest) {
name: data.customer.name,
dodoCustomerId: data.customer.customer_id,
})
.returning()
customer = newCustomer
.returning();
customer = newCustomer;
} else if (!customer.dodoCustomerId) {
// Update existing customer with DodoPayments ID
await db
.update(customers)
.set({ dodoCustomerId: data.customer.customer_id })
.where(eq(customers.id, customer.id))
.where(eq(customers.id, customer.id));
}
// Generate license key
const licenseKey = generateLicenseKey('DPRO')
const updatesUntil = calculateUpdatesUntil()
const licenseKey = generateLicenseKey("DPRO");
const updatesUntil = calculateUpdatesUntil();
// Create license
await db.insert(licenses).values({
customerId: customer.id,
licenseKey,
plan: 'pro',
status: 'active',
plan: "pro",
status: "active",
maxActivations: 3,
dodoPaymentId: data.payment_id,
dodoProductId: data.product_id,
updatesUntil,
})
});
// Send welcome email
await sendWelcomeEmail(
@ -168,45 +174,52 @@ export async function POST(request: NextRequest) {
data.customer.name,
licenseKey,
updatesUntil
)
);
console.log(`License created for ${data.customer.email}: ${licenseKey}`)
break
console.log(
`License created for ${data.customer.email}: ${licenseKey}`
);
break;
}
case 'payment.refunded': {
const { data } = event
case "payment.refunded": {
const { data } = event;
// Find and revoke the license
const license = await db.query.licenses.findFirst({
where: eq(licenses.dodoPaymentId, data.payment_id),
})
});
if (license) {
await db
.update(licenses)
.set({ status: 'revoked' })
.where(eq(licenses.id, license.id))
.set({ status: "revoked" })
.where(eq(licenses.id, license.id));
console.log(`License revoked for payment ${data.payment_id}`)
console.log(`License revoked for payment ${data.payment_id}`);
}
break
break;
}
case 'payment.failed': {
const { data } = event
console.log(`Payment failed for ${data.customer.email}: ${data.payment_id}`)
case "payment.failed": {
const { data } = event;
console.log(
`Payment failed for ${data.customer.email}: ${data.payment_id}`
);
// Could send a failed payment notification email here
break
break;
}
default:
console.log(`Unhandled webhook event: ${event.event}`)
console.log(`Unhandled webhook event: ${event.event}`);
}
return NextResponse.json({ received: true })
return NextResponse.json({ received: true });
} catch (error) {
console.error('Webhook processing error:', error)
return NextResponse.json({ error: 'Webhook processing failed' }, { status: 500 })
console.error("Webhook processing error:", error);
return NextResponse.json(
{ error: "Webhook processing failed" },
{ status: 500 }
);
}
}