PPV composer money bugs
Three composer-layer mistakes that are invisible on screen — dollars not converted to cents, a dropped paywall lock, and a preview id outside the attached media.
The client code that builds a PPV send's request body calls these out explicitly as "errors invisible on screen" — none of them throw, none of them look wrong in the composer UI, and each one is a real-money or real-content mistake if it slips through.
1. Dollars not converted to cents
The price input is dollars; the API takes price_cents. Skip the conversion (or round it wrong)
and a fan gets charged 100× too little — a $20 send going out priced at $0.20, silently accepted
by everything downstream because $0.20 is still a valid non-negative integer.
The guard: the composer's own price function always does Math.round(parseFloat(input) * 100)
and clamps garbage or an empty field to 0 — it never passes a raw dollar figure through to the
send payload.
2. locked dropped when price is set
A PPV message needs locked: true for OnlyFans to actually paywall it. Send a price without the
lock flag and the attachment can ship unlocked — a fully paid file the fan gets for free, with the
price still showing on the message as if it worked.
The guard: the composer derives whether a send is a real PPV from one pair of conditions —
"paid-content mode is on" and "price > 0" — and only in that case does it set
price_cents, locked: true, and any preview_ids at all. A send that fails either half of that
pair goes out as a plain free message, byte-identical to one from before PPV existed — there's no
in-between state where a price is set but the lock silently isn't.
3. A preview id outside the attached media
OnlyFans requires every teaser/preview id to be a subset of the message's attached media ids. If you unmark or remove an attachment that was still flagged as a preview, and the stale preview id survives into the request, OnlyFans rejects the send outright rather than silently ignoring the bad id.
The guard: the composer explicitly filters preview_ids down to whatever's still in
media_ids before building the payload, on every attachment change — a preview can't outlive the
attachment it belongs to.
Where these guards actually live
All three rules sit together in the composer's own send-payload code, deliberately pulled out of the screen component and covered by tests specifically because — per the code's own comment — this is exactly the kind of mistake that "passes silently and is expensive." If you're extending the composer, that's the one file to touch, not the screen.
What's next
- Send a priced PPV message — the normal flow these guards protect.
- Reply to a waiting fan — the plain-text send path these rules leave untouched.
Two campaign systems, both live today
Why a broadcast created under /broadcasts never shows up in the in-chat "Broadcast" composer (or vice versa) — two independent systems share the word "campaign" but aren't merged.
Money masking and chargeback timing
Why a money-hidden request can fail loudly instead of degrading quietly, and why old chargebacks never appear in the recent list.