stripe

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Stripe API Emulator

Stripe API 模拟器

Fully stateful Stripe API emulation. Customers, products, prices, checkout sessions, payment intents, charges, and payment methods persist in memory. Webhooks fire on state changes. The hosted checkout UI lets you complete payments in the browser.
No real payments are processed. Every Stripe SDK call hits the emulator and produces realistic responses.
完全有状态的Stripe API模拟。客户、产品、价格、结账会话、支付意向、收费记录和支付方式会在内存中持久化。状态变更时会触发Webhook。托管的结账UI允许你在浏览器中完成支付流程。
不会处理真实支付。所有Stripe SDK调用都会指向模拟器并生成逼真的响应。

Start

启动

bash
undefined
bash
undefined

Stripe only

Stripe only

npx emulate --service stripe
npx emulate --service stripe

Default port (when run alone)

Default port (when run alone)


Or programmatically:

```typescript
import { createEmulator } from 'emulate'

const stripe = await createEmulator({ service: 'stripe', port: 4000 })
// stripe.url === 'http://localhost:4000'

或以编程方式启动:

```typescript
import { createEmulator } from 'emulate'

const stripe = await createEmulator({ service: 'stripe', port: 4000 })
// stripe.url === 'http://localhost:4000'

Pointing Your App at the Emulator

将你的应用指向模拟器

Stripe SDK

Stripe SDK

The Stripe Node.js SDK does not read an environment variable for the base URL. You must pass it when constructing the client:
typescript
import Stripe from 'stripe'

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2024-12-18.acacia',
  host: 'localhost',
  port: 4000,
  protocol: 'http',
})
Stripe Node.js SDK不会读取环境变量中的基础URL。你必须在构造客户端时传入该参数:
typescript
import Stripe from 'stripe'

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2024-12-18.acacia',
  host: 'localhost',
  port: 4000,
  protocol: 'http',
})

Embedded in Next.js (adapter-next)

嵌入Next.js(adapter-next)

