Add to local .env and Heroku config vars:
STRIPE_SECRET_KEY=sk_live_... # Secret key from Stripe Dashboard
STRIPE_PRO_PRICE_ID=price_... # Pro tier price ID (€6.99/mo recurring)
STRIPE_WEBHOOK_SECRET=whsec_... # Webhook signing secretHeroku:
heroku config:set STRIPE_SECRET_KEY=sk_live_... --app edunode
heroku config:set STRIPE_PRO_PRICE_ID=price_... --app edunode
heroku config:set STRIPE_WEBHOOK_SECRET=whsec_... --app edunode- Get API keys — Developers → API keys → copy Secret key
- Create Pro product — Products → Add product → "EduNode Pro" with recurring €6.99/mo price → copy Price ID
- Register webhook — Developers → Webhooks → Add endpoint:
- URL:
https://edunode.herokuapp.com/api/stripe/webhook - Events to send:
checkout.session.completedinvoice.payment_succeededinvoice.payment_failedcustomer.subscription.updatedcustomer.subscription.deleted
- Copy the signing secret (
whsec_...)
- URL:
Headers: x-auth-token: <JWT>
Body: { successUrl?: string, cancelUrl?: string }
Response: { url: "https://checkout.stripe.com/...", sessionId: "cs_..." }
Headers: x-auth-token: <JWT>
Body: { returnUrl?: string }
Response: { url: "https://billing.stripe.com/..." }
Headers: x-auth-token: <JWT>
Response: { status, plan, currentPeriodEnd }
- Verifies
stripe-signatureheader against raw body - Returns
{ received: true }
Added to models/User.js:
stripeCustomerId— Stripe customer (cus_...)subscriptionId— Stripe subscription (sub_...)subscriptionStatus—active | canceled | past_due | none | ...subscriptionPlan—free | procurrentPeriodEnd— Date
- Webhook raw body: Mounted in
server.jsBEFOREbodyParser.json()so signature verification works. - Customer reuse: A Stripe customer is created on first checkout and stored on the user.
- Source of truth: Subscription state is updated via webhooks, not via the success redirect.
- Metadata:
userIdis attached to checkout sessions and subscriptions for webhook lookup.