Stripe 是一个为互联网业务设计的在线支付处理平台,提供完整的支付 API 和开发者工具。本文将介绍 Stripe 的核心概念、集成方式和最佳实践,帮助开发者快速在应用中接入支付功能。
Stripe 简介
Stripe 是一个为互联网业务提供支付处理的平台,通过 API 让开发者快速集成支付功能。
Stripe 成立于 2010 年,为全球数百万家企业提供支付服务,包括 Amazon、Shopify、Lyft 等。Stripe 已通过 PCI DSS Level 1 认证,支持 135+ 种货币和 100+ 种支付方式(信用卡、Apple Pay、微信支付、支付宝等)。
Stripe 的核心优势:
- 开发者友好:RESTful API 设计简洁,提供多语言 SDK(JavaScript、Python、Ruby、Java 等)
- 文档完善:官方文档详尽,提供丰富的代码示例和集成指南
- 安全合规:Stripe 处理敏感支付信息,开发者无需直接接触信用卡数据
- 功能丰富:支持一次性支付、订阅、分账(Stripe Connect)等多种场景
Stripe 特别适合 SaaS 应用、电商平台、订阅服务等需要灵活支付集成的场景。
参考链接:
核心概念
Stripe 的支付流程围绕几个核心对象构建。理解这些对象及其关系是集成 Stripe 的基础。
Payment Intent(支付意图)
PaymentIntent 是 Stripe 现代支付 API 的核心对象,代表一次支付的完整生命周期。根据 Stripe 官方文档,PaymentIntent 追踪支付从创建到成功的整个过程,自动处理 3D Secure 等复杂的认证流程。
// 创建 PaymentIntent
const paymentIntent = await stripe.paymentIntents.create({
amount: 2000, // 金额(分为单位)
currency: "usd",
payment_method_types: ["card"], // 支持的支付方式
});
PaymentIntent 的状态流转:
requires_payment_method: 需要提供支付方式requires_confirmation: 需要确认支付requires_action: 需要额外操作(如 3D Secure)processing: 处理中succeeded: 支付成功canceled: 已取消
Customer(客户)
Customer 对象用于存储客户信息,支持保存支付方式、管理订阅等。根据 Stripe API 文档,Customer 对象可以关联多个支付方式。
// 创建 Customer
const customer = await stripe.customers.create({
email: "customer@example.com",
name: "John Doe",
});
Payment Method(支付方式)
PaymentMethod 对象代表客户的支付工具(信用卡、银行账户等)。根据 Stripe API 文档,PaymentMethod 可以附加到 Customer 或 PaymentIntent。
// 创建 PaymentMethod
const paymentMethod = await stripe.paymentMethods.create({
type: "card",
card: {
number: "4242424242424242",
exp_month: 12,
exp_year: 2025,
cvc: "123",
},
});
对象关系
Customer (客户)
├─ PaymentMethod (支付方式1)
├─ PaymentMethod (支付方式2)
└─ PaymentIntent (支付意图)
└─ Charge (实际扣款记录)
- Customer 可以有多个 PaymentMethod
- PaymentIntent 关联一个 Customer 和一个 PaymentMethod
- PaymentIntent 成功后会自动创建 Charge 对象
集成方式
Stripe 提供三种主要的集成方式,适应不同的开发需求和时间成本。
Stripe Checkout(托管支付页面)
Stripe Checkout 是托管的支付页面,由 Stripe 提供 UI 和处理逻辑。根据 Stripe Checkout 文档,这是最快的集成方式。
// 后端:创建 Checkout Session
const session = await stripe.checkout.sessions.create({
payment_method_types: ["card"],
line_items: [
{
price_data: {
currency: "usd",
product_data: { name: "T-shirt" },
unit_amount: 2000,
},
quantity: 1,
},
],
mode: "payment",
success_url: "https://example.com/success",
cancel_url: "https://example.com/cancel",
});
// 前端:重定向到 Stripe 托管页面
window.location.href = session.url;
优点:
- 开发速度快,几行代码完成集成
- UI 由 Stripe 维护,支持多语言
- 自动处理 3D Secure、Apple Pay 等
- PCI 合规性由 Stripe 负责
缺点:
- UI 定制受限
- 需要页面跳转
Stripe Elements(自定义 UI)
Stripe Elements 提供预构建的 UI 组件,可嵌入到自己的页面中。根据 Stripe Elements 文档,Elements 平衡了定制性和开发复杂度。
<!-- 前端:嵌入 Stripe Elements -->
<form id="payment-form">
<div id="card-element"></div>
<button type="submit">支付</button>
</form>
<script>
const stripe = Stripe("pk_test_...");
const elements = stripe.elements();
const cardElement = elements.create("card");
cardElement.mount("#card-element");
// 提交表单
const form = document.getElementById("payment-form");
form.addEventListener("submit", async (e) => {
e.preventDefault();
// 后端创建 PaymentIntent
const response = await fetch("/create-payment-intent", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ amount: 2000 }),
});
const { clientSecret } = await response.json();
// 前端确认支付
const result = await stripe.confirmCardPayment(clientSecret, {
payment_method: { card: cardElement },
});
if (result.error) {
console.error(result.error.message);
} else {
console.log("支付成功");
}
});
</script>
优点:
- UI 可以定制样式
- 用户无需离开当前页面
- 仍然处理敏感数据的安全性
缺点:
- 需要编写更多前端代码
- 需要后端 API 配合
Payment Intents API(完全控制)
直接使用 Payment Intents API,完全自定义支付流程。根据 Payment Intents API 文档,这种方式提供最大灵活性。
// 后端:完整的支付流程控制
const paymentIntent = await stripe.paymentIntents.create({
amount: 2000,
currency: "usd",
payment_method: "pm_card_visa", // 已保存的支付方式
customer: "cus_xxx", // 关联客户
confirm: true, // 立即确认
return_url: "https://example.com/return", // 3DS 认证后的返回地址
});
// 检查支付状态
if (paymentIntent.status === "succeeded") {
console.log("支付成功");
} else if (paymentIntent.status === "requires_action") {
// 需要 3D Secure 认证
console.log("需要额外认证:", paymentIntent.next_action);
}
优点:
- 完全控制支付流程
- 适合复杂业务逻辑
- 可以实现订阅、分账等高级功能
缺点:
- 开发复杂度最高
- 需要处理更多边界情况
三种方式对比
| 特性 | Checkout | Elements | Payment Intents API |
|---|---|---|---|
| 开发难度 | 简单 | 中等 | 复杂 |
| UI 定制 | 有限 | 灵活 | 完全自定义 |
| 页面跳转 | 需要 | 不需要 | 不需要 |
| 适用场景 | 快速上线 | 定制体验 | 复杂业务 |
| PCI 合规 | Stripe 负责 | Stripe 负责 | Stripe 负责 |
实现支付流程
本章将从零开始,手把手实现一个完整的支付功能。我们使用 Stripe Elements 方式,前端用原生 HTML/JavaScript,后端用 Node.js。
注册 Stripe 账户
访问 Stripe 官网 注册账户:
- 点击右上角 "Sign in" → "Sign up"
- 填写邮箱、姓名,创建密码
- 验证邮箱
- 登录后会自动进入测试模式(Test mode)
注册完成后,在 Dashboard 左侧菜单找到 "Developers" → "API keys",会看到两种密钥:
- Publishable key(公开密钥):以
pk_test_开头,用于前端 - Secret key(密钥):以
sk_test_开头,用于后端
根据 Stripe API Keys 文档,测试模式的密钥不会产生真实扣款。
理解 Publishable Key 和 Secret Key
Stripe 使用两种密钥实现安全隔离:
| 特性 | Publishable Key (公钥) | Secret Key (私钥) |
|---|---|---|
| 格式 | pk_test_xxx / pk_live_xxx | sk_test_xxx / sk_live_xxx |
| 使用位置 | 前端(浏览器) | 后端(服务器) |
| 是否可公开 | ✅ 可以公开 | ❌ 绝对保密 |
| 权限级别 | 只读,创建 token | 完全权限(创建、查询、修改、删除) |
为什么要分开?
- 安全隔离: 前端代码会被用户看到,如果使用私钥,用户可以直接调用 API 执行任意操作(退款、查询所有客户等)
- 最小权限原则: 公钥只有创建 token 的权限,无法执行敏感操作
- 防止滥用: 即使公钥泄露,也只能收集支付信息,无法直接扣款
使用场景:
// 前端(使用公钥):收集支付信息
const stripe = Stripe('pk_test_xxx'); // 公钥可以直接写在前端
const cardElement = elements.create('card');
await stripe.confirmCardPayment(clientSecret, {
payment_method: { card: cardElement }
});
// 后端(使用私钥):创建支付、退款等操作
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY); // 私钥必须用环境变量
const paymentIntent = await stripe.paymentIntents.create({ amount: 2000 });
这种设计确保了即使前端代码完全暴露,也不会危及账户安全。
项目初始化
创建项目目录并初始化:
# 创建项目目录
mkdir stripe-payment-demo
cd stripe-payment-demo
# 初始化 npm 项目
npm init -y
# 安装依赖
npm install stripe express dotenv
创建项目结构:
stripe-payment-demo/
├── .env # 环境变量(存放 API 密钥)
├── server.js # 后端服务器
├── public/
│ ├── index.html # 前端页面
│ └── checkout.js # 前端逻辑
└── package.json
配置环境变量
创建 .env 文件,存放 Stripe 密钥:
# .env
STRIPE_SECRET_KEY=sk_test_你的密钥
STRIPE_PUBLISHABLE_KEY=pk_test_你的公开密钥
重要:不要将 .env 文件提交到 Git。创建 .gitignore:
# .gitignore
node_modules/
.env
后端实现
创建 server.js,实现两个 API:
// server.js
require("dotenv").config();
const express = require("express");
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);
const app = express();
// 中间件
app.use(express.json());
app.use(express.static("public"));
// API 1: 创建 PaymentIntent
app.post("/create-payment-intent", async (req, res) => {
try {
const { amount } = req.body;
// 创建 PaymentIntent
const paymentIntent = await stripe.paymentIntents.create({
amount: amount, // 金额(分为单位)
currency: "usd",
automatic_payment_methods: {
enabled: true, // 自动启用支持的支付方式
},
});
// 返回 clientSecret 给前端
res.json({ clientSecret: paymentIntent.client_secret });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// API 2: 获取公开密钥
app.get("/config", (req, res) => {
res.json({ publishableKey: process.env.STRIPE_PUBLISHABLE_KEY });
});
// 启动服务器
const PORT = 3000;
app.listen(PORT, () => {
console.log(`服务器运行在 http://localhost:${PORT}`);
});
根据 Payment Intents API 文档,client_secret 用于前端确认支付。
前端实现
创建 public/index.html:
<!-- public/index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Stripe 支付示例</title>
<script src="https://js.stripe.com/v3/"></script>
<style>
body {
font-family: Arial, sans-serif;
max-width: 500px;
margin: 50px auto;
padding: 20px;
}
#card-element {
border: 1px solid #ccc;
padding: 10px;
border-radius: 4px;
margin-bottom: 20px;
}
button {
background: #5469d4;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
width: 100%;
}
button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
#message {
margin-top: 20px;
padding: 10px;
border-radius: 4px;
}
.success {
background: #d4edda;
color: #155724;
}
.error {
background: #f8d7da;
color: #721c24;
}
</style>
</head>
<body>
<h1>Stripe 支付示例</h1>
<form id="payment-form">
<label for="amount">金额(美元):</label>
<input type="number" id="amount" value="10" min="1" style="width: 100%; padding: 8px; margin-bottom: 20px;" />
<label>信用卡信息:</label>
<div id="card-element"></div>
<button id="submit-button">支付</button>
</form>
<div id="message"></div>
<script src="checkout.js"></script>
</body>
</html>
创建 public/checkout.js:
// public/checkout.js
let stripe;
let elements;
let cardElement;
// 初始化 Stripe
async function initialize() {
// 从后端获取公开密钥
const response = await fetch("/config");
const { publishableKey } = await response.json();
stripe = Stripe(publishableKey);
elements = stripe.elements();
// 创建卡片输入组件
cardElement = elements.create("card");
cardElement.mount("#card-element");
}
// 处理表单提交
document.getElementById("payment-form").addEventListener("submit", async (e) => {
e.preventDefault();
const submitButton = document.getElementById("submit-button");
const messageDiv = document.getElementById("message");
// 禁用按钮
submitButton.disabled = true;
messageDiv.textContent = "处理中...";
messageDiv.className = "";
try {
// 获取金额(转换为分)
const amount = document.getElementById("amount").value * 100;
// 步骤 1: 后端创建 PaymentIntent
const response = await fetch("/create-payment-intent", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ amount }),
});
const { clientSecret } = await response.json();
// 步骤 2: 前端确认支付
const result = await stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: cardElement,
},
});
// 步骤 3: 处理结果
if (result.error) {
// 支付失败
messageDiv.textContent = "支付失败: " + result.error.message;
messageDiv.className = "error";
} else if (result.paymentIntent.status === "succeeded") {
// 支付成功
messageDiv.textContent = "支付成功!";
messageDiv.className = "success";
// 清空表单
cardElement.clear();
}
} catch (error) {
messageDiv.textContent = "错误: " + error.message;
messageDiv.className = "error";
} finally {
// 恢复按钮
submitButton.disabled = false;
}
});
// 页面加载时初始化
initialize();
根据 Stripe.js 文档,confirmCardPayment 方法会自动处理 3D Secure 认证。
运行项目
启动服务器:
node server.js
访问 http://localhost:3000,使用 Stripe 提供的测试卡号:
| 卡号 | 说明 |
|---|---|
| 4242 4242 4242 4242 | 支付成功 |
| 4000 0000 0000 0002 | 卡被拒绝 |
| 4000 0025 0000 3155 | 需要 3D Secure 认证 |
根据 Stripe 测试卡号文档,测试卡号的过期日期使用未来任意日期,CVC 使用任意 3 位数字。
验证支付结果
支付成功后,在 Stripe Dashboard 查看:
- 登录 Stripe Dashboard
- 左侧菜单点击 "Payments"
- 可以看到刚才的测试支付记录
完整支付流程图
1. 用户输入金额和卡号
↓
2. 前端调用后端 API: /create-payment-intent
↓
3. 后端创建 PaymentIntent,返回 clientSecret
↓
4. 前端使用 clientSecret 调用 stripe.confirmCardPayment()
↓
5. Stripe 处理支付(可能需要 3D Secure)
↓
6. 返回支付结果
↓
7. 前端显示成功/失败消息
参考链接:
Webhook 事件处理
Webhook 是 Stripe 主动通知你的服务器支付状态变化的机制。处理 Webhook 是生产环境的必要步骤。
为什么需要 Webhook
前端的支付确认可能不可靠:
- 用户可能在支付成功后关闭浏览器
- 网络中断导致前端无法收到结果
- 异步支付方式(银行转账)需要等待数小时
根据 Stripe Webhooks 文档,Webhook 确保服务器能可靠地接收支付状态通知。
Webhook 工作原理
1. 支付状态变化(如支付成功)
↓
2. Stripe 向你的服务器发送 POST 请求
↓
3. 你的服务器验证请求签名
↓
4. 处理事件(如发货、更新订单状态)
↓
5. 返回 200 状态码给 Stripe
添加 Webhook 处理
在之前的 server.js 中,在 app.use(express.json()) 之前添加 Webhook 端点:
// Webhook 端点需要使用原始请求体,必须在 express.json() 之前
app.post("/webhook", express.raw({ type: "application/json" }), async (req, res) => {
const sig = req.headers["stripe-signature"];
const webhookSecret = process.env.WEBHOOK_SECRET;
let event;
try {
// 验证 Webhook 签名
event = stripe.webhooks.constructEvent(req.body, sig, webhookSecret);
} catch (err) {
console.error("Webhook 签名验证失败:", err.message);
return res.status(400).send(`Webhook Error: ${err.message}`);
}
// 处理不同的事件类型
switch (event.type) {
case "payment_intent.succeeded":
const paymentIntent = event.data.object;
console.log("支付成功:", paymentIntent.id);
console.log("金额:", paymentIntent.amount / 100, paymentIntent.currency);
// 这里执行业务逻辑:
// - 更新订单状态
// - 发送确认邮件
// - 触发发货流程等
break;
case "payment_intent.payment_failed":
const failedPayment = event.data.object;
console.log("支付失败:", failedPayment.id);
console.log("失败原因:", failedPayment.last_payment_error?.message);
// 这里执行业务逻辑:
// - 通知用户支付失败
// - 记录失败日志
break;
case "charge.refunded":
const refund = event.data.object;
console.log("退款成功:", refund.id);
// 这里执行业务逻辑:
// - 更新订单为已退款
// - 通知用户
break;
default:
console.log(`未处理的事件类型: ${event.type}`);
}
// 返回 200 状态码告诉 Stripe 已收到
res.json({ received: true });
});
重要:Webhook 端点必须在 express.json() 之前,因为需要原始请求体来验证签名。根据 Webhook 签名验证文档,验证签名可以防止伪造的 Webhook 请求。
获取 Webhook Secret
在 Stripe Dashboard 中配置 Webhook 并获取 Secret:
- 登录 Stripe Dashboard
- 点击 "Developers" → "Webhooks" → "Add endpoint"
- 填写 Endpoint URL:
https://example.com/webhook- 必须填写一个 URL 才能继续,先填写占位符
- 生产环境时再修改为真实 URL
- 选择要监听的事件:
- 推荐选择:
payment_intent.succeeded、payment_intent.payment_failed、charge.refunded - 或选择 "Select all events"
- 推荐选择:
- 点击 "Add endpoint"
- 进入新创建的 Webhook,点击 "Signing secret" 旁的 "Reveal" 按钮
- 复制显示的
whsec_xxx格式的 secret
在 .env 文件中添加:
# .env
WEBHOOK_SECRET=whsec_xxx # 从 Dashboard 复制的 Signing secret
本地测试 Webhook:
由于本地服务器没有公网 IP,使用 Stripe CLI 转发:
# 安装 Stripe CLI (macOS)
brew install stripe/stripe-cli/stripe
# 登录
stripe login
# 转发 Webhook 到本地
stripe listen --forward-to localhost:3000/webhook
命令行会显示一个临时的 webhook secret,复制到 .env 文件用于本地测试。
常见事件类型
| 事件类型 | 说明 | 使用场景 |
|---|---|---|
payment_intent.succeeded | 支付成功 | 更新订单状态、发货 |
payment_intent.payment_failed | 支付失败 | 通知用户、记录日志 |
charge.refunded | 退款成功 | 更新订单、通知用户 |
customer.subscription.created | 订阅创建 | 开通服务、发送欢迎邮件 |
customer.subscription.deleted | 订阅取消 | 停止服务、通知用户 |
invoice.payment_succeeded | 订阅扣款成功 | 续费成功、发送收据 |
完整事件列表请查看 Stripe Event Types 文档。