🟫 ClodHost beta
Sign In
🎉 All services free during beta! 🎉 All services free during beta!

Adding Stripe Payments

Accept one-time payments and recurring subscriptions. Handle checkout, webhooks, and customer management.

Reading time: 30 min Difficulty: Advanced
View examples as:
Ask Claude Manual Code

Table of Contents

Why Stripe?

Stripe is the most developer-friendly payment processor:

Account & API Setup

Create a Stripe Account

  1. Go to dashboard.stripe.com/register
  2. Create your account with email and password
  3. Verify your email address
  4. You're now in test mode by default - perfect for development

Get Your API Keys

  1. Go to Developers → API Keys
  2. Copy your Publishable key (starts with pk_test_)
  3. Copy your Secret key (starts with sk_test_)
  4. Store them as environment variables:
STRIPE_PUBLISHABLE_KEY=pk_test_xxx
STRIPE_SECRET_KEY=sk_test_xxx
Keep Secret Key Safe

Never expose your secret key in frontend code or commit it to git. Only use it on your backend server.

Stripe Checkout

The easiest way to accept payments - Stripe hosts the checkout page.

Create Checkout Session

Example prompt:

"Add Stripe Checkout to my e-commerce app. Create an API endpoint that accepts a cart with product IDs and quantities, creates a Stripe Checkout session, and returns the checkout URL. After payment, redirect to /success with the session ID."
// Install Stripe
npm install stripe

// routes/checkout.js
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

router.post('/create-checkout-session', async (req, res) => {
    const { items } = req.body; // [{ priceId, quantity }]

    const session = await stripe.checkout.sessions.create({
        payment_method_types: ['card'],
        line_items: items.map(item => ({
            price: item.priceId, // Stripe Price ID
            quantity: item.quantity,
        })),
        mode: 'payment',
        success_url: `${process.env.DOMAIN}/success?session_id={CHECKOUT_SESSION_ID}`,
        cancel_url: `${process.env.DOMAIN}/cart`,
        customer_email: req.user?.email, // Optional: pre-fill email
    });

    res.json({ url: session.url });
});

// Frontend: redirect to Stripe
async function checkout(cart) {
    const response = await fetch('/api/create-checkout-session', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ items: cart }),
    });
    const { url } = await response.json();
    window.location.href = url;
}

Handling Webhooks

Webhooks notify your server when payments complete, subscriptions renew, etc.

Set Up Webhook Endpoint

Example prompt:

"Create a Stripe webhook endpoint at /api/webhooks/stripe. Handle checkout.session.completed to create orders in the database and send confirmation emails. Verify the webhook signature for security. Handle invoice.paid for subscription renewals."
// routes/webhooks.js
// IMPORTANT: Use raw body for webhook verification
router.post('/stripe',
    express.raw({ type: 'application/json' }),
    async (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) {
        console.error('Webhook signature verification failed');
        return res.status(400).send(`Webhook Error: ${err.message}`);
    }

    // Handle the event
    switch (event.type) {
        case 'checkout.session.completed':
            const session = event.data.object;
            await createOrder(session);
            await sendConfirmationEmail(session.customer_email);
            break;

        case 'invoice.paid':
            const invoice = event.data.object;
            await extendSubscription(invoice.customer);
            break;

        case 'customer.subscription.deleted':
            await cancelSubscription(event.data.object.customer);
            break;
    }

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

Get your webhook secret from Stripe Dashboard → Developers → Webhooks → Add endpoint. Store it as STRIPE_WEBHOOK_SECRET.

Subscriptions

Charge customers on a recurring basis (monthly, yearly, etc.).

Create Subscription Products

  1. Go to Products in Stripe Dashboard
  2. Click "Add product"
  3. Enter name, description, and set pricing (e.g., $10/month)
  4. Copy the Price ID (starts with price_)

Subscription Checkout

Example prompt:

"Add subscription billing to my SaaS app. I have three plans: Basic ($9/mo), Pro ($29/mo), and Enterprise ($99/mo). Create a pricing page that shows all plans. When a user clicks 'Subscribe', create a Stripe Checkout session for that subscription. After successful payment, update their subscription status in the database."
router.post('/create-subscription', requireAuth, async (req, res) => {
    const { priceId } = req.body; // Stripe Price ID

    // Get or create Stripe customer
    let customerId = req.user.stripeCustomerId;
    if (!customerId) {
        const customer = await stripe.customers.create({
            email: req.user.email,
            metadata: { userId: req.user.id }
        });
        customerId = customer.id;
        await updateUser(req.user.id, { stripeCustomerId: customerId });
    }

    const session = await stripe.checkout.sessions.create({
        customer: customerId,
        payment_method_types: ['card'],
        line_items: [{ price: priceId, quantity: 1 }],
        mode: 'subscription', // Key difference from one-time payment
        success_url: `${process.env.DOMAIN}/dashboard?subscribed=true`,
        cancel_url: `${process.env.DOMAIN}/pricing`,
    });

    res.json({ url: session.url });
});

Customer Portal

Let customers manage their subscriptions, update payment methods, and view invoices.

Portal Integration

Example prompt:

"Add a 'Manage Subscription' button that opens Stripe's Customer Portal. Users should be able to update their payment method, switch plans, and cancel their subscription from there."
router.post('/create-portal-session', requireAuth, async (req, res) => {
    const customerId = req.user.stripeCustomerId;

    if (!customerId) {
        return res.status(400).json({ error: 'No subscription found' });
    }

    const session = await stripe.billingPortal.sessions.create({
        customer: customerId,
        return_url: `${process.env.DOMAIN}/settings`,
    });

    res.json({ url: session.url });
});

// Frontend
async function openPortal() {
    const response = await fetch('/api/create-portal-session', { method: 'POST' });
    const { url } = await response.json();
    window.location.href = url;
}

Testing Payments

Stripe provides test card numbers for development.

Card Number Result
4242 4242 4242 4242 Successful payment
4000 0000 0000 0002 Card declined
4000 0000 0000 3220 Requires 3D Secure

Use any future expiration date (e.g., 12/34) and any 3-digit CVC.

Local Webhook Testing

Use the Stripe CLI to forward webhooks to your local server: stripe listen --forward-to localhost:3000/api/webhooks/stripe