creem webhook route.ts
本地来源:模版/template/raphael-starterkit-v1-main/analysis/08-API路由/creem-webhook-route.ts.md
Creem Webhook处理路由 (app/api/webhooks/creem/route.ts) 逐行分析
文件概述
这是处理 Creem 支付系统 Webhook 事件的 API 路由。它负责接收、验证和处理来自 Creem 的各种支付和订阅事件,确保系统状态与 Creem 保持同步。
导入语句分析
import { headers } from "next/headers";
import { NextResponse } from "next/server";
headers: Next.js 函数,用于获取请求头信息NextResponse: Next.js 的响应对象,用于构建API响应
import { verifyCreemWebhookSignature } from "@/utils/creem/verify-signature";
import { CreemWebhookEvent } from "@/types/creem";
verifyCreemWebhookSignature: 签名验证函数CreemWebhookEvent: Webhook事件类型定义
import {
createOrUpdateCustomer,
createOrUpdateSubscription,
addCreditsToCustomer,
} from "@/utils/supabase/subscriptions";
- 导入Supabase相关的业务逻辑函数
- 处理客户、订阅和积分管理
环境变量配置
const CREEM_WEBHOOK_SECRET = process.env.CREEM_WEBHOOK_SECRET!;
- 获取 Webhook 签名密钥
- 使用
!断言该环境变量存在
主要处理函数分析
export async function POST(request: Request) {
try {
const body = await request.text();
const headersList = headers();
const signature = (await headersList).get("creem-signature") || "";
// 验证逻辑...
} catch (error) {
console.error("Error processing webhook:", error);
return new NextResponse("Webhook error", { status: 400 });
}
}
请求体解析
const body = await request.text();
- 获取原始请求体文本
- 保持原始格式用于签名验证
签名验证
const headersList = headers();
const signature = (await headersList).get("creem-signature") || "";
if (
!signature ||
!verifyCreemWebhookSignature(body, signature, CREEM_WEBHOOK_SECRET)
) {
return new NextResponse("Invalid signature", { status: 401 });
}
- 从请求头获取 Creem 签名
- 验证签名确保请求来自 Creem
- 签名验证失败返回 401 未授权
事件解析和分发
const event = JSON.parse(body) as CreemWebhookEvent;
switch (event.eventType) {
case "checkout.completed":
await handleCheckoutCompleted(event);
break;
case "subscription.active":
await handleSubscriptionActive(event);
break;
case "subscription.paid":
await handleSubscriptionPaid(event);
break;
case "subscription.canceled":
await handleSubscriptionCanceled(event);
break;
case "subscription.expired":
await handleSubscriptionExpired(event);
break;
case "subscription.trialing":
await handleSubscriptionTrialing(event);
break;
default:
console.log(
`Unhandled event type: ${event.eventType} ${JSON.stringify(event)}`
);
}
- 解析JSON事件数据
- 使用 switch 语句分发不同类型的事件
- 未处理的事件类型会记录日志
事件处理函数分析
结账完成处理
async function handleCheckoutCompleted(event: CreemWebhookEvent) {
const checkout = event.object;
console.log("Processing completed checkout:", checkout);
try {
// Create or update customer
const customerId = await createOrUpdateCustomer(
checkout.customer,
checkout.metadata?.user_id
);
// Check if this is a credit purchase
if (checkout.metadata?.product_type === "credits") {
await addCreditsToCustomer(
customerId,
checkout.metadata?.credits,
checkout.order.id,
`Purchased ${checkout.metadata?.credits} credits`
);
}
// If subscription exists, create or update it
else if (checkout.subscription) {
await createOrUpdateSubscription(checkout.subscription, customerId);
}
} catch (error) {
console.error("Error handling checkout completed:", error);
throw error;
}
}
处理逻辑
- 客户管理: 创建或更新客户信息
- 产品类型判断: 区分积分购买和订阅购买
- 积分处理: 如果是积分购买,增加用户积分
- 订阅处理: 如果是订阅购买,创建或更新订阅
元数据使用
checkout.metadata?.user_id: 关联用户IDcheckout.metadata?.product_type: 产品类型checkout.metadata?.credits: 积分数量
订阅激活处理
async function handleSubscriptionActive(event: CreemWebhookEvent) {
const subscription = event.object;
console.log("Processing active subscription:", subscription);
try {
// Create or update customer
const customerId = await createOrUpdateCustomer(
subscription.customer as any,
subscription.metadata?.user_id
);
// Create or update subscription
await createOrUpdateSubscription(subscription, customerId);
} catch (error) {
console.error("Error handling subscription active:", error);
throw error;
}
}
处理逻辑
- 客户信息同步: 确保客户信息是最新的
- 订阅状态更新: 将订阅状态更新为激活
订阅付费处理
async function handleSubscriptionPaid(event: CreemWebhookEvent) {
const subscription = event.object;
console.log("Processing paid subscription:", subscription);
try {
// Update subscription status and period
const customerId = await createOrUpdateCustomer(
subscription.customer as any,
subscription.metadata?.user_id
);
await createOrUpdateSubscription(subscription, customerId);
} catch (error) {
console.error("Error handling subscription paid:", error);
throw error;
}
}
处理逻辑
- 更新订阅状态和周期信息
- 确保付费信息正确同步
订阅取消处理
async function handleSubscriptionCanceled(event: CreemWebhookEvent) {
const subscription = event.object;
console.log("Processing canceled subscription:", subscription);
try {
// Update subscription status
const customerId = await createOrUpdateCustomer(
subscription.customer as any,
subscription.metadata?.user_id
);
await createOrUpdateSubscription(subscription, customerId);
} catch (error) {
console.error("Error handling subscription canceled:", error);
throw error;
}
}
处理逻辑
- 更新订阅状态为取消
- 保持客户信息同步
订阅过期处理
async function handleSubscriptionExpired(event: CreemWebhookEvent) {
const subscription = event.object;
console.log("Processing expired subscription:", subscription);
try {
// Update subscription status
const customerId = await createOrUpdateCustomer(
subscription.customer as any,
subscription.metadata?.user_id
);
await createOrUpdateSubscription(subscription, customerId);
} catch (error) {
console.error("Error handling subscription expired:", error);
throw error;
}
}
处理逻辑
- 更新订阅状态为过期
- 触发相应的业务逻辑
订阅试用处理
async function handleSubscriptionTrialing(event: CreemWebhookEvent) {
const subscription = event.object;
console.log("Processing trialing subscription:", subscription);
try {
// Update subscription status
const customerId = await createOrUpdateCustomer(
subscription.customer as any,
subscription.metadata?.user_id
);
await createOrUpdateSubscription(subscription, customerId);
} catch (error) {
console.error("Error handling subscription trialing:", error);
throw error;
}
}
处理逻辑
- 更新订阅状态为试用
- 设置试用期相关信息
设计模式分析
安全性设计
- 签名验证: 确保请求来自可信源
- 错误处理: 完善的异常处理机制
- 日志记录: 详细的操作日志
事件驱动架构
- 事件分发: 使用 switch 语句分发事件
- 处理函数分离: 每种事件类型有独立的处理函数
- 状态同步: 确保本地状态与 Creem 同步
幂等性设计
- 创建或更新: 使用 upsert 操作避免重复创建
- 状态检查: 处理重复事件的情况
- 事务完整性: 确保数据一致性
错误处理策略
全局错误处理
} catch (error) {
console.error("Error processing webhook:", error);
return new NextResponse("Webhook error", { status: 400 });
}
业务逻辑错误处理
} catch (error) {
console.error("Error handling checkout completed:", error);
throw error;
}
错误处理特点
- 日志记录: 详细记录错误信息
- 异常传播: 让调用者知道错误发生
- HTTP状态码: 返回合适的状态码
最佳实践总结
- 安全性: 签名验证确保请求安全
- 可靠性: 完善的错误处理机制
- 可维护性: 清晰的事件处理函数分离
- 监控性: 详细的日志记录
- 扩展性: 易于添加新的事件类型处理
- 幂等性: 处理重复事件的能力
- 性能: 异步处理提高响应速度
部署和监控考虑
环境配置
- 确保
CREEM_WEBHOOK_SECRET环境变量正确配置 - 验证 Supabase 连接配置
监控指标
- Webhook 接收成功率
- 事件处理时间
- 错误发生频率
- 签名验证失败次数
调试建议
- 日志级别: 根据环境调整日志详细程度
- 事件重放: 支持事件重新处理
- 状态检查: 定期检查数据同步状态
这个 Webhook 处理路由是整个支付系统的核心组件,它确保了 Creem 支付系统与应用程序之间的可靠数据同步。通过完善的错误处理、安全验证和事件分发机制,它为整个支付流程提供了坚实的基础。
本文档为站内渲染。原始文件本地路径:saas/source/templates/模版-template-raphael-starterkit-v1-main-analysis-08-API路由-cre-f5f095.md(仅本地保留,不入库不部署)