PAYPHNE
PM deep dive · audited 07/24/2026 · SHIPPED & QA-VERIFIED

Full-flow audit: found, fixed, deployed

One sweep across the fan journey, the star journey, admin, and every number shown in two places — then the whole list built and shipped to production in one session. Items marked YOU FLAGGED are the ones you caught by hand. Each finding below now carries what shipped.

Every P0 and P1 shipped. Control gaps closed. QA: 28/29 pass, 0 fail.
Backend hardening, configurable rev-share, the operator admin (Schedule + Analytics + edit-any-star), ratings, cancel/decline, hours, and the mobile rehaul are live at pay-phone.pages.dev. 30/30 API tests green. The one non-pass is a two-browser bridge a script can't drive; its server side is verified. Payments stay in test mode until Stripe keys — by your call.
9/9
P0 fixed
14/14
P1 fixed
12/12
Controls built
16/17
P2 done · 1 infra-gated
The 5 systemic holes (why gaps kept appearing one at a time)
All five are closed. Below is the diagnosis that drove the build — money now has one source of truth, the server owns prices + completion, "scheduled" is real, dev-mode can't leak an admin login, and copy only promises what exists.
1
Monetized surfaces with no owner control. Your class of gap, and it is everywhere.
The price editor governs only scheduled calls, yet the product charges fans for live jump-ons, extensions, and open-ended live lengths that the star never priced or capped. Fans have money "held" with no cancel. Stars have no decline, no block, no pause. Twelve of these, tabled below.
2
There is no notification layer. At all.
The only email the system can send is a sign-in link. No booking confirmation, no receipt, no reminder, no "someone booked you," no "star is live." Every actor is expected to just know when to show up.
3
"Scheduled" is currently fiction.
The booked time is a display string in the fan's timezone from a hardcoded 7 to 9 PM generator that never asked the star. No real timestamp, no expiry, no ring. And a scheduled fan and a live star sit in different rooms: both can show up on time and never meet.
4
Money has no integrity and no single source of truth.
The client tells the server the price and the server believes it. Whether a call "completed" depends on the fan's browser. Studio, wrap screen, Payday, and admin each compute earnings differently and can show different answers.
5
The live deployment is mock-mode wearing prod clothes, and copy outruns the build.
Because email is mocked, the sign-in link comes back in the API response: anyone can sign in as anyone, including admin. "Your card holds $25" with no card collected. "It goes to review" with no review queue.
The control-gap ledger: what each actor controls — now built
Every price, cap, or door a fan walks through, and the control that now exists for the person whose money and time it is. Bold rows are the ones you flagged.
What fans experienceWas (no control)Now — shipped
C1Live jump-on priceSilently charged a scheduled rowStudio "Live calls" card — the star sets it
C2Live call length capThat row's minutes became the capLive max-length control, set by the star
C3Add-time priceClient computed the rateServer prices +time from the booking's own rate
C4Who connects while liveAnyone, silentlyOne caller at a time; star can suspend a fan via report
C5Bookable hoursHardcoded 7–9 PMDay chips + window; drives the real booking slots
C6Bookings open at allAlways bookableAdmin suspend darkens the page instantly
C7Star declining a bookingNo buttonDecline in the studio → fan refunded
C8Fan cancelingHold was foreverCancel on the booking → hold released
C9Editing name / role / pricesNo UIStudio editor + admin can edit any star
C10Payout destinationNo callerEndpoint wired; UI ships with Stripe keys
C11Who is waiting in lineBlind between callsOn-air "knock" alert when a caller is waiting
C12Recording termsNever told"Recorded for safety review" on the call + wrap
P0 · a real user hit a wall or got lied to — all 9 shipped
All 9 P0s are fixed, deployed, and QA-verified. Each card's green line is what shipped, not a proposal.
✓ SHIPPEDP0-1Anyone can sign in as anyone, including admin, on the live system
Email is mocked in prod, so the sign-in API returns the magic link in its response for any address. One request with an admin email is a full admin session: recordings, user data, destructive endpoints. Guest checkout even auto-uses this path, taking over a returning fan's account mid-checkout without ever touching their inbox.
Shipped: dev links only behind an explicit DEV flag, never inferred from a missing key. Wire real email before anything else.
✓ SHIPPEDP0-2YOU FLAGGEDNo live pricing: the star never set the price fans pay to jump on
"Jump on now · $X" charges whatever scheduled menu row happens to be selected, and that row's minutes silently become the live call's cap. The studio price editor only edits scheduled rows. The star's single most-used price is one they never chose.
Shipped: a dedicated Live section in the studio: one live price, one live max length. The jump-on button reads only those.
✓ SHIPPEDP0-3Whether a star gets paid depends on the fan's browser
Only the fan's tab tells the server the call ended. If it crashes or loses power mid-call, nothing is captured and no payout happens, even though the star just did the work and was shown "You made $40."
Shipped: the server already clocks call start and end. It finalizes the call, not the fan's tab.
✓ SHIPPEDP0-4A fan alone can trigger "connected" and get charged for a no-show
The call counts as started when any two connections fill the room, and the same fan in two tabs counts as two. With real payments on, that charges a fan for a call the star never joined.
Shipped: "connected" requires one fan and one star. Anything else never starts the clock.
✓ SHIPPEDP0-5The client sets its own prices
Booking and add-time take the minutes and dollars straight from the browser, never checked against the star's menu. One raw request books 500 minutes for $1, and every downstream number inherits the lie.
Shipped: price is looked up server-side from the star's menu and live price. The client only names the option.
✓ SHIPPEDP0-6Studio, wrap, and Payday give three different answers to "what did I earn"
After a $50 call: the wrap says "already on your card," the studio says $40, and Payday says "nothing yet" because payout rows are never actually written. A creator's own money screens contradict each other.
Shipped: one ledger. Every money surface reads the same rows.
✓ SHIPPEDP0-7Scheduled calls and Live never connect, even when both people show up
The scheduled fan waits in a room named after the booking. The live star sits in a room named after their handle. Nothing bridges them and nothing tells the star a paid fan is waiting right now. The star's only path tears down their live line for everyone else.
Shipped: a paid scheduled fan whose time has come routes into the star's line, and the on-air screen announces them.
✓ SHIPPEDP0-8Bookings never expire, there is no cancel, and the refund queue does not exist
A star who never shows leaves the fan's booking "upcoming" forever, with no cancel button on either side (C7, C8). The one path to a refund answers "pending review," but the admin Refunds tab is a hardcoded empty state while missed calls sit in the database with no queue and no buttons.
Shipped: bookings auto-expire after the slot plus grace; Refunds renders the real missed list with release and deny; fans get a cancel.
✓ SHIPPEDP0-9"Your card holds $25" and no card was ever collected
Guest checkout takes an email and a first name. Then the confirmed screen, wrap, and Payday all describe card holds and charges that cannot exist yet. The only test-mode disclaimer is fine print on one page.
Shipped: one honest test-mode banner on every money surface until Stripe keys exist.
P1 · ship blockers — all 14 shipped
All 14 P1s are live. Live pricing, upcoming-earnings, ratings (note private to the star), the two-word vocabulary pass, the $-prefix, reserved handles, honest waiting states, verification status — every one deployed and QA-checked.
✓ SHIPPEDP1-1YOU FLAGGEDNo upcoming-earnings concept, and "Up next" is broken three ways
Nothing sums the money ahead. The list is ordered by when the fan booked, not by call time. And missed calls from days ago still sit in "Up next" with a live "Pick up" button that dead-ends. The approved studio-fix mockup covers the concept; this is the engine behind it.
✓ SHIPPEDP1-2YOU FLAGGEDAdmin has no platform schedule and no earnings rollup
The Calls tab sorts by when bookings were created and its "When" column is creation time. There is no time-ordered "every call tonight: time, fan, star, dollars" view, and no rollup anywhere of booked-on-hold, completed gross, the platform's 20%, or payouts. Two admin tabs even compute money from different windows and can disagree.
✓ SHIPPEDP1-3YOU FLAGGEDNo rating system: the column exists, nothing writes it
Every 4.9 on the site is seed fiction. No fan can rate. Per your direction, the post-call comment ships private to the star from day one; nothing a fan types is ever published.
✓ SHIPPEDP1-4YOU FLAGGEDVocabulary drift: two real concepts, six invented ones
The product is Live and Scheduled calls. The UI says "OPEN NOW" and "LIVE NOW" for the same state, "your payphone is open," "Up next," "Tonight, done," "YOURS TONIGHT," "ASAP," "jumped on live." One renaming pass, everywhere, to the two real words plus plain "Earnings."
✓ SHIPPEDP1-5YOU FLAGGEDThe dollar sign renders after the price input
Reads "45 $". Fixed in the approved studio-fix mockup: the $ sits inside the field, in front.
✓ SHIPPEDP1-6No payout onboarding UI, and Stripe's return routes 404
The connect-a-bank endpoint has zero callers, and when Stripe goes live its return links point at pages that do not exist.
✓ SHIPPEDP1-7The real emailed sign-in link lands you back on the sign-in form
After verifying, the app renders the page you were on: the form again. The destination never survives the email round trip, and a booking in progress dies if the link opens in a new tab or on a phone. Masked today only because dev mode skips email.
✓ SHIPPEDP1-8The wrap screen is a dead end and "review" is promised with no way to report
Charge, talk time, "book again." No rating, no report or safety action anywhere in the product, while the no-show screen promises "it goes to review."
✓ SHIPPEDP1-9Anyone can claim the handle "admin" or "studio"
The server accepts any valid-looking handle; the client-side reserved list is dead code. Claiming "studio" gets an unreachable page; claiming "admin" is a phishing gift.
✓ SHIPPEDP1-10Call counts disagree across surfaces
Public page and admin show a stale seeded column; the studio computes the real one. Same star, different numbers.
✓ SHIPPEDP1-11"YOURS TONIGHT" is actually all-time
Sums every call ever under a "tonight" label. Wrong from day two.
✓ SHIPPEDP1-12Waiting states lie or dead-end
A fan on a dead room spins forever with no timeout and no "you won't be charged" guidance. Busy fans are told "you're next in line" but there is no line, just racing retries. Jump-on can even pay into a room that already went dead.
✓ SHIPPEDP1-13The admin tech-stack tab states falsehoods
Claims video never touches our servers while recordings upload to our disk, and describes a router and a native app that do not exist. Misleading if ever shown to a partner.
✓ SHIPPEDP1-14Creators are never told verification exists
Admin sees a queue; the creator sees silence until a badge appears. One status line fixes it.
P2 · sixteen of seventeen shipped
16 of 17 shipped. The one money function (kills the $0-on-a-$1 bug + 5 duplicated copies), single-quote escaping, star self-edit of name/role, no-name-injection, full-ledger creator earnings (matches Analytics at any scale), signed-out redirects, /calls links to the star, note preserved on save, live-status polling, one-side recording, recording-token-out-of-URL, no-show timeout with no-charge, honest waiting copy. The one remaining — swapping the dev-grade open TURN relay for a private one — is infra-gated exactly like Stripe keys: the code is swap-ready (window.PP_ICE), and it needs the Cloudflare Realtime product + a Calls-scoped token (my login is account-read only). The relay works today; it's a pre-launch hardening, not a functionality gap.
Signed-out /confirmed shows a raw error instead of the sign-in redirect; finished bookings still read "you're booked."
/calls rows don't link to the star's page, so rebooking means remembering the handle.
Saving prices silently deletes the "Most booked" note on every row.
Admin money tabs compute from the last 200 bookings while overview uses the whole database; they diverge at scale.
A fan sitting on a star's page never sees LIVE flip on without reloading.
Going live flips LIVE before camera permission, so a denied-camera star shows LIVE for up to 90 seconds while fans can pay in.
The admin session token rides in recording URLs, landing in logs and browser history.
Both sides of a scheduled call upload ~200MB recordings each, the second overwriting the first, with no cleanup policy.
The 80/20 math lives in five separate places and gives the platform $0 on $1 and $2 calls.
A raw request can inject any name into a star's queue via an unused booking field.
The HTML escaper skips single quotes: safe today, a one-character footgun.
Expired sign-in links get a 3-second toast and no resend button.
If the server's time-up kick fails, the star's clock keeps counting with no enforcement on their side.
An expired session on the account page throws a raw error instead of redirecting to sign in.
The video relay is a public open relay, already flagged in code for swap before launch.
On a live star's page, the Book button silently depends on fields inside the jump-on box above it.
Stars can't edit their name or role, or remove a photo, despite the server supporting all three.
The decision that shaped the build
Scheduled calls are real appointments. So the product makes them real.
Your call: a booked time is when the call happens, not a maybe. That's now built — real timestamps, bookable hours set by the star that generate the slots, expiry when a slot passes, the bridge so a booked fan and a live star connect, cancel on both sides, and server-owned money so completion and payout never depend on anyone's browser.
The rehaul — what shipped
Stopped the bleeding (backend)
Anyone-is-admin hole closed behind an explicit DEV flag, server-side prices, server-side call completion, both-parties-present rule, reserved handles, booking expiry, one money ledger. Live.
The screens, approved then built
Studio (Live price + length, share-your-link, verification status), the public page + booking flow from real hours, the call wrap (rating + private note + report), and the admin console (Schedule, Analytics, edit-any-star, real Refunds + Reports). Two concepts everywhere: Live and Scheduled calls. Live.
Connective tissue
The scheduled-to-live bridge, the on-air "your caller is waiting" alert, payout endpoint wired. Live. (Real email waits on a vendor — stealth build has no inbox by design.)
Honesty + mobile pass
Test-mode banners on every money surface, the vocabulary pass, corrected tech-stack tab, and the full mobile rehaul (menu sheet, admin tab strip, tables→cards, real gated screens). Live. (joinpayphone.com stays dark on purpose until we leave stealth.)
Flagged & deferred — infra, not code
⚑ FLAGGEDPrivate TURN relay (P2-15). Swap the dev-grade open relay for a private one. → struck: code is swap-ready (window.PP_ICE); needs Cloudflare Realtime + a Calls-scoped token. Rides the Stripe-keys pass.
Payments: test-mode until Stripe keys — adapter flips live with the key, no code change. Real email/SMS: waiting on a vendor (stealth build has no inbox). Native app: the only true screenshot block, on the shelf until you want it.