How to integrate Stripe Payments into a chrome extension (step by step)

Published: (December 14, 2025 at 03:05 AM EST)
4 min read
Source: Dev.to

Source: Dev.to

Overview

The payment flow works like this:

  1. User clicks “Upgrade to Premium” in the extension popup.
  2. The extension calls a serverless function to generate a Stripe Checkout URL.
  3. A background script redirects the user to Stripe’s secure Checkout page.
  4. The user completes payment.
  5. Stripe sends a webhook to your serverless function.
  6. The webhook handler saves the subscription data to your database.
  7. The extension verifies premium status when the user accesses premium features.

Part 1: Setting Up Stripe

Step 1 – Create a Stripe Sandbox Account

For development and testing, start with a Stripe sandbox. See the official docs: .

Step 2 – Create Your Product

  1. In the Stripe dashboard, go to ProductsAdd Product.

  2. Fill in the details:

    • Name: “Premium Subscription”
    • Description: “Monthly premium access”
    • Pricing: $9.99 / month (or your chosen price)
  3. Copy the Price ID (it starts with price_). You’ll need it later.

Step 3 – Get Your API Keys

Navigate to Developers → API Keys or visit .

  • Copy the Secret Key (starts with sk_test_ for sandbox).
  • Store it securely—never commit it to source control.

Reference: Stripe API Keys Documentation.

Part 2: Creating Serverless Functions

We’ll create two serverless endpoints:

  • Generate Checkout URLs
  • Handle Stripe Webhooks

Setting Up Your Project

Install the Stripe SDK:

npm install stripe --save

Reference: Stripe SDK Docs.

Initialize Stripe Client

Create a file to configure the Stripe client:

// lib/stripe/stripeClient.ts
import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2025-11-17.clover',
  typescript: true,
});

export default stripe;

STRIPE_SECRET_KEY should be defined in your .env file.

This serverless function generates a personalized Checkout URL (email pre‑filled).

// app/api/checkout/route.ts
import stripe from '@/lib/stripe/stripeClient';

interface CreateCheckoutParams {
  user?: {
    customerId?: string;
    email?: string;
  };
  mode: 'payment' | 'subscription';
  clientReferenceId?: string;
  priceId: string;
  couponId?: string | null;
}

export const createCheckoutStripe = async ({
  user,
  mode,
  clientReferenceId,
  priceId,
  couponId,
}: CreateCheckoutParams): Promise => {
  try {
    const extraParams: {
      customer?: string;
      customer_creation?: 'always';
      customer_email?: string;
      invoice_creation?: { enabled: boolean };
      payment_intent_data?: { setup_future_usage: 'on_session' };
      tax_id_collection?: { enabled: boolean };
    } = {};

    // Existing vs. new customers
    if (user?.customerId) {
      extraParams.customer = user.customerId;
    } else {
      if (mode === 'payment') {
        extraParams.customer_creation = 'always';
        extraParams.invoice_creation = { enabled: true };
        extraParams.payment_intent_data = { setup_future_usage: 'on_session' };
      }
      if (user?.email) extraParams.customer_email = user.email;
      extraParams.tax_id_collection = { enabled: true };
    }

    const stripeSession = await stripe.checkout.sessions.create({
      mode,
      allow_promotion_codes: true,
      client_reference_id: clientReferenceId,
      line_items: [{ price: priceId, quantity: 1 }],
      discounts: couponId ? [{ coupon: couponId }] : [],
      success_url: 'https://example.com/checkout-success',
      cancel_url: 'https://example.com/home',
      locale: 'auto',
      ...extraParams,
    });

    return stripeSession.url;
  } catch (e) {
    console.error('Error creating checkout session:', e);
    return null;
  }
};

export async function POST(request: Request) {
  try {
    const { id, userEmail, mode } = await request.json();

    if (!id) {
      return new Response(JSON.stringify({ error: 'Price ID is missing' }), {
        status: 400,
        headers: { 'Content-Type': 'application/json' },
      });
    }
    if (!mode) {
      return new Response(
        JSON.stringify({
          error: "Mode is missing (choose 'payment' or 'subscription')",
        }),
        { status: 400, headers: { 'Content-Type': 'application/json' } }
      );
    }

    const checkoutUrl = await createCheckoutStripe({
      user: { email: userEmail },
      mode,
      priceId: id,
    });

    if (!checkoutUrl) {
      return new Response(JSON.stringify({ error: 'Failed to create URL' }), {
        status: 500,
        headers: { 'Content-Type': 'application/json' },
      });
    }

    return new Response(JSON.stringify({ checkout_url: checkoutUrl }), {
      status: 200,
      headers: { 'Content-Type': 'application/json' },
    });
  } catch (error) {
    console.error('Checkout error:', error);
    return new Response(JSON.stringify({ error: 'Internal server error' }), {
      status: 500,
      headers: { 'Content-Type': 'application/json' },
    });
  }
}

Alternative: If you don’t need to pre‑fill the user’s email, you can create a fixed Checkout link directly in the Stripe dashboard (Products → Checkout Links) and skip this function.

Function 2 – Handle Stripe Webhooks

(Implementation omitted for brevity; create an endpoint that verifies the Stripe signature, parses the event, and updates your database with subscription status.)

Part 3: Building the Chrome Extension

Extension File Structure

your-extension/
├── manifest.json
├── popup/
│   ├── popup.html
│   └── popup.tsx
├── background/
│   └── background.ts
├── utils/
│   └── handleCheckout.ts
└── components/
    └── PremiumCard.tsx

Step 1 – Checkout Handler

Utility that calls the serverless Checkout endpoint and returns the URL.

// utils/handleCheckout.ts
/**
 * Generates a Stripe checkout URL by calling your serverless function.
 * The email is pre‑filled on the Stripe Checkout page.
 */
export async function createCheckoutURL(
  email: string | null,
  priceId: string,
  mode: 'payment' | 'subscription' = 'subscription'
): Promise {
  const CHECKOUT_ENDPOINT = 'https://your-app.vercel.app/api/checkout';

  try {
    const response = await fetch(CHECKOUT_ENDPOINT, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        id: priceId,
        userEmail: email,
        mode,
      }),
    });

    if (!response.ok) {
      console.error('Failed to create checkout URL:', response.statusText);
      return null;
    }

    const { checkout_url } = await response.json();
    return checkout_url;
  } catch (error) {
    console.error('Error creating checkout URL:', error);
    return null;
  }
}

You can then invoke createCheckoutURL from your popup or background script, open the returned URL in a new tab, and listen for the webhook to update the user’s premium status.

Back to Blog

Related posts

Read more »

First-Class Functions in JavaScript

Introduction For developers learning JavaScript, the term first‑class functions appears frequently in discussions and documentation. In JavaScript, functions a...