I built a legal marketplace in 3 days. Not because I had a burning passion for law (I don't), but because I kept seeing this problem: clients don't know how much legal services cost until they've already paid for a consultation. Lawyers, on the other hand, waste time on intake calls for cases they don't want.
So I built Justitia, a platform where clients post cases, lawyers bid on them, and payment happens upfront through Stripe. Simple concept. Messy execution. Here's how it went.
Except it wasn't simple. At all. So I thought: What if clients posted cases and lawyers bid on them?
await db.transaction(async (tx) => {
const quote = await tx.query.quotes.findFirst({
where: and(eq(quotes.id, quoteId), eq(quotes.status, 'proposed'))
});
if (!quote) throw new Error('Quote already accepted');
// Accept this quote
await tx.update(quotes)
.set({ status: 'accepted' })
.where(eq(quotes.id, quoteId));
// Reject all others
await tx.update(quotes)
.set({ status: 'rejected' })
.where(and(
eq(quotes.caseId, quote.caseId),
ne(quotes.id, quoteId)
));
}); const handlePaymentSuccess = async (paymentIntentId: string) => {
const payment = await db.query.payments.findFirst({
where: eq(payments.stripePaymentIntentId, paymentIntentId)
});
if (payment.status === 'succeeded') {
console.log('Already processed, skipping');
return; // Idempotent!
}
// Update quote status, case status, notify lawyer...
}; /webhooks/stripe and fake a payment.const sig = req.headers['stripe-signature'];
const event = stripe.webhooks.constructEvent(rawBody, sig, WEBHOOK_SECRET); If signature fails → reject. No mercy.
const canAccessFile = async (userId: string, fileId: string) => {
const file = await db.query.files.findFirst({
where: eq(files.id, fileId),
with: { case: { with: { acceptedQuote: true } } }
});
if (!file) return false;
if (file.case.clientId === userId) return true;
if (file.case.acceptedQuote?.lawyerId === userId) return true;
return false;
}; Files stored in Cloudflare R2 with signed URLs (expire after 1 hour). No permanent public links.
Layer 3: Deployment & Costs (The Real World)
Compare that to AWS: S3 + RDS + EC2 = $50+/month easy.