Heizen
About UsPartner With UsContact UsPrivacy PolicyRefund PolicyTerms and Conditions
ISO 27001 CertifiedSOC 2 Type 2 Compliant
© 2026 Heizen. All Rights Reserved

HEIZEN

Heizen
About Us
Case StudiesBlogsCareers
About Us
Offerings
Procurement
Events
Supply Chain and Logistics Summit, DubaiManifest 2026, Las VegasFounder Events by HeizenISM World 2026
Whitepapers
AI in Supply Chain PlanningAI Won’t Fix Your Supply Chain Chaos
Case StudiesBlogsCareers
Hero background

Setting Up Stripe Payments: A Step-by-Step Guide

Integrating Stripe for handling payments, especially subscriptions, can be a complex process. This blog documents the step-by-step approach I followed to integrate Stripe payments into my application

Nilesh Darji
Nilesh Darji
•February 19, 2025•6 min read
Setting Up Stripe Payments: A Step-by-Step Guide

Integrating Stripe for handling payments, especially subscriptions, can be a complex process. This blog documents the step-by-step approach I followed to integrate Stripe payments into my application. This guide serves as a reference for future use and provides context for large language models (LLMs).

Step 1: Define Pricing Details

To maintain structured pricing, we create a JSON schema that includes pricing tiers with their respective Stripe price_id values.

interface PricingTier {
  name: string;
  description: string;
  features: Feature[];
  priceId?: string;
  disabled?: boolean;
}

Step 2: Create a Stripe Checkout Session

Frontend Code

When a user selects a pricing plan, we send a request to the backend API to create a checkout session.

sessionStorage.setItem('pendingPayment', 'true');
const { data } = await apiClient.post('/api/stripe/checkout', {
  email: session?.user?.email,
  planName: name,
});

Backend Code

The backend retrieves the selected plan and its corresponding price_id, then creates a Stripe checkout session.

const selectedTier = tiers.find((tier) => tier.name === planName);

const priceId = selectedTier.priceId;

const session = await stripe.checkout.sessions.create({
  payment_method_types: ['card'],
  mode: 'subscription',
  billing_address_collection: 'auto',
  customer_email: email,
  allow_promotion_codes: true,
  line_items: [
    {
      price: priceId,
      quantity: 1,
    },
  ],

  success_url: ${process.env.NEXT_PUBLIC_BASE_URL}/?success=true&session_id={CHECKOUT_SESSION_ID}#pricing,
  cancel_url: ${process.env.NEXT_PUBLIC_BASE_URL}/?canceled=true#pricing,
});
return NextResponse.json({ sessionId: session.id, url: session.url });

In frontend code we set a session variable and in backend the success and cancel URLs. We pass certain parameters in URL which are helpful to validate the payment from the user and perform necessary operations in frontend.

Step 3: Verify Payment

Frontend Code

After the payment is completed, we verify its success by checking the URL parameters and making an API call to verify the session.

const pendingPayment = sessionStorage.getItem('pendingPayment');
sessionStorage.removeItem('pendingPayment');

const success = searchParams.get('success');
const sessionId = searchParams.get('session_id');
const canceled = searchParams.get('canceled');

if (success === 'true' && sessionId) {
    toast.promise(
      apiClient.get(`/api/stripe/verify-payment?session_id=${sessionId}`).then((response) => {
        if (response.data.success) {
           // Perform operations in UI like session/redirect or others on success.
        }
        return response;
      }),
      {
        loading: 'Verifying payment...',
        success: 'Plan purchased successfully!',
        error: 'Unable to verify payment',
      },
    );
  }
}

Backend Code

The backend verifies the payment status by checking the session details in Stripe.

export async function GET(req: Request) {
  try {
    const { searchParams } = new URL(req.url);
    const sessionId = searchParams.get('session_id');

    if (!sessionId) {
      return NextResponse.json({ error: 'Session ID is required' }, { status: 400 });
    }

    const session = await stripe.checkout.sessions.retrieve(sessionId);

    if (session.payment_status === 'paid') {
      return NextResponse.json({ success: true });
    } else {
      return NextResponse.json({ success: false, error: 'Payment not completed' }, { status: 400 });
    }
  } catch (error) {
    return NextResponse.json({ error: 'Error verifying payment' }, { status: 500 });
  }
}

Step 4: Implement Webhooks for Database Updates

To handle subscription status updates (activation, expiration, and cancellation), we set up Stripe webhooks. Here I have setup the webhooks which are required formy application. You can add/remove webhooks based on your application need.

