TL;DR: I got tired of chasing clients for payments before delivering files, so I built PayGate — a platform where freelancers create payment links, clients pay, and only then get access to download the files. No more "I'll pay you next week." Here's the full technical breakdown.
The Problem
If you've ever freelanced, you know the dance:
- You finish the work
- Client asks to "just see the final version first"
- You send it over
- Invoice goes unpaid for 3 weeks
- Awkward follow-up emails
The power dynamic shifts the moment you hand over the files. I wanted to flip that.
Existing solutions? Sure — Gumroad takes 10% and doesn't use your own Stripe. Payhip and SendOwl charge 5% on free plans. Stripe Payment Links work but have zero file protection — once redirected, the client can share the download link with anyone.
None of them handled invoicing for my local tax regulations either. So I built my own.
Meet PayGate
The concept is dead simple: Upload a file → set a price → send the link to your client → they pay → they download. That's it.
No storefront. No shopping cart. No "build your creator brand" fluff. Just: get paid before you deliver files.
Tech Stack
For the dev crowd — here's what's under the hood:
| Layer | Tech |
|---|---|
| Framework | Next.js 16 (App Router) |
| Language | TypeScript (strict mode, obviously) |
| Styling | Tailwind CSS v4 + shadcn/ui |
| Database | Supabase (Postgres + Auth + RLS) |
| ORM | Drizzle ORM |
| Payments | Stripe Connect Express |
| File Storage | Vercel Blob |
| Resend | |
| Hosting | Vercel |
~5,800 lines of TypeScript across ~45 commits. Solo dev, from zero to launch.
Architecture Decisions Worth Sharing
Why Stripe Connect Express (not just Stripe Checkout)
This was the biggest architectural decision. Regular Stripe integration means I collect the money and then pay out the freelancer. That's a regulatory nightmare — you become a payment facilitator.
With Stripe Connect Express, each freelancer onboards their own Stripe account. Money flows directly from client → freelancer. PayGate never touches the funds. This means:
- No money transmission license needed
- Freelancers get paid instantly (well, on Stripe's schedule)
- I just collect the platform fee via Stripe's
application_fee_amount
const session = await stripe.checkout.sessions.create({
payment_intent_data: {
application_fee_amount: calculateFee(price, userPlan),
transfer_data: {
destination: freelancerStripeAccountId,
},
},
// ... rest of checkout config
});
File Security with Vercel Blob
Files are stored in Vercel Blob with no public URLs. When a client completes payment, the app generates a short-lived signed URL. The download page checks:
- Payment status (via Stripe webhook confirmation)
- Download token validity
- Expiration time
No payment, no download. Simple.
Supabase RLS for Multi-Tenancy
Every table has Row Level Security policies. A freelancer can only see their own deliverables, their own transactions, their own analytics. This isn't just "filter by user_id in the query" — it's enforced at the database level.
CREATE POLICY "Users can only view own deliverables"
ON deliverables FOR SELECT
USING (auth.uid() = user_id);
Drizzle ORM Over Prisma
I went with Drizzle because:
- It's closer to SQL (if you know Postgres, you know Drizzle)
- Type inference is incredible — no
prisma generatestep - Lighter bundle size on the edge
- Migrations are just SQL files
Pricing Model
I tried to make it freelancer-friendly:
| Free | Pro ($19/mo) | |
|---|---|---|
| Deliverables | 3 | Unlimited |
| Max price per file | $500 | Unlimited |
| Platform fee | 5% | 0% |
The math works out: if you're doing more than ~$380/month in transactions, Pro pays for itself versus any 5% competitor.
Lessons Learned
1. Stripe Connect Onboarding is Complex
The Express onboarding flow is well-documented but has edge cases. Different countries have different KYC requirements. Testing in development mode ≠ production behavior. Budget extra time here.
2. Webhook Reliability is Everything
Your payment flow is only as good as your webhook handling. I implemented idempotency keys and a retry mechanism from day one. If the webhook fails, the client shouldn't lose access to their download.
3. Start With Fewer Features
My V1 had no analytics, no custom branding, no fancy dashboard. Just: upload, price, share, get paid. That constraint forced me to nail the core flow.
What's Next (V2 Roadmap)
- Custom branding — Let freelancers add their logo, colors
- Analytics dashboard — Views, conversion rates, revenue tracking
- Google OAuth — Faster onboarding
- DAC7 tax reporting — EU compliance for platform sellers
- API access — For devs who want to integrate PayGate into their own tools
Try It / Roast It
If you're a freelancer who's ever had a client ghost after receiving the files — give PayGate a try. Free plan, no credit card required.
And if you're a dev — I'd love your feedback on the architecture. Roast my stack, suggest improvements, or tell me what I'm doing wrong. That's what dev.to is for. 🤙
Top comments (0)