Papermark集成支付系统:订阅管理与发票系统详解

Papermark集成支付系统:订阅管理与发票系统详解

【免费下载链接】papermark Papermark is the open-source DocSend alternative with built-in analytics and custom domains. 【免费下载链接】papermark 项目地址: https://gitcode.com/GitHub_Trending/pa/papermark

引言:为什么选择支付系统作为订阅解决方案?

在当今数字化时代,对于开源项目而言,选择一个可靠、安全且易于集成的支付解决方案至关重要。Papermark作为一款开源的DocSend替代品,提供了内置的分析功能和自定义域名支持。为了满足用户对订阅管理和发票系统的需求,Papermark选择集成支付系统作为其订阅解决方案。

支付系统作为全球领先的支付处理平台,具有以下优势:

  • 强大的API和开发工具,易于集成到各种应用程序中
  • 支持多种支付方式,包括信用卡、借记卡、Apple Pay和Google Pay等
  • 完善的订阅管理功能,支持灵活的定价模型
  • 自动化的发票生成和发送系统
  • 强大的安全保障,符合PCI DSS标准
  • 详细的交易记录和财务报告

本文将详细介绍Papermark如何集成支付系统,包括订阅管理、发票生成、支付流程等关键功能的实现。

Papermark与支付系统集成架构

系统架构概览

Papermark与支付系统的集成采用了现代化的微服务架构,确保支付流程的安全性和可靠性。下图展示了系统的整体架构:

mermaid

核心组件

  1. 前端应用:负责展示订阅计划、收集用户支付信息
  2. 后端API:处理订阅创建、管理和查询请求
  3. 支付系统API:与支付平台交互,处理支付相关操作
  4. 数据库:存储用户订阅信息和交易记录
  5. Webhook处理服务:接收并处理支付系统发送的事件通知
  6. 发票系统:生成和发送订阅发票

订阅管理系统实现

订阅计划设计

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与支付系统集成的核心功能之一。下面我们详细介绍订阅创建的实现流程:

  1. 用户选择订阅计划:用户在Papermark界面上选择合适的订阅计划
  2. 创建支付系统结账会话:后端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;
}
  1. 用户完成支付:用户被重定向到支付系统托管的结账页面,完成支付流程
  2. 处理支付成功事件:支付系统通过webhook通知Papermark支付成功
  3. 更新订阅状态: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利用支付系统的发票功能,实现了自动化的发票生成和发送系统:

mermaid

发票管理功能

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采取了以下措施确保支付数据的安全:

  1. 不存储敏感支付信息:所有支付信息直接由支付系统处理和存储,Papermark不存储任何信用卡信息。
  2. 使用HTTPS:所有API通信都通过HTTPS加密。
  3. 实施webhook签名验证:确保所有来自支付系统的webhook事件都是真实有效的。
// 验证webhook签名
const event = paymentSystem.webhooks.constructEvent(
  buf.toString(),
  sig,
  webhookSecret
);
  1. 最小权限原则: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确保其支付系统符合相关法规和标准:

  1. PCI DSS合规:通过使用支付系统,Papermark减少了PCI DSS合规的负担。
  2. 数据保护:符合GDPR等数据保护法规,用户可以随时导出或删除其数据。

结论与未来展望

通过集成支付系统,Papermark为用户提供了强大而灵活的订阅管理和发票系统。本文详细介绍了集成的核心组件、实现细节和最佳实践。

未来,Papermark计划在支付系统方面进行以下改进:

  1. 支持更多支付方式:如银行转账、其他数字支付方式等。
  2. 增强的发票定制功能:允许用户自定义发票模板和信息。
  3. 多币种支持:支持多种货币的定价和支付。
  4. 更详细的财务分析:提供收入报告和趋势分析。

通过不断优化支付体验,Papermark致力于为用户提供更好的服务,同时为开源项目的可持续发展提供支持。

参考资料

  1. 支付系统官方文档
  2. Papermark GitHub仓库
  3. Next.js API路由文档
  4. Prisma ORM文档

【免费下载链接】papermark Papermark is the open-source DocSend alternative with built-in analytics and custom domains. 【免费下载链接】papermark 项目地址: https://gitcode.com/GitHub_Trending/pa/papermark

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值