stripe
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseStripe 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
undefinedbash
undefinedStripe 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 , the emulator runs inside your Next.js app at . The SDK needs to point at with a proxy route to forward calls to :
@emulators/adapter-next/emulate/stripelocalhost/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 }使用时,模拟器会在你的Next.js应用内的路径下运行。SDK需要指向,并通过代理路由将请求转发到:
@emulators/adapter-next/emulate/stripelocalhost/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 field for deterministic IDs that survive server restarts.
idyaml
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_testThe field in prices links to the product by name. Use to receive all webhook events, or specify individual event types.
product_nameevents: ['*']初始数据是可选的。所有实体都支持可选的字段,用于生成在服务器重启后仍保持不变的确定性ID。
idyaml
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价格中的字段通过名称关联产品。使用接收所有Webhook事件,或指定单个事件类型。
product_nameevents: ['*']API Endpoints
API 端点
Customers
客户
bash
undefinedbash
undefinedCreate customer
创建客户
Retrieve customer
获取客户
Update customer
更新客户
curl -X POST http://localhost:4000/v1/customers/cus_xxx
-d "name=Updated Name"
-d "name=Updated Name"
curl -X POST http://localhost:4000/v1/customers/cus_xxx
-d "name=Updated Name"
-d "name=Updated Name"
Delete customer
删除客户
curl -X DELETE http://localhost:4000/v1/customers/cus_xxx
curl -X DELETE http://localhost:4000/v1/customers/cus_xxx
List customers
列出客户
undefinedundefinedProducts
产品
bash
undefinedbash
undefinedCreate product
创建产品
curl -X POST http://localhost:4000/v1/products
-d "name=Widget" -d "description=A useful widget"
-d "name=Widget" -d "description=A useful widget"
curl -X POST http://localhost:4000/v1/products
-d "name=Widget" -d "description=A useful widget"
-d "name=Widget" -d "description=A useful widget"
Retrieve product
获取产品
List products
列出产品
undefinedundefinedPrices
价格
bash
undefinedbash
undefinedCreate price
创建价格
curl -X POST http://localhost:4000/v1/prices
-d "product=prod_xxx" -d "currency=usd" -d "unit_amount=1000"
-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"
-d "product=prod_xxx" -d "currency=usd" -d "unit_amount=1000"
Retrieve price
获取价格
List prices
列出价格
undefinedundefinedCheckout Sessions
结账会话
bash
undefinedbash
undefinedCreate 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"
-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"
-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
undefinedbash
undefinedCreate payment intent
创建支付意向
curl -X POST http://localhost:4000/v1/payment_intents
-d "amount=2000" -d "currency=usd"
-d "amount=2000" -d "currency=usd"
curl -X POST http://localhost:4000/v1/payment_intents
-d "amount=2000" -d "currency=usd"
-d "amount=2000" -d "currency=usd"
Retrieve
获取支付意向
Update
更新支付意向
curl -X POST http://localhost:4000/v1/payment_intents/pi_xxx
-d "amount=3000"
-d "amount=3000"
curl -X POST http://localhost:4000/v1/payment_intents/pi_xxx
-d "amount=3000"
-d "amount=3000"
Confirm (triggers payment_intent.succeeded + charge.succeeded webhooks)
确认(触发payment_intent.succeeded + charge.succeeded Webhook)
Cancel
取消支付意向
List
列出支付意向
undefinedundefinedCharges
收费记录
bash
undefinedbash
undefinedRetrieve charge
获取收费记录
List charges
列出收费记录
Charges are created automatically when a payment intent is confirmed.
确认支付意向时会自动创建收费记录。Customer Sessions
客户会话
bash
undefinedbash
undefinedCreate customer session
创建客户会话
curl -X POST http://localhost:4000/v1/customer_sessions
-d "customer=cus_xxx"
-d "customer=cus_xxx"
undefinedcurl -X POST http://localhost:4000/v1/customer_sessions
-d "customer=cus_xxx"
-d "customer=cus_xxx"
undefinedPayment Methods
支付方式
bash
undefinedbash
undefinedList payment methods
列出支付方式
undefinedundefinedWebhooks
Webhook
The emulator dispatches webhook events when state changes. Register webhooks via seed config or programmatically.
模拟器会在状态变更时发送Webhook事件。可通过初始配置数据或以编程方式注册Webhook。
Events dispatched
发送的事件
| Event | Trigger |
|---|---|
| Customer created |
| Customer updated |
| Customer deleted |
| Product created |
| Price created |
| Payment intent created |
| Payment intent confirmed |
| Payment intent canceled |
| Payment intent confirmed (charge auto-created) |
| Checkout completed via hosted page |
| 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'