payments

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Stripe Payments Integration

Stripe支付集成

You are an expert in Stripe payments for Vercel-deployed applications — covering the native Vercel Marketplace integration, Checkout Sessions, webhook handling, subscription billing, and the Stripe Node.js SDK.
您是Vercel部署应用的Stripe支付专家——涵盖原生Vercel Marketplace集成、Checkout Sessions、Webhook处理、订阅计费及Stripe Node.js SDK相关内容。

Vercel Marketplace Setup (Recommended)

Vercel Marketplace配置(推荐方案)

Stripe is a native Vercel Marketplace integration with sandbox provisioning and unified billing.
Stripe是Vercel Marketplace的原生集成服务,支持沙箱环境配置和统一计费。

Install via Marketplace

通过Marketplace安装

bash
undefined
bash
undefined

Install Stripe from Vercel Marketplace (auto-provisions sandbox + env vars)

Install Stripe from Vercel Marketplace (auto-provisions sandbox + env vars)

vercel integration add stripe

Auto-provisioned environment variables:
- `STRIPE_SECRET_KEY` — server-side API key
- `STRIPE_PUBLISHABLE_KEY` — client-side publishable key
- `STRIPE_WEBHOOK_SECRET` — webhook endpoint signing secret
vercel integration add stripe

自动配置的环境变量:
- `STRIPE_SECRET_KEY` — 服务器端API密钥
- `STRIPE_PUBLISHABLE_KEY` — 客户端可发布密钥
- `STRIPE_WEBHOOK_SECRET` — Webhook端点签名密钥

SDK Setup

SDK配置

bash
undefined
bash
undefined

Server-side SDK

Server-side SDK

npm install stripe
npm install stripe

Client-side SDK (for Stripe Elements / Checkout)

Client-side SDK (for Stripe Elements / Checkout)

npm install @stripe/stripe-js @stripe/react-stripe-js
undefined
npm install @stripe/stripe-js @stripe/react-stripe-js
undefined

Initialize the Stripe Client

初始化Stripe客户端

ts
// lib/stripe.ts
import Stripe from "stripe";

export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: "2026-02-25.clover",
  typescript: true,
});
ts
// lib/stripe.ts
import Stripe from "stripe";

export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: "2026-02-25.clover",
  typescript: true,
});

Checkout Sessions

结账会话(Checkout Sessions)

Server Action (Recommended for 2026)

Server Action(2026年推荐方案)

Server Actions are the preferred pattern for creating Checkout Sessions in Next.js 15+, eliminating the need for API routes:
ts
// app/actions/checkout.ts
"use server";
import { redirect } from "next/navigation";
import { stripe } from "@/lib/stripe";

export async function createCheckoutSession(priceId: string) {
  const session = await stripe.checkout.sessions.create({
    mode: "payment",
    line_items: [{ price: priceId, quantity: 1 }],
    success_url: `${process.env.NEXT_PUBLIC_APP_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/cancel`,
  });

  redirect(session.url!);
}
tsx
// app/pricing/page.tsx
import { createCheckoutSession } from "@/app/actions/checkout";

export default function PricingPage() {
  return (
    <form action={createCheckoutSession.bind(null, "price_xxx")}>
      <button type="submit">Buy Now</button>
    </form>
  );
}
在Next.js 15+中,Server Action是创建结账会话的首选模式,无需使用API路由:
ts
// app/actions/checkout.ts
"use server";
import { redirect } from "next/navigation";
import { stripe } from "@/lib/stripe";

export async function createCheckoutSession(priceId: string) {
  const session = await stripe.checkout.sessions.create({
    mode: "payment",
    line_items: [{ price: priceId, quantity: 1 }],
    success_url: `${process.env.NEXT_PUBLIC_APP_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/cancel`,
  });

  redirect(session.url!);
}
tsx
// app/pricing/page.tsx
import { createCheckoutSession } from "@/app/actions/checkout";

export default function PricingPage() {
  return (
    <form action={createCheckoutSession.bind(null, "price_xxx")}>
      <button type="submit">立即购买</button>
    </form>
  );
}

Create a Checkout Session (API Route)

创建结账会话(API路由方式)

ts
// app/api/checkout/route.ts
import { NextResponse } from "next/server";
import { stripe } from "@/lib/stripe";

export async function POST(req: Request) {
  const { priceId } = await req.json();

  const session = await stripe.checkout.sessions.create({
    mode: "payment", // or "subscription" for recurring
    payment_method_types: ["card"],
    line_items: [{ price: priceId, quantity: 1 }],
    success_url: `${process.env.NEXT_PUBLIC_APP_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/cancel`,
  });

  return NextResponse.json({ url: session.url });
}
ts
// app/api/checkout/route.ts
import { NextResponse } from "next/server";
import { stripe } from "@/lib/stripe";

export async function POST(req: Request) {
  const { priceId } = await req.json();

  const session = await stripe.checkout.sessions.create({
    mode: "payment", // 订阅服务请使用"subscription"
    payment_method_types: ["card"],
    line_items: [{ price: priceId, quantity: 1 }],
    success_url: `${process.env.NEXT_PUBLIC_APP_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/cancel`,
  });

  return NextResponse.json({ url: session.url });
}

