Join our new Affiliate Program!
    Stripe Integration Guide: Building Payment Flows for SaaS

    payments

    development

    saas

    Stripe Integration Guide: Building Payment Flows for SaaS

    Introduction

    Integrating payments is one of the most critical aspects of building a SaaS product. Stripe has become the industry standard payment infrastructure, powering businesses of all sizes from startups to enterprises. This guide covers both one-time payments and subscription flows with Stripe, based on Stripe's official documentation.

    Before You Start Coding

    Before diving into implementation, several key considerations will save you time and prevent future refactoring. According to Stripe's planning guide, you should address these questions early:

    1. Account Structure Planning

    Decide whether you need a single Stripe account or multiple accounts:

    • Single account: Simplest approach for most businesses
    • Multiple accounts: Required for marketplaces, platforms with multiple merchants, or businesses operating in multiple legal entities

    For more detailed guidance on account structure, see Stripe's account structure documentation.

    2. Integration Security Requirements

    Ensure your implementation follows security best practices:

    • Use Stripe.js or Elements to avoid handling sensitive card data
    • Set up proper TLS/SSL for all communications
    • Understand PCI compliance requirements
    • Implement proper server-side validation

    Stripe provides a comprehensive integration security guide to help you maintain PCI compliance.

    3. Choosing Between Checkout and Elements

    Stripe offers two main ways to create payment forms, each with different tradeoffs:

    OptionsStripe CheckoutStripe.js and Elements
    DescriptionStripe Checkout is a secure, Stripe-hosted page that lets you collect payments quickly. It works across devices and can help increase conversion.Elements is a set of prebuilt UI components for building your custom checkout flow. Stripe.js tokenizes sensitive payment details without letting them touch your server.
    BenefitsSimplified integration
    Up-to-date with available payment methods
    Optimized conversion
    Co-branded with your business logo and colors
    Optimized conversion with dynamic inputs
    Simplified PCI compliance with SAQ A reporting
    Customizable styling to match your site
    LimitationsTemporarily redirects customers off your web domain
    Fewer options for customization
    Increased integration time and effort
    Elements doesn't support all payment methods

    For this guide, we'll focus on Checkout for its simplicity, but the concepts apply to Elements implementations as well.

    4. Business Model Selection

    For subscription businesses, determine which model fits your needs according to Stripe's subscription integration guide:

    • Pay up front: Collect payment details and charge before providing access
    • Free trial: Collect payment details, offer free period, then begin charging
    • Freemium: Provide limited access without payment details, charge for premium features

    One-Time Payment Integration

    Let's start with implementing a basic one-time payment flow using Stripe Checkout, Stripe's pre-built, hosted payment page.

    Step 1: Set Up Your Stripe Account and Install Dependencies

    First, create a Stripe account if you don't have one. Then install the necessary libraries:

    # Install Stripe server-side library in Node.js
    npm install stripe
     
    # For client-side integration
    npm install @stripe/stripe-js

    Step 2: Create a Product and Price in Stripe Dashboard

    Create your products and prices in the Stripe Dashboard, or programmatically:

    // Server-side code to create a product and price
    import Stripe from "stripe";
    const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
     
    // Create a product
    // Documentation: https://docs.stripe.com/api/products/create
    const product = await stripe.products.create({
      name: "Basic Plan",
      description: "One-time purchase for basic access",
    });
     
    // Create a price for the product
    // Documentation: https://docs.stripe.com/api/prices/create
    const price = await stripe.prices.create({
      product: product.id,
      unit_amount: 1999, // $19.99 in cents
      currency: "usd",
    });
     
    console.log(`Product created with ID: ${product.id}`);
    console.log(`Price created with ID: ${price.id}`);

    Make sure to store the price ID, as you'll need it for creating checkout sessions.

    Step 3: Create a Checkout Session (Server-Side)

    Following Stripe's Checkout documentation:

    // Next.js API route for creating a checkout session
    import { NextApiRequest, NextApiResponse } from "next";
    import Stripe from "stripe";
     
    const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
     
    export default async function handler(
      req: NextApiRequest,
      res: NextApiResponse
    ) {
      if (req.method !== "POST") {
        return res.status(405).json({ error: "Method not allowed" });
      }
     
      try {
        // Create the checkout session
        // Documentation: https://docs.stripe.com/api/checkout/sessions/create
        const session = await stripe.checkout.sessions.create({
          payment_method_types: ["card"],
          line_items: [
            {
              price: process.env.PRICE_ID, // Your actual Price ID from Step 2
              quantity: 1,
            },
          ],
          mode: "payment", // One-time payment
          success_url: `${req.headers.origin}/success?session_id={CHECKOUT_SESSION_ID}`,
          cancel_url: `${req.headers.origin}/canceled`,
          // Optional: Prefill customer email
          customer_email: req.body.email,
        });
     
        res.status(200).json({ sessionId: session.id, url: session.url });
      } catch (error) {
        console.error("Error creating checkout session:", error);
        res.status(500).json({ error: "Failed to create checkout session" });
      }
    }

    Step 4: Redirect to Checkout (Client-Side)

    // React component to handle checkout
    import React from "react";
    import { loadStripe } from "@stripe/stripe-js";
     
    // Initialize Stripe with your publishable key
    // Documentation: https://docs.stripe.com/js/initializing
    const stripePromise = loadStripe(
      process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
    );
     
    export default function CheckoutButton() {
      const handleCheckout = async () => {
        try {
          // Call your backend to create the Checkout Session
          const response = await fetch("/api/create-checkout-session", {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify({
              email: "[email protected]", // Optional: prefill customer email
            }),
          });
     
          const { url } = await response.json();
     
          // Redirect to Checkout
          window.location.href = url;
        } catch (error) {
          console.error("Error:", error);
        }
      };
     
      return (
        <button
          onClick={handleCheckout}
          className="bg-blue-600 text-white px-4 py-2 rounded"
        >
          Buy Now ($19.99)
        </button>
      );
    }

    Step 5: Handle Successful Payments with Webhooks

    Setting up webhooks is critical for reliable payment processing. Follow Stripe's webhook guide:

    // Next.js API route for Stripe webhooks
    import { NextApiRequest, NextApiResponse } from "next";
    import Stripe from "stripe";
    import { buffer } from "micro";
     
    const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
    const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
     
    export const config = {
      api: {
        bodyParser: false,
      },
    };
     
    export default async function handler(
      req: NextApiRequest,
      res: NextApiResponse
    ) {
      if (req.method !== "POST") {
        return res.status(405).end();
      }
     
      const buf = await buffer(req);
      const sig = req.headers["stripe-signature"];
     
      let event;
     
      try {
        // Verify webhook signature and extract the event
        // Documentation: https://docs.stripe.com/webhooks/signatures
        event = stripe.webhooks.constructEvent(buf.toString(), sig, webhookSecret);
      } catch (err) {
        console.error(`Webhook signature verification failed: ${err.message}`);
        return res.status(400).send(`Webhook Error: ${err.message}`);
      }
     
      // Handle specific events
      if (event.type === "checkout.session.completed") {
        const session = event.data.object;
     
        // Fulfill the order
        await fulfillOrder(session);
      }
     
      res.status(200).json({ received: true });
    }
     
    async function fulfillOrder(session) {
      // Implement your order fulfillment logic here
      // e.g., update database, grant access, send confirmation email
      console.log(`Order fulfilled for session ${session.id}`);
     
      // Retrieve the session with line items to get product details
      // Documentation: https://docs.stripe.com/api/checkout/sessions/retrieve
      const checkoutSession = await stripe.checkout.sessions.retrieve(session.id, {
        expand: ["line_items"],
      });
     
      // Now you can access product information
      const lineItems = checkoutSession.line_items;
      // Process the purchased items
    }

    Subscription Payment Integration

    Now, let's implement a subscription-based payment flow using Stripe Checkout, following Stripe's subscription guide.

    Step 1: Create Subscription Products and Prices

    // Server-side code to create subscription products and prices
    import Stripe from "stripe";
    const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
     
    // Create a product for subscription
    // Documentation: https://docs.stripe.com/api/products/create
    const product = await stripe.products.create({
      name: "Pro Plan",
      description: "Monthly subscription for premium features",
    });
     
    // Create a recurring price
    // Documentation: https://docs.stripe.com/api/prices/create
    const price = await stripe.prices.create({
      product: product.id,
      unit_amount: 2999, // $29.99 in cents
      currency: "usd",
      recurring: {
        interval: "month",
      },
    });
     
    console.log(`Subscription product created with ID: ${product.id}`);
    console.log(`Subscription price created with ID: ${price.id}`);

    Step 2: Create a Subscription Checkout Session

    // Next.js API route for creating a subscription checkout session
    import { NextApiRequest, NextApiResponse } from "next";
    import Stripe from "stripe";
     
    const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
     
    export default async function handler(
      req: NextApiRequest,
      res: NextApiResponse
    ) {
      if (req.method !== "POST") {
        return res.status(405).json({ error: "Method not allowed" });
      }
     
      const { priceId, customerId } = req.body;
     
      try {
        // Create the checkout session for subscription
        // Documentation: https://docs.stripe.com/api/checkout/sessions/create
        const session = await stripe.checkout.sessions.create({
          payment_method_types: ["card"],
          line_items: [
            {
              price: priceId, // Use the price ID from Step 1
              quantity: 1,
            },
          ],
          mode: "subscription", // Set mode to subscription
          success_url: `${req.headers.origin}/dashboard?session_id={CHECKOUT_SESSION_ID}`,
          cancel_url: `${req.headers.origin}/pricing`,
          // If you have an existing customer, pass their ID
          customer: customerId || undefined,
          customer_email: customerId ? undefined : req.body.email,
          // Enable automatic tax calculation if needed
          // automatic_tax: { enabled: true },
          // Allow promotion codes
          allow_promotion_codes: true,
        });
     
        res.status(200).json({ sessionId: session.id, url: session.url });
      } catch (error) {
        console.error("Error creating subscription session:", error);
        res.status(500).json({ error: "Failed to create subscription session" });
      }
    }

    Step 3: Set Up Customer Portal for Subscription Management

    Stripe's Customer Portal allows customers to manage their own subscriptions:

    // Next.js API route for creating a customer portal session
    import { NextApiRequest, NextApiResponse } from "next";
    import Stripe from "stripe";
     
    const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
     
    export default async function handler(
      req: NextApiRequest,
      res: NextApiResponse
    ) {
      if (req.method !== "POST") {
        return res.status(405).json({ error: "Method not allowed" });
      }
     
      const { customerId } = req.body;
     
      try {
        // Create customer portal session
        // Documentation: https://docs.stripe.com/api/customer_portal/sessions/create
        const session = await stripe.billingPortal.sessions.create({
          customer: customerId,
          return_url: `${req.headers.origin}/account`,
          // Optional: Configure the portal features
          // configuration: 'conf_xyz'
        });
     
        res.status(200).json({ url: session.url });
      } catch (error) {
        console.error("Error creating portal session:", error);
        res.status(500).json({ error: "Failed to create portal session" });
      }
    }

    Step 4: Handle Subscription Lifecycle Events

    Following Stripe's webhook events guide:

    // Handling subscription lifecycle events in webhook handler
    // Add these cases to your existing webhook handler
     
    async function handler(req: NextApiRequest, res: NextApiResponse) {
      // ... existing webhook setup code ...
     
      // Handle different event types
      // Documentation: https://docs.stripe.com/webhooks/stripe-events
      switch (event.type) {
        case "customer.subscription.created":
          // New subscription created
          await handleSubscriptionCreated(event.data.object);
          break;
     
        case "customer.subscription.updated":
          // Subscription updated (plan change, etc.)
          await handleSubscriptionUpdated(event.data.object);
          break;
     
        case "customer.subscription.deleted":
          // Subscription cancelled or expired
          await handleSubscriptionCancelled(event.data.object);
          break;
     
        case "invoice.payment_failed":
          // Failed payment for subscription
          await handleFailedPayment(event.data.object);
          break;
     
        case "invoice.paid":
          // Successful payment - extend access
          await handleSuccessfulPayment(event.data.object);
          break;
      }
     
      res.status(200).json({ received: true });
    }
     
    async function handleSubscriptionCreated(subscription) {
      // Grant access to your service
      // Update your database with new subscription status
      console.log(`New subscription started: ${subscription.id}`);
     
      // Retrieve the customer to get their email or custom metadata
      const customer = await stripe.customers.retrieve(subscription.customer);
     
      // Update your user records with subscription details
    }
     
    async function handleSubscriptionUpdated(subscription) {
      // Update access level based on new subscription plan
      console.log(`Subscription updated: ${subscription.id}`);
     
      // Check new status and items to update user permissions
      const newStatus = subscription.status;
      const items = subscription.items.data;
     
      // Update your database accordingly
    }
     
    async function handleSubscriptionCancelled(subscription) {
      // Remove access or downgrade to free tier
      console.log(`Subscription cancelled: ${subscription.id}`);
     
      // Check if access should end immediately or at period end
      const endDate = subscription.cancel_at || subscription.current_period_end;
    }
     
    async function handleFailedPayment(invoice) {
      // Notify customer, retry payment, or temporarily restrict access
      console.log(`Payment failed for invoice: ${invoice.id}`);
     
      // Get customer information
      const customer = await stripe.customers.retrieve(invoice.customer);
     
      // Send email notification about failed payment
    }
     
    async function handleSuccessfulPayment(invoice) {
      // Extend subscription access period
      console.log(`Payment succeeded for invoice: ${invoice.id}`);
     
      // Update subscription status in your database
    }

    Advanced Stripe Features for SaaS

    Free Trial Implementation

    To offer a free trial with Stripe, follow the free trial documentation:

    // Documentation: https://docs.stripe.com/api/checkout/sessions/create
    const session = await stripe.checkout.sessions.create({
      // ... other parameters ...
      line_items: [
        {
          price: process.env.SUBSCRIPTION_PRICE_ID, // Your subscription price ID
          quantity: 1,
        },
      ],
      mode: "subscription",
      subscription_data: {
        trial_period_days: 14, // 14-day free trial
      },
      payment_method_collection: "always", // Collect payment method upfront
    });

    Usage-Based Billing

    For metered billing where you charge based on usage, follow Stripe's metered billing guide:

    1. Create a metered price:
    // Documentation: https://docs.stripe.com/api/prices/create
    const price = await stripe.prices.create({
      product: process.env.PRODUCT_ID,
      unit_amount: 100, // $1.00 per unit
      currency: "usd",
      recurring: {
        interval: "month",
        usage_type: "metered", // Specify metered billing
      },
    });
    1. Report usage:
    // Documentation: https://docs.stripe.com/api/usage_records/create
    await stripe.subscriptionItems.createUsageRecord(
      "si_123ABC", // Subscription item ID
      {
        quantity: 10, // Number of units used
        timestamp: Math.floor(Date.now() / 1000),
        action: "increment", // Add to the existing usage
      }
    );

    Apply Discount Coupons

    To offer discount coupons, follow Stripe's discount documentation:

    // Documentation: https://docs.stripe.com/api/checkout/sessions/create
    const session = await stripe.checkout.sessions.create({
      // ... other parameters ...
      discounts: [
        {
          coupon: "SUMMER20", // Coupon code created in Stripe Dashboard
        },
      ],
    });

    Best Practices for Stripe Integration

    1. Always Use Webhooks

    Don't rely solely on client-side success redirects. Use webhooks to reliably track payment outcomes and subscription status changes. Stripe provides guidance in their webhook best practices.

    2. Implement Idempotency

    Use idempotency keys for API requests that might be retried to prevent duplicate transactions:

    // Documentation: https://docs.stripe.com/api/idempotent_requests
    await stripe.customers.create(
      { email: "[email protected]" },
      { idempotencyKey: "unique-key-123" }
    );

    3. Test Thoroughly with Stripe CLI

    Use the Stripe CLI to test webhooks locally:

    # Install the CLI (macOS)
    brew install stripe/stripe-cli/stripe
     
    # Forward webhooks to your local server
    stripe listen --forward-to localhost:3000/api/webhooks

    4. Store Stripe Customer IDs

    Always store Stripe customer IDs in your database to link users with their payment history and subscriptions:

    // Create a mapping in your database between your user ID and Stripe customer ID
    db.users.update({ id: userId }, { stripeCustomerId: customer.id });

    5. Handle Failed Payments Gracefully

    Implement proper failed payment handling with appropriate retry logic and customer communication. Stripe provides automatic retries for failed subscription payments.

    Conclusion

    Integrating Stripe into your SaaS application gives you a robust payment infrastructure that scales with your business. By properly implementing both one-time and subscription payment flows, you can offer your customers flexible payment options while maintaining a secure and compliant payment system.

    For more complex use cases like metered billing, free trials, or marketplace payments, Stripe provides extensive documentation and SDKs to handle virtually any payment scenario. Remember to always follow security best practices, properly test your integration, and set up reliable webhook handling to ensure your payment flows work flawlessly.

    Additional Resources

    Fekri

    Fekri

    Related Blogs

    AI Model Deployment: Expert Strategies to Deploy Successfully

    ai model deployment

    MLOps

    production AI

    model serving

    deployment strategy

    AI Model Deployment: Expert Strategies to Deploy Successfully

    Learn essential AI model deployment techniques from industry experts. Discover proven methods to deploy your AI models efficiently and confidently.

    Fekri

    Fekri

    May 11, 2025

    AI MVP Development: Build Smarter & Launch Faster

    ai mvp development

    product innovation

    startup technology

    artificial intelligence

    lean development

    AI MVP Development: Build Smarter & Launch Faster

    Learn top strategies for AI MVP development to speed up your product launch and outperform competitors. Start building smarter today!

    Fekri

    Fekri

    May 12, 2025

    Top AI Prototyping Tools of 2025 to Boost Your Projects

    ai prototyping tools

    ai tools

    prototyping

    ai development

    ux design

    Top AI Prototyping Tools of 2025 to Boost Your Projects

    Discover the best AI prototyping tools of 2025. Find the perfect platform to elevate your AI projects and streamline development today!

    Fekri

    Fekri

    May 13, 2025

    Build
    faster using AI templates.

    AnotherWrapper gives you the foundation to build and ship fast. No more reinventing the wheel.

    Fekri — Solopreneur building AI startups
    Founder's Note

    Hi, I'm Fekri 👋

    @fekdaoui

    Over the last 15 months, I've built around 10 different AI apps. I noticed I was wasting a lot of time on repetitive tasks like:

    • Setting up tricky APIs
    • Generating vector embeddings
    • Integrating different AI models into a flow
    • Handling user input and output
    • Authentication, paywalls, emails, ...

    So I built something to make it easy.

    Now I can build a new AI app in just a couple of hours, leveraging one of the 10+ different AI demo apps.

    10+ ready-to-use apps

    10+ AI app templates to kickstart development

    Complete codebase

    Auth, payments, APIs — all integrated

    AI-ready infrastructure

    Vector embeddings, model switching, RAG

    Production-ready

    Secure deployment, rate limiting, error handling

    Get AnotherWrapper

    One-time purchase, lifetime access

    $249

    Pay once, use forever

    FAQ
    Frequently asked questions

    Have questions before getting started? Here are answers to common questions about AnotherWrapper.

    Still have questions? Email us at [email protected]