export async function POST(req: Request) {
  try {
    const body = await req.text();
    const signature = headers().get('stripe-signature');

    if (!signature) {
      return new NextResponse('No signature', { status: 400 });
    }

    let event;

    try {
      event = stripe.webhooks.constructEvent(body, signature, process.env.STRIPE_WEBHOOK_SECRET!);
    } catch (err: any) {
      return NextResponse.json({ error: err.message }, { status: 400 });
    }

    const data = event.data;
    const eventType = event.type;

    switch (eventType) {
      case 'checkout.session.completed':
        const checkoutSession = data.object as Stripe.Checkout.Session;
        const session = await stripe.checkout.sessions.retrieve(checkoutSession.id, {
          expand: ['line_items.data.price'],
        });

        const email = session.customer_details?.email;
        const customerId = session.customer;
        const priceId = session.line_items?.data[0]?.price?.id;
        const planName = getPlanNameFromPriceId(priceId || '');

        const subscription = await stripe.subscriptions.retrieve(session.subscription as string);
        const activatedAt = new Date(subscription.current_period_start * 1000);
        const expiresAt = new Date(subscription.current_period_end * 1000);

        // Perform any DB/Backend operation based on your application need here.

        break;

      case 'customer.subscription.updated':
        const updatedSubscription = data.object as Stripe.Subscription;
        const updatedCustomer = updatedSubscription.customer as string;

        const updatedUser = await prisma.users.findFirst({
          where: { stripeCustomerId: updatedCustomer },
          select: { id: true, planDetails: true },
        });

        if (!updatedUser) break;

        const updatedPriceId = updatedSubscription.items.data[0].price.id;
        const updatedPlanName = getPlanNameFromPriceId(updatedPriceId || '');
        const updatedAmount = '$' + (updatedSubscription.items.data[0].price.unit_amount || 0) / 100;
        const updatedActivatedAt = new Date(updatedSubscription.current_period_start * 1000);
        const updatedExpiresAt = new Date(updatedSubscription.current_period_end * 1000);

       // Perform any DB/Backend operation based on your application need here.
        break;

      case 'customer.subscription.deleted':
        const deletedSubscription = data.object as Stripe.Subscription;
        const customer = deletedSubscription.customer as string;

        const userToUpdate = await prisma.users.findFirst({
          where: { stripeCustomerId: customer },
          select: { id: true, planDetails: true },
        });

        if (!userToUpdate) break;

       // Perform any DB/Backend operation based on your application need here.
        break;

      case 'customer.subscription.resumed':
        const resumedSubscription = data.object as Stripe.Subscription;
        const resumedCustomer = resumedSubscription.customer as string;

        const resumedUser = await prisma.users.findFirst({
          where: { stripeCustomerId: resumedCustomer },
          select: { id: true, planDetails: true },
        });

        if (!resumedUser) break;

        const resumedPriceId = resumedSubscription.items.data[0].price.id;
        const resumedPlanName = getPlanNameFromPriceId(resumedPriceId || '');
        const resumedAmount = '$' + (resumedSubscription.items.data[0].price.unit_amount || 0) / 100;
        const resumedActivatedAt = new Date(resumedSubscription.current_period_start * 1000);
        const resumedExpiresAt = new Date(resumedSubscription.current_period_end * 1000);

       // Perform any DB/Backend operation based on your application need here.
        break;
    }

    return new NextResponse('Webhook processed', { status: 200 });
  } catch (error) {
    return new NextResponse('Webhook handling error', { status: 500 });
  }
}

Step 5 (Optional): Free Trial Integration.

This is a API for free trial. You can can call this API whenever required for free trial. This will enable free trial for the user in stripe.

export async function POST(req: Request) {
  try {
    const { userId } = await req.json();
    const user = await UserService.getUserById(userId);
    if (!user) {
      return NextResponse.json({ error: 'User not found' }, { status: 404 });
    }
    const customer = await stripe.customers.create({
      email: user.email,
      name: user.name,
    });
    const customerId = customer.id;
    const subscription = await stripe.subscriptions.create({
      customer: customerId,
      items: [
        {
          price: tiers.find((tier) => tier.name === 'Free')?.priceId,
        },
      ],
      trial_period_days: 30,
    });
    // Perform DB/Backend Operation based on app. logic here.

    return NextResponse.json({ success: true, planDetails });
  } catch (error) {
    return NextResponse.json({ error: 'Failed to create free trial' }, { status: 500 });
  }
}

Step 6 (Optional): Customer Portal

To allow customers to manage their subscriptions, you can provide a link to the billing portal, such as https://billing.stripe.com/... This link can be used in your UI to redirect users to the Stripe platform, where they can enter the email associated with their purchase to manage subscriptions and payments.

Conclusion

Setting up Stripe payments, especially for subscriptions, requires handling multiple steps, including checkout session creation, payment verification, and webhook handling. This structured approach ensures a seamless integration, making it easier to manage customer subscriptions efficiently.

Topics

stripesoftware-engineeringpayments

Explore how we ship AI products 10x faster

Get a personalized demo of our development process and see how we can accelerate your AI initiatives.

You might also like

Top Logistics Analytics Tools Compared: Power BI vs Tableau vs Qlik for Supply Chain Data

Top Logistics Analytics Tools Compared: Power BI vs Tableau vs Qlik for Supply Chain Data

Which analytics platform best serves supply chain and logistics reporting? Every supply chain leader eventually circles back to the same frustration: the data is there, but the insights feel late, shallow, or inconsistent. Dashboards summarize history; decisions need context, prediction, and clarity. Logistics and supply chain data — with its mix of orders, inventory positions, transport events, and exceptions — demands analytics that can keep up with operational realities. Power BI, Tableau,

Aryan
•January 24, 2026•6 min read
Keeping an Eye on Production Application Logs with Grafana Loki

Keeping an Eye on Production Application Logs with Grafana Loki

Learn how Grafana Loki simplifies production log monitoring with real-time tracking, advanced filters, and actionable insights. Step-by-step guide to preven

Nilesh Darji
Nilesh Darji
•March 30, 2025•5 min read
Approval Latency Is the Real Bottleneck in Supply Chain AI

Approval Latency Is the Real Bottleneck in Supply Chain AI

In supply chain AI, the model isn't the bottleneck — the approval queue is. Why the next gains come from redesigning decision authority, not accuracy.

Nakshatra
•June 9, 2026•3 min read

Never miss an Update

Get actionable insights on AI, product development, and scaling engineering teams

Join 1000+ Subscribers, Unsubscribe anytime