Redirect to Checkout (Client)

客户端跳转至结账页面

tsx
"use client";
import { loadStripe } from "@stripe/stripe-js";

const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);

export function CheckoutButton({ priceId }: { priceId: string }) {
  const handleCheckout = async () => {
    const res = await fetch("/api/checkout", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ priceId }),
    });
    const { url } = await res.json();
    window.location.href = url;
  };

  return <button onClick={handleCheckout}>Subscribe</button>;
}
tsx
"use client";
import { loadStripe } from "@stripe/stripe-js";

const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);

export function CheckoutButton({ priceId }: { priceId: string }) {
  const handleCheckout = async () => {
    const res = await fetch("/api/checkout", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ priceId }),
    });
    const { url } = await res.json();
    window.location.href = url;
  };

  return <button onClick={handleCheckout}>订阅</button>;
}

Webhook Handling

Webhook处理

Stripe sends events to your webhook endpoint for asynchronous payment processing. Always verify the signature.
ts
// app/api/webhook/stripe/route.ts
import { NextResponse } from "next/server";
import { stripe } from "@/lib/stripe";
import Stripe from "stripe";

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

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

  switch (event.type) {
    case "checkout.session.completed": {
      const session = event.data.object as Stripe.Checkout.Session;
      // Fulfill the order — update database, send confirmation, etc.
      break;
    }
    case "invoice.payment_succeeded": {
      const invoice = event.data.object as Stripe.Invoice;
      // Handle successful subscription renewal
      break;
    }
    case "customer.subscription.deleted": {
      const subscription = event.data.object as Stripe.Subscription;
      // Handle cancellation — revoke access
      break;
    }
  }

  return NextResponse.json({ received: true });
}
Important: Webhook routes must read the raw body as text (not JSON) for signature verification. Do not add
bodyParser
or JSON middleware to webhook routes.
Stripe会向您的Webhook端点发送事件,用于异步支付处理。请务必验证签名的有效性。
ts
// app/api/webhook/stripe/route.ts
import { NextResponse } from "next/server";
import { stripe } from "@/lib/stripe";
import Stripe from "stripe";

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

  let event: Stripe.Event;
  try {
    event = stripe.webhooks.constructEvent(
      body,
      signature,
      process.env.STRIPE_WEBHOOK_SECRET!
    );
  } catch (err) {
    return NextResponse.json({ error: "无效签名" }, { status: 400 });
  }

  switch (event.type) {
    case "checkout.session.completed": {
      const session = event.data.object as Stripe.Checkout.Session;
      // 处理订单完成逻辑——更新数据库、发送确认邮件等
      break;
    }
    case "invoice.payment_succeeded": {
      const invoice = event.data.object as Stripe.Invoice;
      // 处理订阅续费成功逻辑
      break;
    }
    case "customer.subscription.deleted": {
      const subscription = event.data.object as Stripe.Subscription;
      // 处理订阅取消逻辑——收回服务权限
      break;
    }
  }

  return NextResponse.json({ received: true });
}
重要提示:Webhook路由必须以文本形式读取原始请求体(而非JSON),才能完成签名验证。请勿为Webhook路由添加
bodyParser
或JSON中间件。

Subscription Billing

订阅计费

Create a Subscription Checkout

创建订阅服务结账会话

ts
const session = await stripe.checkout.sessions.create({
  mode: "subscription",
  line_items: [{ price: priceId, quantity: 1 }],
  success_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard`,
  cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/pricing`,
});
ts
const session = await stripe.checkout.sessions.create({
  mode: "subscription",
  line_items: [{ price: priceId, quantity: 1 }],
  success_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard`,
  cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/pricing`,
});

Customer Portal

客户管理门户

Allow customers to manage their subscriptions:
ts
// app/api/portal/route.ts
import { stripe } from "@/lib/stripe";

export async function POST(req: Request) {
  const { customerId } = await req.json();

  const session = await stripe.billingPortal.sessions.create({
    customer: customerId,
    return_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard`,
  });

  return Response.json({ url: session.url });
}
允许客户自行管理其订阅服务:
ts
// app/api/portal/route.ts
import { stripe } from "@/lib/stripe";

export async function POST(req: Request) {
  const { customerId } = await req.json();

  const session = await stripe.billingPortal.sessions.create({
    customer: customerId,
    return_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard`,
  });

  return Response.json({ url: session.url });
}

Embedded Checkout (Recommended)

嵌入式结账(推荐方案)

Stripe's Embedded Checkout renders inside your page via an iframe, keeping users on your domain while offloading PCI compliance to Stripe:
ts
// app/actions/embedded-checkout.ts
"use server";
import { stripe } from "@/lib/stripe";

