← back to Professional Directory
agents/api-agent/routes/subscriptions.js
97 lines
/**
* /subscriptions — Stripe Checkout for paid tier upgrades.
*
* POST /subscriptions/checkout — { plan } → returns { url } for hosted Checkout
* GET /subscriptions/mine — current user's subscriptions
*
* Two products:
* - doctor_pro_monthly → STRIPE_PRICE_DOCTOR_PRO env var
* - patient_plus_monthly → STRIPE_PRICE_PATIENT_PLUS env var
*
* Webhook handling lives in data_market.js (extended) — that file already
* verifies signatures. Sub events upsert into the subscriptions table and
* flip users.tier accordingly.
*/
const express = require('express');
const Stripe = require('stripe');
const { query } = require('../../shared/db');
const router = express.Router();
const STRIPE_KEY = process.env.STRIPE_SECRET_KEY || '';
const stripe = STRIPE_KEY ? new Stripe(STRIPE_KEY) : null;
const PLAN_PRICES = {
doctor_pro_monthly: process.env.STRIPE_PRICE_DOCTOR_PRO || '',
patient_plus_monthly: process.env.STRIPE_PRICE_PATIENT_PLUS || '',
};
router.use((req, res, next) => {
if (!req.user) return res.status(401).json({ error: 'auth required' });
if (!stripe) return res.status(503).json({ error: 'Stripe not configured (set STRIPE_SECRET_KEY + STRIPE_PRICE_DOCTOR_PRO + STRIPE_PRICE_PATIENT_PLUS)' });
next();
});
router.post('/checkout', async (req, res, next) => {
try {
const plan = String(req.body?.plan || '');
const priceId = PLAN_PRICES[plan];
if (!priceId) return res.status(400).json({ error: 'unknown plan; allowed: ' + Object.keys(PLAN_PRICES).join(', ') });
// Doctor pro plan only available to verified-doctor accounts.
if (plan === 'doctor_pro_monthly' && req.user.role !== 'doctor' && req.user.role !== 'admin') {
return res.status(403).json({ error: 'doctor_pro_monthly requires a verified doctor account; submit a claim at /doctor-claims first' });
}
// Reuse existing Stripe customer or create one.
let customerId = req.user.stripe_customer_id;
if (!customerId) {
const c = await stripe.customers.create({
email: req.user.email,
metadata: { pd_user_id: String(req.user.id), pd_role: req.user.role },
});
customerId = c.id;
await query('UPDATE users SET stripe_customer_id = $1 WHERE id = $2', [customerId, req.user.id]);
}
const session = await stripe.checkout.sessions.create({
mode: 'subscription',
customer: customerId,
line_items: [{ price: priceId, quantity: 1 }],
success_url: `${req.protocol}://${req.get('host')}/?subscribed=${plan}`,
cancel_url: `${req.protocol}://${req.get('host')}/?cancelled=1`,
allow_promotion_codes: true,
metadata: { pd_user_id: String(req.user.id), plan },
});
res.json({ url: session.url, plan });
} catch (e) { next(e); }
});
router.get('/mine', async (req, res, next) => {
try {
const r = await query(`
SELECT id, stripe_subscription_id, plan, status, current_period_end,
cancel_at_period_end, started_at, ended_at
FROM subscriptions
WHERE user_id = $1
ORDER BY id DESC
`, [req.user.id]);
res.json({ count: r.rowCount, rows: r.rows, current_tier: req.user.tier });
} catch (e) { next(e); }
});
// Stripe Billing Portal redirect (manage card, cancel, etc).
router.get('/portal', async (req, res, next) => {
try {
if (!req.user.stripe_customer_id) return res.status(400).json({ error: 'no Stripe customer on file' });
const portal = await stripe.billingPortal.sessions.create({
customer: req.user.stripe_customer_id,
return_url: `${req.protocol}://${req.get('host')}/`,
});
res.json({ url: portal.url });
} catch (e) { next(e); }
});
module.exports = router;