When using
@emulators/adapter-next
, the emulator runs inside your Next.js app at
/emulate/stripe
. The SDK needs to point at
localhost
with a proxy route to forward
/v1/*
calls to
/emulate/stripe/v1/*
:
typescript
// next.config.ts
import { withEmulate } from '@emulators/adapter-next'

export default withEmulate({
  env: {
    STRIPE_SECRET_KEY: 'sk_test_emulated',
  },
})
typescript
// lib/stripe.ts
import Stripe from 'stripe'

const port = process.env.PORT ?? '3000'

export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2024-12-18.acacia',
  host: 'localhost',
  port: parseInt(port, 10),
  protocol: 'http',
})
typescript
// app/emulate/[...path]/route.ts
import { createEmulateHandler } from '@emulators/adapter-next'
import * as stripe from '@emulators/stripe'

export const { GET, POST, PUT, PATCH, DELETE } = createEmulateHandler({
  services: {
    stripe: {
      emulator: stripe,
      seed: {
        products: [
          { id: 'prod_widget', name: 'Widget', description: 'A useful widget' },
        ],
        prices: [
          { id: 'price_widget', product_name: 'Widget', currency: 'usd', unit_amount: 1000 },
        ],
        webhooks: [
          {
            url: `http://localhost:${process.env.PORT ?? '3000'}/api/webhooks/stripe`,
            events: ['*'],
          },
        ],
      },
    },
  },
})
typescript
// app/v1/[...path]/route.ts  (proxy for Stripe SDK)
const STRIPE_URL = `http://localhost:${process.env.PORT ?? '3000'}/emulate/stripe`

async function handler(req: Request, ctx: { params: Promise<{ path: string[] }> }) {
  const { path } = await ctx.params
  const url = new URL(req.url)
  const target = `${STRIPE_URL}/v1/${path.join('/')}${url.search}`

  const res = await fetch(target, {
    method: req.method,
    headers: req.headers,
    body: req.body,
    duplex: 'half',
  } as any)

  return new Response(res.body, {
    status: res.status,
    statusText: res.statusText,
    headers: res.headers,
  })
}

export { handler as GET, handler as POST, handler as PUT, handler as PATCH, handler as DELETE }
使用
@emulators/adapter-next
时,模拟器会在你的Next.js应用内的
/emulate/stripe
路径下运行。SDK需要指向
localhost
,并通过代理路由将
/v1/*
请求转发到
/emulate/stripe/v1/*
typescript
// next.config.ts
import { withEmulate } from '@emulators/adapter-next'

export default withEmulate({
  env: {
    STRIPE_SECRET_KEY: 'sk_test_emulated',
  },
})
typescript
// lib/stripe.ts
import Stripe from 'stripe'

const port = process.env.PORT ?? '3000'

export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2024-12-18.acacia',
  host: 'localhost',
  port: parseInt(port, 10),
  protocol: 'http',
})
typescript
// app/emulate/[...path]/route.ts
import { createEmulateHandler } from '@emulators/adapter-next'
import * as stripe from '@emulators/stripe'

export const { GET, POST, PUT, PATCH, DELETE } = createEmulateHandler({
  services: {
    stripe: {
      emulator: stripe,
      seed: {
        products: [
          { id: 'prod_widget', name: 'Widget', description: 'A useful widget' },
        ],
        prices: [
          { id: 'price_widget', product_name: 'Widget', currency: 'usd', unit_amount: 1000 },
        ],
        webhooks: [
          {
            url: `http://localhost:${process.env.PORT ?? '3000'}/api/webhooks/stripe`,
            events: ['*'],
          },
        ],
      },
    },
  },
})
typescript
// app/v1/[...path]/route.ts  (proxy for Stripe SDK)
const STRIPE_URL = `http://localhost:${process.env.PORT ?? '3000'}/emulate/stripe`

async function handler(req: Request, ctx: { params: Promise<{ path: string[] }> }) {
  const { path } = await ctx.params
  const url = new URL(req.url)
  const target = `${STRIPE_URL}/v1/${path.join('/')}${url.search}`

  const res = await fetch(target, {
    method: req.method,
    headers: req.headers,
    body: req.body,
    duplex: 'half',
  } as any)

  return new Response(res.body, {
    status: res.status,
    statusText: res.statusText,
    headers: res.headers,
  })
}

export { handler as GET, handler as POST, handler as PUT, handler as PATCH, handler as DELETE }

Direct fetch

直接调用

bash
curl http://localhost:4000/v1/customers \
  -H "Authorization: Bearer sk_test_emulated"
bash
curl http://localhost:4000/v1/customers \
  -H "Authorization: Bearer sk_test_emulated"

Seed Config

初始配置数据

Seed data is optional. All entities support an optional
id
field for deterministic IDs that survive server restarts.
yaml
stripe:
  customers:
    - id: cus_demo
      email: demo@example.com
      name: Demo User
  products:
    - id: prod_tshirt
      name: T-Shirt
      description: A comfortable tee
  prices:
    - id: price_tshirt
      product_name: T-Shirt
      currency: usd
      unit_amount: 2500
  webhooks:
    - url: http://localhost:3000/api/webhooks/stripe
      events: ['*']
      secret: whsec_test
The
product_name
field in prices links to the product by name. Use
events: ['*']
to receive all webhook events, or specify individual event types.
初始数据是可选的。所有实体都支持可选的
id
字段,用于生成在服务器重启后仍保持不变的确定性ID。
yaml
stripe:
  customers:
    - id: cus_demo
      email: demo@example.com
      name: Demo User
  products:
    - id: prod_tshirt
      name: T-Shirt
      description: A comfortable tee
  prices:
    - id: price_tshirt
      product_name: T-Shirt
      currency: usd
      unit_amount: 2500
  webhooks:
    - url: http://localhost:3000/api/webhooks/stripe
      events: ['*']
      secret: whsec_test
价格中的
product_name
字段通过名称关联产品。使用
events: ['*']
接收所有Webhook事件,或指定单个事件类型。

API Endpoints

API 端点

Customers

客户

bash
undefined
bash
undefined

Create customer

创建客户

curl -X POST http://localhost:4000/v1/customers
-d "email=user@example.com" -d "name=Jane Doe"
curl -X POST http://localhost:4000/v1/customers
-d "email=user@example.com" -d "name=Jane Doe"

Retrieve customer

获取客户

Update customer

更新客户

curl -X POST http://localhost:4000/v1/customers/cus_xxx
-d "name=Updated Name"
curl -X POST http://localhost:4000/v1/customers/cus_xxx
-d "name=Updated Name"

Delete customer

删除客户

List customers

列出客户

Products

产品

bash
undefined
bash
undefined

Create product

创建产品

curl -X POST http://localhost:4000/v1/products
-d "name=Widget" -d "description=A useful widget"
curl -X POST http://localhost:4000/v1/products
-d "name=Widget" -d "description=A useful widget"

Retrieve product

获取产品

List products

列出产品

Prices

价格

bash
undefined
bash
undefined

Create price

创建价格

curl -X POST http://localhost:4000/v1/prices
-d "product=prod_xxx" -d "currency=usd" -d "unit_amount=1000"
curl -X POST http://localhost:4000/v1/prices
-d "product=prod_xxx" -d "currency=usd" -d "unit_amount=1000"

Retrieve price

获取价格

List prices

列出价格

Checkout Sessions

结账会话

bash
undefined
bash
undefined

Create checkout session

创建结账会话

curl -X POST http://localhost:4000/v1/checkout/sessions
-d "mode=payment"
-d "line_items[0][price]=price_xxx"
-d "line_items[0][quantity]=1"
-d "success_url=http://localhost:3000/success?session_id={CHECKOUT_SESSION_ID}"
-d "cancel_url=http://localhost:3000/cart"
curl -X POST http://localhost:4000/v1/checkout/sessions
-d "mode=payment"
-d "line_items[0][price]=price_xxx"
-d "line_items[0][quantity]=1"
-d "success_url=http://localhost:3000/success?session_id={CHECKOUT_SESSION_ID}"
-d "cancel_url=http://localhost:3000/cart"

Retrieve session

获取会话

List sessions

列出会话

Expire a session

使会话过期


The session's `url` field points to a hosted checkout page at `/checkout/cs_xxx`. Clicking "Pay" on that page completes the session, fires the `checkout.session.completed` webhook, and redirects to `success_url`. The `{CHECKOUT_SESSION_ID}` template in `success_url` is replaced with the actual session ID.

会话的`url`字段指向位于`/checkout/cs_xxx`的托管结账页面。点击该页面上的「支付」按钮将完成会话,触发`checkout.session.completed` Webhook,并跳转到`success_url`。`success_url`中的`{CHECKOUT_SESSION_ID}`模板会被替换为实际的会话ID。

Payment Intents

支付意向

bash
undefined
bash
undefined

Create payment intent

创建支付意向

curl -X POST http://localhost:4000/v1/payment_intents
-d "amount=2000" -d "currency=usd"
curl -X POST http://localhost:4000/v1/payment_intents
-d "amount=2000" -d "currency=usd"

Retrieve

获取支付意向

Update

更新支付意向

Confirm (triggers payment_intent.succeeded + charge.succeeded webhooks)

确认(触发payment_intent.succeeded + charge.succeeded Webhook)

Cancel

取消支付意向

List

列出支付意向

Charges

收费记录

bash
undefined
bash
undefined

Retrieve charge

获取收费记录

List charges

列出收费记录


Charges are created automatically when a payment intent is confirmed.

确认支付意向时会自动创建收费记录。

Customer Sessions

客户会话

bash
undefined
bash
undefined

Create customer session

创建客户会话

curl -X POST http://localhost:4000/v1/customer_sessions
-d "customer=cus_xxx"
undefined
curl -X POST http://localhost:4000/v1/customer_sessions
-d "customer=cus_xxx"
undefined

Payment Methods

支付方式

bash
undefined
bash
undefined

List payment methods

列出支付方式

Webhooks

Webhook

The emulator dispatches webhook events when state changes. Register webhooks via seed config or programmatically.
模拟器会在状态变更时发送Webhook事件。可通过初始配置数据或以编程方式注册Webhook。

Events dispatched

发送的事件

EventTrigger
customer.created
Customer created
customer.updated
Customer updated
customer.deleted
Customer deleted
product.created
Product created
price.created
Price created
payment_intent.created
Payment intent created
payment_intent.succeeded
Payment intent confirmed
payment_intent.canceled
Payment intent canceled
charge.succeeded
Payment intent confirmed (charge auto-created)
checkout.session.completed
Checkout completed via hosted page
checkout.session.expired
Checkout session expired
事件触发条件
customer.created
创建客户时
customer.updated
更新客户时
customer.deleted
删除客户时
product.created
创建产品时
price.created
创建价格时
payment_intent.created
创建支付意向时
payment_intent.succeeded
确认支付意向时
payment_intent.canceled
取消支付意向时
charge.succeeded
确认支付意向时(自动创建收费记录)
checkout.session.completed
通过托管页面完成结账时
checkout.session.expired
结账会话过期时

Webhook handler example

Webhook处理示例

typescript
// app/api/webhooks/stripe/route.ts
import { NextResponse } from 'next/server'

export async function POST(request: Request) {
  const body = await request.json()
  const event = body.type as string
  const obj = body.data?.object

  switch (event) {
    case 'customer.created':
      console.log('Customer created:', obj.id, obj.email)
      break
    case 'checkout.session.completed':
      console.log('Checkout completed:', obj.id)
      break
    case 'payment_intent.succeeded':
      console.log('Payment succeeded:', obj.id)
      break
    case 'charge.succeeded':
      console.log('Charge succeeded:', obj.id)
      break
  }

  return NextResponse.json({ received: true })
}
typescript
// app/api/webhooks/stripe/route.ts
import { NextResponse } from 'next/server'

export async function POST(request: Request) {
  const body = await request.json()
  const event = body.type as string
  const obj = body.data?.object

  switch (event) {
    case 'customer.created':
      console.log('Customer created:', obj.id, obj.email)
      break
    case 'checkout.session.completed':
      console.log('Checkout completed:', obj.id)
      break
    case 'payment_intent.succeeded':
      console.log('Payment succeeded:', obj.id)
      break
    case 'charge.succeeded':
      console.log('Charge succeeded:', obj.id)
      break
  }

  return NextResponse.json({ received: true })
}

Common Patterns

常见模式

Checkout Flow (embedded Next.js)

结账流程(嵌入Next.js)

typescript
// Server action
const customer = await stripe.customers.create({
  email: 'shopper@example.com',
  name: 'Demo Shopper',
})

const session = await stripe.checkout.sessions.create({
  mode: 'payment',
  customer: customer.id,
  line_items: [{ price: 'price_widget', quantity: 2 }],
  success_url: `${origin}/success?session_id={CHECKOUT_SESSION_ID}`,
  cancel_url: `${origin}/cart`,
})

redirect(session.url!)
typescript
// Server action
const customer = await stripe.customers.create({
  email: 'shopper@example.com',
  name: 'Demo Shopper',
})

const session = await stripe.checkout.sessions.create({
  mode: 'payment',
  customer: customer.id,
  line_items: [{ price: 'price_widget', quantity: 2 }],
  success_url: `${origin}/success?session_id={CHECKOUT_SESSION_ID}`,
  cancel_url: `${origin}/cart`,
})

redirect(session.url!)

Retrieve Session on Success Page

在成功页面获取会话

typescript
const session = await stripe.checkout.sessions.retrieve(session_id)
const customer = await stripe.customers.retrieve(session.customer as string)

console.log(session.payment_status) // 'paid'
console.log(customer.name)          // 'Demo Shopper'
typescript
const session = await stripe.checkout.sessions.retrieve(session_id)
const customer = await stripe.customers.retrieve(session.customer as string)

console.log(session.payment_status) // 'paid'
console.log(customer.name)          // 'Demo Shopper'

Payment Intent Flow (no checkout UI)

支付意向流程(无结账UI)

typescript
const pi = await stripe.paymentIntents.create({
  amount: 5000,
  currency: 'usd',
  customer: 'cus_xxx',
})

// Confirm triggers payment_intent.succeeded + charge.succeeded webhooks
const confirmed = await stripe.paymentIntents.confirm(pi.id)
console.log(confirmed.status) // 'succeeded'
typescript
const pi = await stripe.paymentIntents.create({
  amount: 5000,
  currency: 'usd',
  customer: 'cus_xxx',
})

// 确认会触发payment_intent.succeeded + charge.succeeded Webhook
const confirmed = await stripe.paymentIntents.confirm(pi.id)
console.log(confirmed.status) // 'succeeded'