The LMS backend delegates payment collection to Stripe Checkout. Your client never handles raw card data — it receives a Stripe-hosted session URL, redirects the user there, and waits for the server to confirm the outcome through a webhook. This page covers every step a client needs to implement that flow correctly.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/Pragyat-Nikunj/Learning-Management-System-backend/llms.txt
Use this file to discover all available pages before exploring further.
Environment variables
Before making any payment calls, ensure these variables are set on the server:| Variable | Purpose |
|---|---|
STRIPE_SECRET_KEY | Authenticates all Stripe API requests |
STRIPE_WEBHOOK_SECRET | Validates the signature on every incoming webhook event |
STRIPE_SUCCESS_URL | Where Stripe redirects after successful payment (defaults to http://localhost:5173/payment/success?session_id={CHECKOUT_SESSION_ID}) |
STRIPE_CANCEL_URL | Where Stripe redirects when the user cancels (defaults to http://localhost:5173/payment/cancel) |
Payment flow
Create a checkout session
Your client sends a Response
POST request with the courseId the user wants to buy. The server looks up the course, creates a Stripe Checkout session priced in USD, records a CoursePurchase document with status pending, and returns the full session object.This endpoint requires authentication. The
isAuthenticated middleware reads the user ID from the request, so the user must be logged in before initiating checkout.Redirect the user to Stripe
Extract
data.session.url from the response and redirect the browser there. Stripe hosts the payment form — no card UI is needed in your frontend.Handle post-payment redirects
After payment, Stripe redirects the user back to your app:
- Success:
STRIPE_SUCCESS_URL— thesession_idquery parameter is appended automatically when you use{CHECKOUT_SESSION_ID}in the URL template. - Cancel:
STRIPE_CANCEL_URL— the user abandoned checkout; the purchase remainspendingin the database.
Stripe calls the webhook
After a charge settles, Stripe sends a
The webhook responds with
POST request to POST /api/v1/payments/webhook. The server verifies the stripe-signature header and updates the CoursePurchase record accordingly.| Event | Result |
|---|---|
charge.succeeded | Purchase status set to completed |
charge.failed | Purchase status set to failed |
{"received": true} on success. Any other event type is logged and ignored.Purchase status lifecycle
ACoursePurchase document moves through these states:
refunded status exists in the model but is not set by the Stripe webhook handler — it is reserved for manual refund processing.
Webhook setup
Why express.raw is required
The webhook route is registered with express.raw({ type: 'application/json' }) instead of the standard JSON body parser:
stripe.webhooks.constructEvent) requires the raw, unmodified request body as a Buffer. If any middleware parses the body into a JavaScript object first, the bytes change and the signature check will always fail with a 400 error.
Test webhooks locally with Stripe CLI
Run the Stripe CLI listener to forward live Stripe events to your local server:STRIPE_WEBHOOK_SECRET in your .env for local testing. Stop the listener with Ctrl+C when done.
Install the Stripe CLI from stripe.com/docs/stripe-cli before running the command above.
Related endpoints
GET /api/v1/payments/courses/:courseId/purchase-status
GET /api/v1/payments/courses/:courseId/purchase-status
Returns the purchase status for the authenticated user and a given course. Use this after the Stripe redirect to decide whether to unlock course content.Response
purchaseStatus will be "not purchased" if no record exists.GET /api/v1/payments/purchased-courses
GET /api/v1/payments/purchased-courses
Returns all courses the authenticated user has successfully purchased (status
completed), sorted newest first.Response shape