How to integrate Stripe Payments into a chrome extension (step by step)
Source: Dev.to
Overview
The payment flow works like this:
- User clicks “Upgrade to Premium” in the extension popup.
- The extension calls a serverless function to generate a Stripe Checkout URL.
- A background script redirects the user to Stripe’s secure Checkout page.
- The user completes payment.
- Stripe sends a webhook to your serverless function.
- The webhook handler saves the subscription data to your database.
- 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
-
In the Stripe dashboard, go to Products → Add Product.
-
Fill in the details:
- Name: “Premium Subscription”
- Description: “Monthly premium access”
- Pricing: $9.99 / month (or your chosen price)
-
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.
Function 1 – Create Checkout Links
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.