export async function createEmbeddedCheckout(priceId: string) {
  const session = await stripe.checkout.sessions.create({
    mode: "payment",
    line_items: [{ price: priceId, quantity: 1 }],
    ui_mode: "embedded",
    return_url: `${process.env.NEXT_PUBLIC_APP_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
  });

  return { clientSecret: session.client_secret! };
}
tsx
"use client";
import { loadStripe } from "@stripe/stripe-js";
import { EmbeddedCheckoutProvider, EmbeddedCheckout } from "@stripe/react-stripe-js";
import { createEmbeddedCheckout } from "@/app/actions/embedded-checkout";

const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);

export function CheckoutEmbed({ priceId }: { priceId: string }) {
  return (
    <EmbeddedCheckoutProvider
      stripe={stripePromise}
      options={{ fetchClientSecret: () => createEmbeddedCheckout(priceId).then(r => r.clientSecret) }}
    >
      <EmbeddedCheckout />
    </EmbeddedCheckoutProvider>
  );
}
Stripe的嵌入式结账功能通过iframe在您的页面内渲染结账界面,用户无需离开您的域名,同时将PCI合规责任转移给Stripe:
ts
// app/actions/embedded-checkout.ts
"use server";
import { stripe } from "@/lib/stripe";

export async function createEmbeddedCheckout(priceId: string) {
  const session = await stripe.checkout.sessions.create({
    mode: "payment",
    line_items: [{ price: priceId, quantity: 1 }],
    ui_mode: "embedded",
    return_url: `${process.env.NEXT_PUBLIC_APP_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
  });

  return { clientSecret: session.client_secret! };
}
tsx
"use client";
import { loadStripe } from "@stripe/stripe-js";
import { EmbeddedCheckoutProvider, EmbeddedCheckout } from "@stripe/react-stripe-js";
import { createEmbeddedCheckout } from "@/app/actions/embedded-checkout";

const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);

export function CheckoutEmbed({ priceId }: { priceId: string }) {
  return (
    <EmbeddedCheckoutProvider
      stripe={stripePromise}
      options={{ fetchClientSecret: () => createEmbeddedCheckout(priceId).then(r => r.clientSecret) }}
    >
      <EmbeddedCheckout />
    </EmbeddedCheckoutProvider>
  );
}

Stripe Elements (Custom Forms)

Stripe Elements(自定义表单)

tsx
"use client";
import { Elements, PaymentElement, useStripe, useElements } from "@stripe/react-stripe-js";
import { loadStripe } from "@stripe/stripe-js";

const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);

function CheckoutForm() {
  const stripe = useStripe();
  const elements = useElements();

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!stripe || !elements) return;

    const { error } = await stripe.confirmPayment({
      elements,
      confirmParams: { return_url: `${window.location.origin}/success` },
    });

    if (error) console.error(error.message);
  };

  return (
    <form onSubmit={handleSubmit}>
      <PaymentElement />
      <button type="submit" disabled={!stripe}>Pay</button>
    </form>
  );
}

export function PaymentForm({ clientSecret }: { clientSecret: string }) {
  return (
    <Elements stripe={stripePromise} options={{ clientSecret }}>
      <CheckoutForm />
    </Elements>
  );
}
tsx
"use client";
import { Elements, PaymentElement, useStripe, useElements } from "@stripe/react-stripe-js";
import { loadStripe } from "@stripe/stripe-js";

const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);

function CheckoutForm() {
  const stripe = useStripe();
  const elements = useElements();

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!stripe || !elements) return;

    const { error } = await stripe.confirmPayment({
      elements,
      confirmParams: { return_url: `${window.location.origin}/success` },
    });

    if (error) console.error(error.message);
  };

  return (
    <form onSubmit={handleSubmit}>
      <PaymentElement />
      <button type="submit" disabled={!stripe}>支付</button>
    </form>
  );
}

export function PaymentForm({ clientSecret }: { clientSecret: string }) {
  return (
    <Elements stripe={stripePromise} options={{ clientSecret }}>
      <CheckoutForm />
    </Elements>
  );
}

Environment Variables

环境变量

VariableScopeDescription
STRIPE_SECRET_KEY
ServerAPI secret key (starts with
sk_
)
STRIPE_PUBLISHABLE_KEY
ClientPublishable key (starts with
pk_
)
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
ClientAlias exposed to browser via Next.js
STRIPE_WEBHOOK_SECRET
ServerWebhook signing secret (starts with
whsec_
)
变量名称作用范围说明
STRIPE_SECRET_KEY
服务器端API密钥(以
sk_
开头)
STRIPE_PUBLISHABLE_KEY
客户端可发布密钥(以
pk_
开头)
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
客户端Next.js暴露给浏览器的别名
STRIPE_WEBHOOK_SECRET
服务器端Webhook签名密钥(以
whsec_
开头)

Cross-References

交叉引用

  • Marketplace install and env var provisioning
    ⤳ skill: marketplace
  • Webhook route patterns
    ⤳ skill: routing-middleware
  • Environment variable management
    ⤳ skill: env-vars
  • Serverless function config
    ⤳ skill: vercel-functions
  • Marketplace安装与环境变量配置
    ⤳ skill: marketplace
  • Webhook路由模式
    ⤳ skill: routing-middleware
  • 环境变量管理
    ⤳ skill: env-vars
  • 无服务器函数配置
    ⤳ skill: vercel-functions

Official Documentation

官方文档