owl-stream/backend/src/routes/donations.ts

57 lines
2.1 KiB
TypeScript

import { Router, Response, NextFunction } from 'express';
import { body, validationResult } from 'express-validator';
import prisma from '../lib/prisma';
import { optionalAuth, authenticate, requireRole, AuthRequest } from '../middleware/auth';
const router = Router();
// POST /api/donations — public (guest) or authenticated
// Stripe integration stub: stores intent, returns client_secret placeholder
router.post('/', optionalAuth,
[
body('amount').isFloat({ min: 1 }).withMessage('Amount must be at least $1'),
body('campaign').optional().isIn(['land_preservation', 'volunteer_equipment', 'general']),
],
async (req: AuthRequest, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) { res.status(422).json({ errors: errors.array() }); return; }
try {
const { amount, campaign } = req.body;
// STUB: In production, create a Stripe PaymentIntent here and return client_secret
// const intent = await stripe.paymentIntents.create({ amount: Math.round(amount * 100), currency: 'usd' });
const stripe_payment_id = `pi_stub_${Date.now()}`;
const donation = await prisma.donation.create({
data: {
donor_id: req.user?.userId ?? null,
amount: parseFloat(amount),
campaign: campaign || 'general',
stripe_payment_id,
},
});
res.status(201).json({
donation,
// stub: replace with real Stripe client_secret for frontend confirmation
client_secret: `${stripe_payment_id}_secret`,
message: 'Stripe integration pending — payment recorded as stub',
});
} catch (err) { next(err); }
}
);
// GET /api/donations — admin only
router.get('/', authenticate, requireRole('admin'), async (_req: AuthRequest, res: Response, next: NextFunction) => {
try {
const donations = await prisma.donation.findMany({
include: { donor: { select: { id: true, name: true, email: true } } },
orderBy: { created_at: 'desc' },
});
res.json(donations);
} catch (err) { next(err); }
});
export default router;