支付集成
支付概述
目前 RavenSaaS 支持 Stripe 网页支付功能。其他支付渠道逐步对接。
配置 Stripe 支付
1. 开通 Stripe 商户
请确保在 RavenSaaS 配置 Stripe 支付前,你已经开通了 Stripe 商户。
2. 创建支付密钥
在 Stripe 开发者控制台,创建支付密钥。
如果是本地调试,你可以选择进入沙盒模式,获取一对测试密钥。

3. 配置环境变量
修改 RavenSaaS 项目配置,开启 Stripe 支付。
配置环境变量,根据自己项目的需求修改支付成功/支付失败/支付取消的回调地址。
1#
2STRIPE_PUBLIC_KEY = "pk_test_xxx"
3STRIPE_PRIVATE_KEY = "sk_test_xxx"
4
5NEXT_PUBLIC_PAY_SUCCESS_URL = "http://localhost:3000/my-orders"
6NEXT_PUBLIC_PAY_FAIL_URL = "http://localhost:3000/#pricing"
7NEXT_PUBLIC_PAY_CANCEL_URL = "http://localhost:3000/#pricing"
4. 创建订单表
创建订单表之前,请确保你已经参考 数据库 一章的步骤,配置好了数据库存储和连接信息。
并且执行 sql 创建了 users
用户信息表。
复制 data/install.sql
orders orders
表的建表语句,在你的数据库里面创建订单信息表。
1CREATE TABLE orders (
2 id SERIAL PRIMARY KEY,
3 order_no VARCHAR(255) UNIQUE NOT NULL,
4 created_at timestamptz,
5 user_uuid VARCHAR(255) NOT NULL DEFAULT '',
6 user_email VARCHAR(255) NOT NULL DEFAULT '',
7 amount INT NOT NULL,
8 interval VARCHAR(50),
9 expired_at timestamptz,
10 status VARCHAR(50) NOT NULL,
11 stripe_session_id VARCHAR(255),
12 credits INT NOT NULL,
13 currency VARCHAR(50),
14 sub_id VARCHAR(255),
15 sub_interval_count int,
16 sub_cycle_anchor int,
17 sub_period_end int,
18 sub_period_start int,
19 sub_times int,
20 product_id VARCHAR(255),
21 product_name VARCHAR(255),
22 valid_months int,
23 order_detail TEXT,
24 paid_at timestamptz,
25 paid_email VARCHAR(255),
26 paid_detail TEXT
27);
5. 配置价格表
RavenSaaS 内置了一个价格表组件(Pricing):src/components/blocks/pricing.tsx
,通过配置数据,展示价格表,默认支持多语言。
src/i18n/messages/en.json
你可以根据自己的需求,修改 pricing 字段下的价格表信息。

6. 预览价格表
配置完成后,打开网站首页,可以看到配置好的价格表。

7. 测试支付
点击价格表的下单按钮,跳转到支付控制台。
如果是测试环境,可以在 Stripe 测试卡 页面,复制一张测试卡号,填写到 Stripe 支付表单,进行支付测试。
常用测试卡号
• 4242 4242 4242 4242
- Visa 卡(成功支付)
• 4000 0000 0000 0002
- 卡被拒绝
• 过期日期:任意未来日期,CVC:任意3位数字

