Camwhores.v May 2026

if (event.type === 'checkout.session.completed') const session = event.data.object; const userId, streamId = session.metadata;

-- Plans (subscription tiers) CREATE TABLE subscription_plans ( id BIGSERIAL PRIMARY KEY, name VARCHAR(100) NOT NULL, price_cents INTEGER NOT NULL, -- stored in cents interval VARCHAR(10) NOT NULL, -- 'month' or 'year' description TEXT );

router.post('/:streamId', requireAuth, async (req, res) => const userId = req.user.id; const streamId = req.params; camwhores.v

// 1️⃣ Validate stream & price const stream = await Stream.findByPk(streamId); if (!stream );

// Update purchase record await Purchase.update( stripe_charge_id: session.payment_intent , where: user_id: userId, stream_id: streamId, stripe_charge_id: null ); if (event

-- Subscriptions (active recurring) CREATE TABLE user_subscriptions ( id BIGSERIAL PRIMARY KEY, user_id BIGINT REFERENCES users(id) ON DELETE CASCADE, plan_id BIGINT REFERENCES subscription_plans(id), stripe_sub_id VARCHAR(255) UNIQUE, status VARCHAR(20) CHECK (status IN ('active','canceled','past_due')), current_period_end TIMESTAMP, created_at TIMESTAMP DEFAULT NOW() );

// Handle subscription events similarly… streamId = session.metadata

useEffect(() => async function load() const data = await axios.get(`/api/streams/$id`); setStream(data); setHasAccess(data.access); setLoading(false); load(); , [id]);