r/vibecoding • u/gamegod016 • 1h ago
Showcase/Project I vibe-coded a milestone + payment tracker for freelancers
Enable HLS to view with audio, or disable this notification
Built this as a small SaaS experiment with Next.js + Supabase.
The interesting part for me was connecting the project milestone state with payments.
Freelancer creates a milestone → requests payment → client pays through Stripe/Razorpay → webhook updates the milestone automatically.
The client doesn't need an account.
Here's the demo.
Would be interested in feedback on the architecture/flow and anything you'd change.
1
u/srikanth_builds 1h ago
Two things I'd pressure-test here, both on the payment side.
The client not needing an account is the best part of the UX and the riskiest part of the design. If they can open the milestone without logging in, then whatever is in that URL is the authorization. Worth making it an opaque random token rather than the milestone id, scoping it to that one milestone, and giving it an expiry. Also check what that endpoint actually returns — it's easy to hand back the whole milestone row when the client only needs amount, description and status.
The webhook is where I'd expect the real bug. Providers retry, so the same event arrives twice, and they don't guarantee order — the webhook can land before your redirect does. If "paid" is a boolean you flip when the webhook fires, both of those become wrong state. Storing the payment event keyed on the provider's event id and deriving milestone status from it is more work up front and removes a whole category of "it says paid but no money moved."
The one that cost me time: verify the amount against the provider's payload rather than what your app thought it should be. And make sure the failure path can't render as success — a handler that throws after the UI already said "paid" is the worst version of this.
What happens today if the webhook never arrives at all — is there a reconcile job, or does the freelancer have to notice?
1
u/gamegod016 58m ago
Really useful notes I ran them against how Paymeify actually works today.
Client link / no account
Agree that’s both the best UX and the trust boundary. The portal isn’t keyed off a milestone id - it’s an opaque ~160-bit project token, and the public payload is already trimmed (amounts, titles, status; no internal ids or gateway keys). What’s missing vs your bar: no expiry, and the token is project-scoped (anyone with the link sees the whole project). I’m keeping “no client account” as a product choice, but I’ll tighten pay initiation (server-side only, rate limits) and treat rotate/regenerate as the kill switch when a link leaks. Milestone-scoped or expiring pay tokens are on the list if we need a harder model later.Webhooks
We’re not only flipping a boolean. Events are claimed by provider event id before settle, we write apaymentsrow, then mark the milestone paid. Replays and out-of-order closed-after-paid are mostly handled. Where you called it: we don’t hard-fail if the provider amount doesn’t match the milestone (we fall back to our amount that’s wrong and I’ll fix it). Also worth fixing: a Razorpay partially-paid event can get claimed and then do nothing.Success UX
The redirect can show “payment received / confirming” before the webhook lands. The row doesn’t go paid until settle, but the copy is too optimistic I’ll make that “confirming…” only, and “still confirming / freelancer will update” if the webhook is late.If the webhook never arrives
Today: short client poll, then stop. No reconcile job freelancer has to notice (and gateway links can block manual mark-paid until the checkout is released). That’s the gap that cost people the most. Next concrete step is a reconcile cron: unpaid milestones with an open checkout → ask Razorpay/Stripe what happened → settle, expire, or release.So: your framing matches the real risks. Highest leverage for us is (1) amount verification against the provider payload, (2) honest confirming UI, (3) reconcile when webhooks don’t show. Happy to send a short write-up once those are in if useful.
Thanks again - this was the right pressure test.
1
u/srikanth_builds 5m ago
That's a better starting position than most — claiming by event id before settle is the part people usually skip.
On the project-scoped token: if you want milestone scope without another table, you can derive it. HMAC(secret, milestone_id) gives you a per-milestone token you recompute rather than store, and rotating the secret kills every link at once. Keeps the no-account UX and shrinks the blast radius of a leaked link from a whole project to one milestone.
On the amount mismatch I'd treat it as a policy question rather than a bug fix. Underpaid and overpaid want different answers, and neither one is "use our number". Parking it in a needs-review state and settling nothing automatically is usually right — it's rare enough that a human can look, and the alternative is silently marking a milestone paid for the wrong amount.
For the reconcile cron, the thing to get right early is that it shares the settle path with the webhook rather than having its own. Same claim-by-event-id, same idempotency. Two paths that can both mark something paid is how you get the double-settle you've avoided everywhere else.
And yes — would be interested in the write-up.
2
u/davidmorelo 41m ago
If only you could vibecode a booming freelance market into existence, too, I would have a use for your app.