feat: ko-fi integration for credit purchases
This commit is contained in:
@@ -6,6 +6,7 @@ import { prisma } from "../utils/prisma";
|
||||
|
||||
const router = Router();
|
||||
const WEBHOOK_SECRET = process.env.TAUTULLI_WEBHOOK_SECRET || "";
|
||||
const KOFI_VERIFICATION_TOKEN = process.env.KOFI_VERIFICATION_TOKEN || "";
|
||||
|
||||
function verifyWebhookSignature(payload: string, signature: string): boolean {
|
||||
if (!WEBHOOK_SECRET) return true;
|
||||
@@ -115,4 +116,115 @@ router.post(
|
||||
}),
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/kofi",
|
||||
asyncHandler(async (req, res) => {
|
||||
const rawData = req.body.data;
|
||||
if (!rawData) return res.status(400).json({ error: "Missing data" });
|
||||
|
||||
let payload: any;
|
||||
try {
|
||||
payload = JSON.parse(rawData);
|
||||
} catch {
|
||||
return res.status(400).json({ error: "Invalid JSON" });
|
||||
}
|
||||
|
||||
if (payload.verification_token !== KOFI_VERIFICATION_TOKEN) {
|
||||
return res.status(403).json({ error: "Invalid verification token" });
|
||||
}
|
||||
|
||||
if (payload.type !== "Donation") {
|
||||
return res.json({ message: "Non-donation event ignored" });
|
||||
}
|
||||
|
||||
const kofiTxId = payload.kofi_transaction_id;
|
||||
if (!kofiTxId) {
|
||||
return res.status(400).json({ error: "Missing transaction id" });
|
||||
}
|
||||
|
||||
const existing = await prisma.kofiPayment.findUnique({
|
||||
where: { kofiTransactionId: kofiTxId },
|
||||
});
|
||||
if (existing) {
|
||||
return res.json({ success: true, alreadyProcessed: true });
|
||||
}
|
||||
|
||||
const amount = parseFloat(payload.amount) || 0;
|
||||
const creditsGranted = Math.floor(amount * 100);
|
||||
const email = (payload.email || "").toLowerCase().trim();
|
||||
|
||||
if (creditsGranted <= 0) {
|
||||
return res.status(400).json({ error: "Invalid amount" });
|
||||
}
|
||||
|
||||
const user = email
|
||||
? await prisma.user.findFirst({
|
||||
where: {
|
||||
email: { equals: email, mode: "insensitive" },
|
||||
},
|
||||
})
|
||||
: null;
|
||||
|
||||
if (user) {
|
||||
await prisma.$transaction([
|
||||
prisma.kofiPayment.create({
|
||||
data: {
|
||||
kofiTransactionId: kofiTxId,
|
||||
email: payload.email || "",
|
||||
fromName: payload.from_name || "",
|
||||
amount,
|
||||
creditsGranted,
|
||||
message: payload.message || null,
|
||||
isPublic: payload.is_public ?? true,
|
||||
currency: payload.currency || "USD",
|
||||
userId: user.id,
|
||||
status: "CREDITED",
|
||||
},
|
||||
}),
|
||||
prisma.transaction.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
type: "PURCHASE",
|
||||
amount: creditsGranted,
|
||||
description: `Ko-fi $${amount.toFixed(2)}`,
|
||||
contentTitle: "Ko-fi Credit Purchase",
|
||||
},
|
||||
}),
|
||||
prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: { totalEarned: { increment: creditsGranted } },
|
||||
}),
|
||||
]);
|
||||
|
||||
io.to(`user:${user.id}`).emit("credits_earned", {
|
||||
amount: creditsGranted,
|
||||
title: "Ko-fi Purchase",
|
||||
transaction: {
|
||||
id: kofiTxId,
|
||||
type: "PURCHASE",
|
||||
amount: creditsGranted,
|
||||
contentTitle: "Ko-fi Credit Purchase",
|
||||
createdAt: new Date(),
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await prisma.kofiPayment.create({
|
||||
data: {
|
||||
kofiTransactionId: kofiTxId,
|
||||
email: payload.email || "",
|
||||
fromName: payload.from_name || "",
|
||||
amount,
|
||||
creditsGranted,
|
||||
message: payload.message || null,
|
||||
isPublic: payload.is_public ?? true,
|
||||
currency: payload.currency || "USD",
|
||||
status: "UNCLAIMED",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
res.json({ success: true, credited: !!user, creditsGranted });
|
||||
}),
|
||||
);
|
||||
|
||||
export { router as webhookRouter };
|
||||
|
||||
Reference in New Issue
Block a user