Skip to main content

Overview

EducaStream uses Stripe to process course payments securely. The integration handles:
  • Shopping cart checkout
  • Payment session creation
  • Redirect to Stripe-hosted checkout
  • Payment confirmation and fulfillment
Stripe handles all sensitive payment data, so you never need to process credit card information directly.

Stripe Dependencies

EducaStream uses the following Stripe packages from package.json:14-15,27:
"dependencies": {
  "@stripe/react-stripe-js": "^2.3.1",
  "@stripe/stripe-js": "^2.1.11",
  "stripe": "^14.3.0"
}

@stripe/stripe-js

Official Stripe.js library for client-side

@stripe/react-stripe-js

React components for Stripe Elements

stripe

Server-side Stripe SDK (backend)

Setup Stripe Account

1

Create Stripe Account

  1. Go to Stripe Dashboard
  2. Sign up for a new account
  3. Complete account verification
2

Get API Keys

  1. Navigate to DevelopersAPI Keys
  2. Copy your Publishable key (starts with pk_)
  3. Copy your Secret key (starts with sk_)
Keep your Secret key secure! Never commit it to version control or expose it in client-side code.
3

Configure Test Mode

For development, use Stripe’s test mode:
  • Toggle Test mode in the top right of the dashboard
  • Use test API keys (pk_test_… and sk_test_…)
  • Use test card numbers for testing

Environment Variables

Add your Stripe keys to environment variables:
# Stripe Publishable Key (safe for client-side)
VITE_STRIPE_PUBLIC_KEY=pk_test_51XXXXXXXXXXXXXXXXXXXXX

Payment Flow

The payment implementation from src/Components/PayButton/Paybutton.jsx:1-35:
Paybutton.jsx
import axios from "axios";
import style from "./Paybutton.module.css";

const PayButton = ({ text, disabled }) => {
  const user = JSON.parse(localStorage.getItem("userOnSession"));
  const cartItems = JSON.parse(localStorage.getItem("cart"));
  
  const handleCheckout = () => {
    axios
      .post(`/payment/create-checkout-session`, {
        cartItems,
        userId: user.id,
      })
      .then((response) => {
        if (response.data.url) {
          localStorage.setItem("payment", JSON.stringify(response.data));
          window.location.href = response.data.url;
        }
      })
      .catch((err) => console.log(err.message));
  };

  return (
    <>
      <button
        className={style.finalizePurchase}
        onClick={() => handleCheckout()}
        disabled={disabled}
      >
        {text}
      </button>
    </>
  );
};

export default PayButton;

Payment Process

1

User Initiates Checkout

When user clicks the PayButton:
  • Cart items retrieved from localStorage
  • User ID retrieved from session
  • Request sent to backend
2

Backend Creates Checkout Session

Backend endpoint /payment/create-checkout-session:
  • Validates cart items and user
  • Creates Stripe Checkout Session
  • Returns session URL
3

Redirect to Stripe

User is redirected to Stripe-hosted checkout:
  • Secure payment form
  • Support for cards, Apple Pay, Google Pay
  • PCI-compliant payment processing
4

Payment Completion

After successful payment:
  • User redirected to /payment/checkout/sucess
  • Payment data stored in localStorage
  • Order fulfillment triggered

Backend Implementation

Your backend needs to implement the checkout session creation:
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

app.post('/payment/create-checkout-session', async (req, res) => {
  const { cartItems, userId } = req.body;

  try {
    // Create line items from cart
    const lineItems = cartItems.map(item => ({
      price_data: {
        currency: 'usd',
        product_data: {
          name: item.name,
          description: item.description,
          images: [item.image],
        },
        unit_amount: item.price * 100, // Convert to cents
      },
      quantity: 1,
    }));

    // Create Stripe Checkout Session
    const session = await stripe.checkout.sessions.create({
      payment_method_types: ['card'],
      line_items: lineItems,
      mode: 'payment',
      success_url: `${process.env.CLIENT_URL}/payment/checkout/sucess?session_id={CHECKOUT_SESSION_ID}`,
      cancel_url: `${process.env.CLIENT_URL}/cart/${userId}`,
      metadata: {
        userId: userId,
      },
    });

    res.json({ url: session.url });
  } catch (error) {
    console.error('Stripe error:', error);
    res.status(500).json({ error: error.message });
  }
});

Success Page Implementation

The success route is defined in App.jsx:120-122:
<Route
  path="/payment/checkout/sucess"
  element={<CheckOut updateContextUser={updateContextUser} />}
/>
Implement the CheckOut component to handle successful payments:
CheckOut.jsx
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';

