Add paginated payment history to user dashboard
Users can now view their own payments (excluding donations) with server-side pagination (25/page), date range, method, and payment/refund filters, both on the API and the new client page.
This commit is contained in:
@@ -349,30 +349,57 @@ const getPayments = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
// @desc Get user payments
|
||||
// @desc Get user payments (paginated, excludes donations, supports date range/method/kind filters)
|
||||
// @route GET /api/payments/mypayments
|
||||
// @access Private
|
||||
const getUserPayments = async (req, res) => {
|
||||
try {
|
||||
// Users should see payments they made (donations) and payments tied to their registrations
|
||||
const payments = await prisma.payment.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ userId: req.user.id }, // payments made by the user (includes donations)
|
||||
{ registration: { userId: req.user.id } } // payments tied to the user's registrations
|
||||
]
|
||||
},
|
||||
include: {
|
||||
registration: {
|
||||
include: {
|
||||
event: true
|
||||
}
|
||||
},
|
||||
event: true
|
||||
}
|
||||
});
|
||||
const page = Math.max(1, parseInt(req.query.page) || 1);
|
||||
const limit = Math.min(25, Math.max(1, parseInt(req.query.limit) || 25));
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
res.json(payments);
|
||||
// Users should see payments they made and payments tied to their registrations,
|
||||
// excluding donations.
|
||||
const where = {
|
||||
isDonation: false,
|
||||
OR: [
|
||||
{ userId: req.user.id },
|
||||
{ registration: { userId: req.user.id } }
|
||||
]
|
||||
};
|
||||
|
||||
if (req.query.method) {
|
||||
where.method = { startsWith: String(req.query.method), mode: 'insensitive' };
|
||||
}
|
||||
|
||||
if (req.query.kind === 'refund') {
|
||||
where.amount = { lt: 0 };
|
||||
} else if (req.query.kind === 'payment') {
|
||||
where.amount = { gte: 0 };
|
||||
}
|
||||
|
||||
if (req.query.startDate || req.query.endDate) {
|
||||
where.createdAt = {
|
||||
...(req.query.startDate && { gte: new Date(req.query.startDate) }),
|
||||
...(req.query.endDate && { lte: new Date(req.query.endDate) })
|
||||
};
|
||||
}
|
||||
|
||||
const [payments, total] = await prisma.$transaction([
|
||||
prisma.payment.findMany({
|
||||
where,
|
||||
include: {
|
||||
registration: { include: { event: true } },
|
||||
event: true
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
skip,
|
||||
take: limit
|
||||
}),
|
||||
prisma.payment.count({ where })
|
||||
]);
|
||||
|
||||
res.json({ data: payments, total, page, limit, pages: Math.ceil(total / limit) });
|
||||
} catch (error) {
|
||||
res.status(res.statusCode === 200 ? 400 : res.statusCode).json({ message: safeErrorMessage(error) });
|
||||
}
|
||||
|
||||
@@ -543,8 +543,9 @@ app.get('/docs', async (req, res) => {
|
||||
responses:[
|
||||
{ status:201, desc:'Recorded', body:{ id:'pay-uuid-...', amount:450, method:'cash', status:'succeeded', createdAt:'2025-06-10T09:00:00.000Z' }},
|
||||
]},
|
||||
{ method:'GET', path:'/api/payments/mypayments', auth:'user+', desc:'Get own payment history',
|
||||
responses:[{ status:200, desc:'Success', body:[{ id:'pay-uuid-...', amount:450, method:'card', status:'succeeded', createdAt:'2025-06-01T11:00:00.000Z' }]}]},
|
||||
{ method:'GET', path:'/api/payments/mypayments', auth:'user+', desc:'Get own payment history (paginated, excludes donations)',
|
||||
queryParams:{ page:'Page (default 1)', limit:'Per page (default 25, max 25)', startDate:'ISO date, filters createdAt >=', endDate:'ISO date, filters createdAt <=', method:'Filter by method prefix (cash|card|eft|voucher)', kind:'payment|refund — filters by amount sign' },
|
||||
responses:[{ status:200, desc:'Success', body:{ data:[{ id:'pay-uuid-...', amount:450, method:'card', status:'succeeded', createdAt:'2025-06-01T11:00:00.000Z' }], total:1, page:1, limit:25, pages:1 }}]},
|
||||
{ method:'GET', path:'/api/payments', auth:'supervisor+', desc:'List all payments',
|
||||
queryParams:{ page:'Page (default 1)', limit:'Per page (default 20)', eventId:'Filter by event', userId:'Filter by user', method:'Filter by method (cash|card|eft|donation)', startDate:'ISO date', endDate:'ISO date' },
|
||||
responses:[{ status:200, desc:'Success', body:{ data:[{ id:'pay-uuid-...', amount:450, method:'card', user:{ name:'Jane Doe' }, registration:{ event:{ title:'Camp 2025' }}}], total:1 }}]},
|
||||
|
||||
Reference in New Issue
Block a user