Papermark集成支付系统:订阅管理与发票系统详解
引言:为什么选择支付系统作为订阅解决方案?
在当今数字化时代,对于开源项目而言,选择一个可靠、安全且易于集成的支付解决方案至关重要。Papermark作为一款开源的DocSend替代品,提供了内置的分析功能和自定义域名支持。为了满足用户对订阅管理和发票系统的需求,Papermark选择集成支付系统作为其订阅解决方案。
支付系统作为全球领先的支付处理平台,具有以下优势:
- 强大的API和开发工具,易于集成到各种应用程序中
- 支持多种支付方式,包括信用卡、借记卡、Apple Pay和Google Pay等
- 完善的订阅管理功能,支持灵活的定价模型
- 自动化的发票生成和发送系统
- 强大的安全保障,符合PCI DSS标准
- 详细的交易记录和财务报告
本文将详细介绍Papermark如何集成支付系统,包括订阅管理、发票生成、支付流程等关键功能的实现。
Papermark与支付系统集成架构
系统架构概览
Papermark与支付系统的集成采用了现代化的微服务架构,确保支付流程的安全性和可靠性。下图展示了系统的整体架构:
核心组件
- 前端应用:负责展示订阅计划、收集用户支付信息
- 后端API:处理订阅创建、管理和查询请求
- 支付系统API:与支付平台交互,处理支付相关操作
- 数据库:存储用户订阅信息和交易记录
- Webhook处理服务:接收并处理支付系统发送的事件通知
- 发票系统:生成和发送订阅发票
订阅管理系统实现
订阅计划设计
Papermark提供了多种订阅计划,以满足不同用户的需求。在lib/constants.ts文件中,我们定义了各种订阅计划的详细信息:
export const PLANS = {
free: {
id: "free",
name: "Free",
description: "Free plan with basic features",
price: 0,
currency: "usd",
features: [
"Up to 5 documents",
"Basic analytics",
"Standard sharing links",
],
},
pro: {
id: "pro",
name: "Pro",
description: "Professional plan for individuals",
price: 12,
currency: "usd",
features: [
"Unlimited documents",
"Advanced analytics",
"Custom domains",
"Password protection",
],
priceId: "price_123456789", // 支付系统价格ID
},
team: {
id: "team",
name: "Team",
description: "Team plan for collaborative work",
price: 29,
currency: "usd",
features: [
"Everything in Pro",
"Up to 5 team members",
"Team analytics dashboard",
"Priority support",
],
priceId: "price_987654321", // 支付系统价格ID
},
};
订阅创建流程
订阅创建是Papermark与支付系统集成的核心功能之一。下面我们详细介绍订阅创建的实现流程:
- 用户选择订阅计划:用户在Papermark界面上选择合适的订阅计划
- 创建支付系统结账会话:后端API调用支付系统API创建结账会话
// lib/api/billing.ts
import { PLANS } from "@/lib/constants";
const paymentSystem = new PaymentSystem(process.env.PAYMENT_SYSTEM_SECRET_KEY as string, {
apiVersion: "2023-10-16",
});
export async function createCheckoutSession(
userId: string,
planId: string,
teamId?: string
) {
// 获取所选计划
const plan = Object.values(PLANS).find((p) => p.id === planId);
if (!plan || plan.id === "free") {
throw new Error("Invalid plan");
}
// 创建或获取客户
const customer = await getOrCreatePaymentSystemCustomer(userId);
// 创建结账会话
const session = await paymentSystem.checkout.sessions.create({
customer: customer.id,
payment_method_types: ["card"],
line_items: [
{
price: plan.priceId,
quantity: 1,
},
],
mode: "subscription",
success_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard/billing?success=true`,
cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard/billing?canceled=true`,
metadata: {
userId,
teamId: teamId || "",
planId,
},
});
return { sessionId: session.id, url: session.url };
}
async function getOrCreatePaymentSystemCustomer(userId: string) {
// 从数据库获取用户
const user = await prisma.user.findUnique({ where: { id: userId } });
if (!user) throw new Error("User not found");
// 如果用户已有支付系统客户ID,直接返回
if (user.paymentSystemCustomerId) {
return paymentSystem.customers.retrieve(user.paymentSystemCustomerId);
}
// 创建新的支付系统客户
const customer = await paymentSystem.customers.create({
email: user.email,
name: user.name || undefined,
metadata: {
userId,
},
});
// 更新用户记录,存储支付系统客户ID
await prisma.user.update({
where: { id: userId },
data: { paymentSystemCustomerId: customer.id },
});
return customer;
}
- 用户完成支付:用户被重定向到支付系统托管的结账页面,完成支付流程
- 处理支付成功事件:支付系统通过webhook通知Papermark支付成功
- 更新订阅状态:Papermark更新用户的订阅状态和权限
订阅管理功能
Papermark提供了完整的订阅管理功能,包括升级、降级和取消订阅等操作:
// lib/api/billing.ts
export async function updateSubscription(
userId: string,
subscriptionId: string,
newPlanId: string
) {
// 获取新计划信息
const newPlan = Object.values(PLANS).find((p) => p.id === newPlanId);
if (!newPlan || newPlan.id === "free") {
throw new Error("Invalid plan");
}
// 获取当前订阅
const subscription = await paymentSystem.subscriptions.retrieve(subscriptionId);
// 更新订阅
const updatedSubscription = await paymentSystem.subscriptions.update(
subscriptionId,
{
items: [
{
id: subscription.items.data[0].id,
price: newPlan.priceId,
},
],
proration_behavior: "create_prorations",
payment_behavior: "default_incomplete",
expand: ["latest_invoice.payment_intent"],
}
);
// 更新数据库中的订阅信息
await prisma.subscription.update({
where: { paymentSystemSubscriptionId: subscriptionId },
data: {
planId: newPlanId,
priceId: newPlan.priceId,
status: updatedSubscription.status,
currentPeriodStart: new Date(updatedSubscription.current_period_start * 1000),
currentPeriodEnd: new Date(updatedSubscription.current_period_end * 1000),
},
});
return updatedSubscription;
}
export async function cancelSubscription(userId: string, subscriptionId: string) {
// 获取当前订阅
const subscription = await paymentSystem.subscriptions.retrieve(subscriptionId);
// 取消订阅(在当前计费周期结束时)
const canceledSubscription = await paymentSystem.subscriptions.update(
subscriptionId,
{
cancel_at_period_end: true,
}
);
// 更新数据库中的订阅状态
await prisma.subscription.update({
where: { paymentSystemSubscriptionId: subscriptionId },
data: {
status: "canceled",
cancelAtPeriodEnd: true,
canceledAt: new Date(),
},
});
return canceledSubscription;
}
发票系统实现
发票生成流程
Papermark利用支付系统的发票功能,实现了自动化的发票生成和发送系统:
发票管理功能
Papermark提供了发票查询和下载功能,方便用户管理财务记录:
// lib/api/billing.ts
export async function getInvoices(userId: string) {
// 获取用户信息
const user = await prisma.user.findUnique({ where: { id: userId } });
if (!user || !user.paymentSystemCustomerId) {
throw new Error("User not found or no payment system customer ID");
}
// 获取用户的订阅
const subscriptions = await prisma.subscription.findMany({
where: { userId },
select: { paymentSystemSubscriptionId: true },
});
if (subscriptions.length === 0) {
return [];
}
// 获取支付系统发票
const invoices = await paymentSystem.invoices.list({
customer: user.paymentSystemCustomerId,
limit: 100,
expand: ["data.subscription"],
});
// 处理发票数据
return invoices.data.map((invoice) => ({
id: invoice.id,
invoiceNumber: invoice.invoice_number || "",
amount: invoice.total / 100, // 转换为美元
currency: invoice.currency.toUpperCase(),
status: invoice.status,
date: new Date(invoice.created * 1000),
dueDate: invoice.due_date ? new Date(invoice.due_date * 1000) : null,
pdfUrl: invoice.invoice_pdf || "",
subscriptionId: invoice.subscription as string,
planName: getPlanNameFromSubscription(invoice.subscription as string),
}));
}
function getPlanNameFromSubscription(subscriptionId: string) {
// 从订阅ID获取计划名称的逻辑
// 实际实现中可能需要查询数据库或支付系统API
return "Pro Plan"; // 示例返回值
}
export async function getInvoicePDF(invoiceId: string, userId: string) {
// 验证用户是否有权限访问此发票
const user = await prisma.user.findUnique({ where: { id: userId } });
if (!user || !user.paymentSystemCustomerId) {
throw new Error("User not found or no payment system customer ID");
}
// 获取发票
const invoice = await paymentSystem.invoices.retrieve(invoiceId);
// 验证发票属于该用户
if (invoice.customer !== user.paymentSystemCustomerId) {
throw new Error("Access denied");
}
// 返回发票PDF URL
return invoice.invoice_pdf;
}
Webhook处理与事件响应
支付系统webhook是Papermark与支付系统之间实时通信的关键组件,用于处理各种支付事件:
// pages/api/webhooks/payment.ts
import type { NextApiRequest, NextApiResponse } from "next";
import { buffer } from "micro";
import { prisma } from "@/lib/prisma";
import { PLANS } from "@/lib/constants";
const paymentSystem = new PaymentSystem(process.env.PAYMENT_SYSTEM_SECRET_KEY as string, {
apiVersion: "2023-10-16",
});
export const config = {
api: {
bodyParser: false,
},
};
const webhookSecret = process.env.PAYMENT_SYSTEM_WEBHOOK_SECRET;
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== "POST") {
res.setHeader("Allow", "POST");
return res.status(405).end("Method Not Allowed");
}
const buf = await buffer(req);
const sig = req.headers["payment-system-signature"] as string;
let event;
try {
event = paymentSystem.webhooks.constructEvent(buf.toString(), sig, webhookSecret);
} catch (err: any) {
console.error(`Webhook Error: ${err.message}`);
return res.status(400).send(`Webhook Error: ${err.message}`);
}
// 处理事件
switch (event.type) {
case "checkout.session.completed":
await handleCheckoutSessionCompleted(event.data.object);
break;
case "invoice.paid":
await handleInvoicePaid(event.data.object);
break;
case "invoice.payment_failed":
await handleInvoicePaymentFailed(event.data.object);
break;
case "customer.subscription.updated":
await handleSubscriptionUpdated(event.data.object);
break;
case "customer.subscription.deleted":
await handleSubscriptionDeleted(event.data.object);
break;
default:
console.log(`Unhandled event type ${event.type}`);
}
res.status(200).json({ received: true });
}
async function handleCheckoutSessionCompleted(session) {
const { userId, teamId, planId } = session.metadata;
if (!userId || !planId) {
console.error("Missing metadata in checkout session");
return;
}
// 获取订阅信息
const subscription = await paymentSystem.subscriptions.retrieve(
session.subscription as string
);
// 创建或更新订阅记录
await prisma.subscription.upsert({
where: { paymentSystemSubscriptionId: subscription.id },
update: {
status: subscription.status,
currentPeriodStart: new Date(subscription.current_period_start * 1000),
currentPeriodEnd: new Date(subscription.current_period_end * 1000),
planId,
priceId: subscription.items.data[0].price.id,
},
create: {
userId,
teamId: teamId || null,
paymentSystemSubscriptionId: subscription.id,
status: subscription.status,
planId,
priceId: subscription.items.data[0].price.id,
currentPeriodStart: new Date(subscription.current_period_start * 1000),
currentPeriodEnd: new Date(subscription.current_period_end * 1000),
},
});
// 更新用户或团队的计划
if (teamId) {
await prisma.team.update({
where: { id: teamId },
data: { plan: planId },
});
} else {
await prisma.user.update({
where: { id: userId },
data: { plan: planId },
});
}
}
// 其他webhook处理函数...
// (handleInvoicePaid, handleInvoicePaymentFailed, handleSubscriptionUpdated, handleSubscriptionDeleted)
安全考虑与最佳实践
数据安全
在处理支付信息时,数据安全至关重要。Papermark采取了以下措施确保支付数据的安全:
- 不存储敏感支付信息:所有支付信息直接由支付系统处理和存储,Papermark不存储任何信用卡信息。
- 使用HTTPS:所有API通信都通过HTTPS加密。
- 实施webhook签名验证:确保所有来自支付系统的webhook事件都是真实有效的。
// 验证webhook签名
const event = paymentSystem.webhooks.constructEvent(
buf.toString(),
sig,
webhookSecret
);
- 最小权限原则:API密钥和敏感配置通过环境变量管理,只授予必要的权限。
错误处理与日志记录
Papermark实现了完善的错误处理和日志记录机制,确保支付流程的可靠性:
// lib/api/billing.ts
export async function safeCreateCheckoutSession(
userId: string,
planId: string,
teamId?: string
) {
try {
// 记录创建结账会话的尝试
console.log(`Creating checkout session for user ${userId}, plan ${planId}`);
const result = await createCheckoutSession(userId, planId, teamId);
// 记录成功创建结账会话
console.log(`Checkout session created: ${result.sessionId}`);
return result;
} catch (error) {
// 记录错误详情
console.error(`Error creating checkout session: ${error.message}`, {
userId,
planId,
teamId,
stack: error.stack,
});
// 抛出友好的错误信息
throw new Error("Failed to create checkout session. Please try again later.");
}
}
合规性考虑
Papermark确保其支付系统符合相关法规和标准:
- PCI DSS合规:通过使用支付系统,Papermark减少了PCI DSS合规的负担。
- 数据保护:符合GDPR等数据保护法规,用户可以随时导出或删除其数据。
结论与未来展望
通过集成支付系统,Papermark为用户提供了强大而灵活的订阅管理和发票系统。本文详细介绍了集成的核心组件、实现细节和最佳实践。
未来,Papermark计划在支付系统方面进行以下改进:
- 支持更多支付方式:如银行转账、其他数字支付方式等。
- 增强的发票定制功能:允许用户自定义发票模板和信息。
- 多币种支持:支持多种货币的定价和支付。
- 更详细的财务分析:提供收入报告和趋势分析。
通过不断优化支付体验,Papermark致力于为用户提供更好的服务,同时为开源项目的可持续发展提供支持。
参考资料
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