export const CheckOut = ({ updateContextUser }) => {
  const navigate = useNavigate();

  useEffect(() => {
    const processPayment = async () => {
      // Get session ID from URL
      const params = new URLSearchParams(window.location.search);
      const sessionId = params.get('session_id');

      if (sessionId) {
        // Verify payment with backend
        // Update user's purchased courses
        // Clear cart
        localStorage.removeItem('cart');
        
        // Redirect to student dashboard
        const user = JSON.parse(localStorage.getItem('userOnSession'));
        navigate(`/student/${user.id}`);
      }
    };

    processPayment();
  }, []);

  return (
    <div>
      <h1>Payment Successful!</h1>
      <p>Thank you for your purchase. Redirecting...</p>
    </div>
  );
};

Webhooks for Payment Verification

For production, implement Stripe webhooks to verify payments:
app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
  const sig = req.headers['stripe-signature'];
  let event;

  try {
    event = stripe.webhooks.constructEvent(
      req.body,
      sig,
      process.env.STRIPE_WEBHOOK_SECRET
    );
  } catch (err) {
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  // Handle the event
  switch (event.type) {
    case 'checkout.session.completed':
      const session = event.data.object;
      // Fulfill the purchase
      fulfillOrder(session);
      break;
    case 'payment_intent.payment_failed':
      const paymentIntent = event.data.object;
      // Handle failed payment
      console.log('Payment failed:', paymentIntent.id);
      break;
    default:
      console.log(`Unhandled event type ${event.type}`);
  }

  res.json({received: true});
});

Testing Payments

Test Card Numbers

Use these test cards in Stripe test mode:

Successful Payment

4242 4242 4242 4242Any future expiry, any CVC

Payment Declined

4000 0000 0000 0002Card declined

Insufficient Funds

4000 0000 0000 9995Insufficient funds

3D Secure

4000 0025 0000 3155Requires authentication

Testing Workflow

1

Add Items to Cart

Browse courses and add them to cart using the CartContext
2

Proceed to Checkout

Click the PayButton component to initiate checkout
3

Complete Payment

Use a test card number on Stripe’s checkout page
4

Verify Success

Confirm redirect to success page and cart is cleared

Cart Integration

The cart is managed by CartContext.jsx:1-117 with localStorage persistence:
const initialState = {
  cart: JSON.parse(localStorage.getItem("cart")) || [],
};

const cartReducer = (state = initialState, action) => {
  switch (action.type) {
    case ADD_TO_CART:
      // Prevent duplicate courses
      const isCourseInCart = state.cart.find(
        (product) => product.id === action.payload.id
      );
      
      if (isCourseInCart) {
        // Show warning toast
        return state;
      }
      
      return {
        ...state,
        cart: [...state.cart, action.payload],
      };
      
    case REMOVE_FROM_CART:
      const updatedCart = state.cart.filter(
        (product) => product.id !== action.payload
      );
      return { ...state, cart: updatedCart };
      
    case CLEAR:
      return { ...state, cart: action.payload };
      
    default:
      return state;
  }
};

Security Best Practices

Never expose Stripe Secret Key in frontend code
Always validate cart items on the backend
Verify payment amounts server-side
Use webhooks for payment confirmation
Implement idempotency keys for duplicate requests
Enable Stripe Radar for fraud detection
Use HTTPS in production

Common Issues

Ensure your backend has proper CORS configuration:
app.use(cors({
  origin: process.env.CLIENT_URL,
  credentials: true
}));
  • Verify you’re using the correct key (test vs live)
  • Check the key is properly set in environment variables
  • Ensure no extra spaces in the key
  • Implement webhook handlers
  • Check webhook signing secret is correct
  • Verify webhook endpoint is publicly accessible
  • Check success_url and cancel_url are correct
  • Verify URLs are properly encoded
  • Ensure frontend routes exist

Production Checklist

1

Switch to Live Mode

  • Use live API keys (pk_live_… and sk_live_…)
  • Update environment variables
  • Complete Stripe account activation
2

Configure Webhooks

  • Set up webhook endpoint
  • Add webhook secret to environment
  • Test webhook delivery
3

Enable Security Features

  • Turn on Stripe Radar
  • Configure 3D Secure
  • Set up fraud detection rules
4

Test End-to-End

  • Complete test purchase in production
  • Verify order fulfillment
  • Check email notifications

Monitoring Payments

In Stripe Dashboard:
  • Payments - View all transactions
  • Customers - Manage customer data
  • Products - Configure course products
  • Reports - Analyze revenue and trends

Next Steps

Architecture

Understand the full application architecture

Firebase Config

Configure Firebase Storage

Additional Resources

Stripe Checkout Docs

Official Stripe Checkout documentation

Testing Stripe

Stripe testing guide with test card numbers

Build docs developers (and LLMs) love