8. 处理支付结果
支付成功后,默认跳转到 /pay-success/xxx
页面,同步处理支付回调。
1import Stripe from "stripe";
2import { handleOrderSession } from "@/services/order";
3import { redirect } from "next/navigation";
4
5export default async function ({ params }: { params: Promise<{ session_id: string }> }) {
6 const { session_id } = await params;
7
8 try {
9 const stripe = new Stripe(process.env.STRIPE_PRIVATE_KEY || "");
10 const session = await stripe.checkout.sessions.retrieve(session_id);
11
12 await handleOrderSession(session);
13
14 } catch (e) {
15 const failUrl = process.env.NEXT_PUBLIC_PAY_FAIL_URL || "/";
16 redirect(failUrl);
17 }
18
19 const successUrl = process.env.NEXT_PUBLIC_PAY_SUCCESS_URL || "/my-orders";
20 redirect(successUrl);
21}
更新完订单状态后,再跳转到配置文件中设置的 NEXT_PUBLIC_PAY_SUCCESS_URL
地址。默认情况下,支付成功后的处理逻辑,只会更新订单的状态和支付信息。
你也可以修改这里的逻辑,加上你自己的业务逻辑。比如发邮件 / 发通知 / 加积分等。
1import { findOrderByOrderNo, updateOrderStatus } from "@/models/order";
2import Stripe from "stripe";
3import { getIsoTimestr } from "@/lib/time";
4
5export async function handleOrderSession(session: Stripe.Checkout.Session) {
6 try {
7 if (
8 !session ||
9 !session.metadata ||
10 !session.metadata.order_no ||
11 session.payment_status !== "paid"
12 ) {
13 throw new Error("invalid session");
14 }
15
16 const order_no = session.metadata.order_no;
17 const paid_email =
18 session.customer_details?.email || session.customer_email || "";
19 const paid_detail = JSON.stringify(session);
20
21 const order = await findOrderByOrderNo(order_no);
22 if (!order || order.status !== "pending") {
23 throw new Error("invalid order");
24 }
25
26 const paid_at = getIsoTimestr();
27 await updateOrderStatus(order_no, "paid", paid_at, paid_email, paid_detail);
28
29 console.log(
30 "handle order session successed: ",
31 order_no,
32 paid_at,
33 paid_email,
34 paid_detail
35 );
36 } catch (e) {
37 console.log("handle order session failed: ", e);
38 throw e;
39 }
40}
支付结果异步通知
同步处理支付结果是不可靠的,可能出现的情况是在跳转过程中,用户误操作关闭了浏览器页面,导致更新订单状态和支付信息的逻辑没办法执行。项目正式上线之前,建议配置支付异步通知。
1. Stripe 后台配置 Webhook
参考 Stripe Webhook 配置文档,配置 Webhook。
本地调试,通过 stripe cli 监听事件:
1stripe listen --events checkout.session.completed,invoice.payment_succeeded --forward-to localhost:3000/api/payment/stripe/notify
把用户支付后的 Stripe 回调事件,转发到本地的 RavenSaaS 服务的 /api/payment/stripe/notify
接口。
2. 修改配置文件
上一步在本地监听成功后,会得到一个 webhook signing secret
,把这个参数的值填写到 RavenSaaS 项目的配置文件中:
1STRIPE_WEBHOOK_SECRET = "whsec_cexxx"

3. 处理支付回调结果
你可以按照自己的实际需求,修改默认的支付回调处理逻辑:
1import Stripe from "stripe";
2import { respOk } from "@/lib/response";
3
4export const runtime = "edge";
5
6export async function POST(req: Request) {
7 try {
8 const stripePrivateKey = process.env.STRIPE_PRIVATE_KEY;
9 const stripeWebhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
10
11 if (!stripePrivateKey || !stripeWebhookSecret) {
12 throw new Error("invalid stripe config");
13 }
14
15 const stripe = new Stripe(stripePrivateKey);
16
17 const sign = req.headers.get("stripe-signature") as string;
18 const body = await req.text();
19 if (!sign || !body) {
20 throw new Error("invalid notify data");
21 }
22
23 const event = await stripe.webhooks.constructEventAsync(
24 body,
25 sign,
26 stripeWebhookSecret
27 );
28
29 console.log("stripe notify event: ", event);
30
31 switch (event.type) {
32 case "checkout.session.completed": {
33 const session = event.data.object;
34
35 const handleResponse = await fetch(`${process.env.NEXT_PUBLIC_WEB_URL}/api/payment/stripe/handle-session`, {
36 method: 'POST',
37 headers: {
38 'Content-Type': 'application/json',
39 },
40 body: JSON.stringify({ session }),
41 });
42
43 if (!handleResponse.ok) {
44 throw new Error(`Failed to handle session: ${handleResponse.statusText}`);
45 }
46
47 break;
48 }
49
50 default:
51 console.log("not handle event: ", event.type);
52 }
53
54 return respOk();
55 } catch (e: any) {
56 console.log("stripe notify failed: ", e);
57 return Response.json(
58 { error: `handle stripe notify failed: ${e.message}` },
59 { status: 500 }
60 );
61 }
62}
在上线到生产环境之前,你需要在 Stripe 生产环境配置支付回调 Webhook。

支付定制化
订阅支付
RavenSaaS 默认支持三种支付方案:
- 一次性扣费:one-time
- 按月订阅扣费:month
- 按年订阅扣费:year
你只需要修改价格表配置,把每个价格方案的 paymentType
字段,设置成上述三个值之一。
同时,按需修改价格 amount
/ credits
/ valid_months
等字段。
举例:按月订阅扣费,月付 $99,购买后得到 30 个积分,有效期 1 个月,则核心的价格表配置信息为:
1{
2 "interval": "month",
3 "amount": 9900,
4 "credits": 30,
5 "valid_months": 1
6}
最佳实践
RavenSaaS 未能适配所有的支付场景。请根据你的实际业务需求,自行修改:
- 价格表组件:src/components/blocks/pricing.tsx
- 支付下单接口:src/app/api/checkout/route.ts
- 支付回调逻辑:src/app/api/payment/stripe/notify/route.ts