如何将 Stripe Payments 集成到 Chrome 扩展程序(一步一步)

发布: (2025年12月14日 GMT+8 16:05)
6 min read
原文: Dev.to

Source: Dev.to

概览

支付流程如下:

  1. 用户在扩展弹窗中点击 “升级为高级版”
  2. 扩展调用无服务器函数生成 Stripe Checkout URL。
  3. 背景脚本将用户重定向到 Stripe 的安全 Checkout 页面。
  4. 用户完成支付。
  5. Stripe 向你的无服务器函数发送 webhook。
  6. webhook 处理程序将订阅数据保存到数据库。
  7. 当用户访问高级功能时,扩展会验证其高级状态。

第 1 部分:设置 Stripe

步骤 1 – 创建 Stripe 沙箱账户

用于开发和测试时,请先使用 Stripe 沙箱。请参阅官方文档: 。

步骤 2 – 创建你的产品

  1. 在 Stripe 仪表板中,进入 ProductsAdd Product

  2. 填写详情:

    • Name(名称):“Premium Subscription”
    • Description(描述):“Monthly premium access”
    • Pricing(定价):“$9.99 / month”(或你选择的价格)
  3. 复制 Price ID(以 price_ 开头)。稍后会用到。

步骤 3 – 获取 API 密钥

前往 Developers → API Keys 或访问 。

  • 复制 Secret Key(以 sk_test_ 开头的用于沙箱)。
  • 安全保存——切勿将其提交到源码控制。

参考文档:Stripe API Keys Documentation

第 2 部分:创建无服务器函数

我们将创建两个无服务器端点:

  • 生成 Checkout URL
  • 处理 Stripe Webhook

项目初始化

安装 Stripe SDK:

npm install stripe --save

参考文档:Stripe SDK Docs

初始化 Stripe 客户端

创建文件以配置 Stripe 客户端:

// 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 应在 .env 文件中定义。

函数 1 – 创建 Checkout 链接

此无服务器函数生成个性化的 Checkout URL(预填邮箱)。

// 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: 5
00,
        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' },
    });
  }
}

可选方案:如果不需要预填用户邮箱,你可以直接在 Stripe 仪表板(Products → Checkout Links)创建固定的 Checkout 链接,省去此函数。

函数 2 – 处理 Stripe Webhook

(此处省略实现细节;创建一个端点以验证 Stripe 签名、解析事件,并在数据库中更新订阅状态。)

第 3 部分:构建 Chrome 扩展

扩展文件结构

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

步骤 1 – Checkout 处理器

调用无服务器 Checkout 端点并返回 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;
  }
}

随后可在弹窗或后台脚本中调用 createCheckoutURL,在新标签页打开返回的 URL,并监听 webhook 以更新用户的高级状态。

Back to Blog

相关文章

阅读更多 »

JavaScript 中的一等函数

介绍 对于学习 JavaScript 的开发者来说,术语 first‑class functions 在讨论和文档中经常出现。在 JavaScript 中,函数 a...