Files
gridpilot.gg/apps/website/app/api/payments/membership-fees/route.ts
2025-12-10 12:38:55 +01:00

171 lines
4.5 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server';
// Alpha: In-memory membership fee storage
const membershipFees: Map<string, {
id: string;
leagueId: string;
seasonId?: string;
type: 'season' | 'monthly' | 'per_race';
amount: number;
enabled: boolean;
createdAt: Date;
updatedAt: Date;
}> = new Map();
const memberPayments: Map<string, {
id: string;
feeId: string;
driverId: string;
amount: number;
platformFee: number;
netAmount: number;
status: 'pending' | 'paid' | 'overdue';
dueDate: Date;
paidAt?: Date;
}> = new Map();
const PLATFORM_FEE_RATE = 0.10;
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const leagueId = searchParams.get('leagueId');
const driverId = searchParams.get('driverId');
if (!leagueId) {
return NextResponse.json(
{ error: 'leagueId is required' },
{ status: 400 }
);
}
const fee = Array.from(membershipFees.values()).find(f => f.leagueId === leagueId);
let payments: typeof memberPayments extends Map<string, infer V> ? V[] : never[] = [];
if (driverId) {
payments = Array.from(memberPayments.values()).filter(
p => membershipFees.get(p.feeId)?.leagueId === leagueId && p.driverId === driverId
);
}
return NextResponse.json({ fee, payments });
}
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { leagueId, seasonId, type, amount } = body;
if (!leagueId || !type || amount === undefined) {
return NextResponse.json(
{ error: 'Missing required fields: leagueId, type, amount' },
{ status: 400 }
);
}
if (!['season', 'monthly', 'per_race'].includes(type)) {
return NextResponse.json(
{ error: 'Type must be "season", "monthly", or "per_race"' },
{ status: 400 }
);
}
// Check for existing fee config
const existingFee = Array.from(membershipFees.values()).find(f => f.leagueId === leagueId);
if (existingFee) {
// Update existing fee
existingFee.type = type;
existingFee.amount = amount;
existingFee.seasonId = seasonId || existingFee.seasonId;
existingFee.enabled = amount > 0;
existingFee.updatedAt = new Date();
membershipFees.set(existingFee.id, existingFee);
return NextResponse.json({ fee: existingFee });
}
const id = `fee-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
const fee = {
id,
leagueId,
seasonId: seasonId || undefined,
type,
amount,
enabled: amount > 0,
createdAt: new Date(),
updatedAt: new Date(),
};
membershipFees.set(id, fee);
return NextResponse.json({ fee }, { status: 201 });
} catch (err) {
console.error('Membership fee creation failed:', err);
return NextResponse.json(
{ error: 'Failed to create membership fee' },
{ status: 500 }
);
}
}
// Record a member payment
export async function PATCH(request: NextRequest) {
try {
const body = await request.json();
const { feeId, driverId, status, paidAt } = body;
if (!feeId || !driverId) {
return NextResponse.json(
{ error: 'Missing required fields: feeId, driverId' },
{ status: 400 }
);
}
const fee = membershipFees.get(feeId);
if (!fee) {
return NextResponse.json(
{ error: 'Membership fee configuration not found' },
{ status: 404 }
);
}
// Find or create payment record
let payment = Array.from(memberPayments.values()).find(
p => p.feeId === feeId && p.driverId === driverId
);
if (!payment) {
const platformFee = fee.amount * PLATFORM_FEE_RATE;
const netAmount = fee.amount - platformFee;
const paymentId = `mp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
payment = {
id: paymentId,
feeId,
driverId,
amount: fee.amount,
platformFee,
netAmount,
status: 'pending',
dueDate: new Date(),
};
memberPayments.set(paymentId, payment);
}
if (status) {
payment.status = status;
}
if (paidAt || status === 'paid') {
payment.paidAt = paidAt ? new Date(paidAt) : new Date();
}
memberPayments.set(payment.id, payment);
return NextResponse.json({ payment });
} catch (err) {
console.error('Member payment update failed:', err);
return NextResponse.json(
{ error: 'Failed to update member payment' },
{ status: 500 }
);
}
}