wip
This commit is contained in:
171
apps/website/app/api/payments/membership-fees/route.ts
Normal file
171
apps/website/app/api/payments/membership-fees/route.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user