-- Streams (live or recorded) CREATE TABLE streams ( id BIGSERIAL PRIMARY KEY, model_id BIGINT REFERENCES users(id), title VARCHAR(255), is_live BOOLEAN DEFAULT FALSE, start_time TIMESTAMP, end_time TIMESTAMP, is_premium BOOLEAN DEFAULT FALSE, -- true = requires purchase/subscription price_cents INTEGER, -- optional PPV price thumbnail_url VARCHAR(255) ); | Method | URL | Description | Auth | |--------|-----|-------------|------| | GET /api/plans | List all subscription plans | Public | | POST /api/subscriptions | Create a Stripe Checkout session for a plan | Viewer (JWT) | | POST /api/webhooks/stripe | Handle subscription events ( invoice.payment_succeeded , customer.subscription.deleted ) | Stripe secret | | GET /api/streams/:id | Retrieve stream metadata; includes access flag | Viewer (JWT) | | POST /api/purchases/:streamId | Create a PPV checkout session for a specific stream | Viewer (JWT) | | GET /api/user/me | Current user profile + subscription status | Viewer/Model (JWT) |
if (event.type === 'checkout.session.completed') const session = event.data.object; const userId, streamId = session.metadata; camwhores.v
router.post('/:streamId', requireAuth, async (req, res) => const userId = req.user.id; const streamId = req.params; -- Streams (live or recorded) CREATE TABLE streams
useEffect(() => async function load() const data = await axios.get(`/api/streams/$id`); setStream(data); setHasAccess(data.access); setLoading(false); load(); , [id]); model_id BIGINT REFERENCES users(id)
try event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET); catch (err) console.error('⚠️ Webhook signature verification failed.', err); return res.sendStatus(400);