DEV Community

Kaneki
Kaneki

Posted on

I Built a File Paywall Platform for Freelancers

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:

  1. You finish the work
  2. Client asks to "just see the final version first"
  3. You send it over
  4. Invoice goes unpaid for 3 weeks
  5. 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.

getpaygate.com

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
Email 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
});
Enter fullscreen mode Exit fullscreen mode

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:

  1. Payment status (via Stripe webhook confirmation)
  2. Download token validity
  3. 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);
Enter fullscreen mode Exit fullscreen mode

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 generate step
  • 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)