Stripe、Paddle、Lemon Squeezy 怎么选:独立开发者收款方案对比

0阅读13分钟

从一个真实的纠结开始

去年底,我的一个 SaaS 产品准备开始收费。功能已经写好, Landing Page 也上线了,就差最后一步——接支付。我打开 Stripe 的文档,花了两个小时跑通了 Checkout Session 的 Demo,感觉一切顺利。然后我注意到一个问题:我的用户分布在 40 多个国家,每个国家的 VAT、GST、销售税规则都不一样。

Stripe 的 Stripe Tax 可以自动计算税率,但需要我自己在税务管辖区注册、申报和缴纳。换句话说,Stripe 帮我算税,但报税和缴税还是我的事。对于一个没有海外公司主体的独立开发者来说,这意味着我可能要同时在欧盟、英国、澳大利亚、日本等地完成税务登记。

后来我了解到 Paddle 和 Lemon Squeezy 这类 Merchant of Record(MoR)方案——它们不是帮我算税,而是直接替我把税报了、缴了。代价是每笔交易多收大约 2% 的手续费。这个取舍值不值,取决于我到底愿意把多少时间花在税务合规上。

这篇文章是我在做选型调研和实际接入过程中积累下来的。不涉及任何平台的推广,纯粹从独立开发者的视角,把选支付方案时会遇到的关键问题拆开讲清楚。

Merchant of Record 与 Payment Processor:最核心的概念分野

选型之前,必须先搞清楚一个底层概念:Merchant of Record(MoR)和 Payment Processor(PSP)的区别。这不是简单的功能差异,而是法律责任归属的根本不同。

Stripe 官方文档对 MoR 的定义是这样的:MoR 是「在法律上被授权且负责处理客户付款的实体」,它承担交易全生命周期的合规责任——包括退款、拒付、PCI DSS 合规、税务计算与申报。而 PSP 只负责「交易处理的技术层面」,也就是把钱从用户账户转到你的账户,其余的事都是你自己的1

Paddle 的博客文章对这个模型有更实操的描述:MoR 模式下存在两笔交易——用户先把钱付给 MoR(reseller),MoR 再把钱结算给你。你的名字不会出现在用户的信用卡账单上,出现在账单上的是 MoR 的名字2

这个区别直接影响三个维度:

维度Payment Processor(如 Stripe)Merchant of Record(如 Paddle)
税务责任你负责计算、申报、缴纳MoR 全程代处理
拒付处理你自行申诉,每笔罚 $15MoR 代为处理
账单显示你的公司名/产品名MoR 的公司名

用 Stripe 的时候,我是卖家,Stripe 是支付工具。用 Paddle 的时候,Paddle 是卖家,我是供应商。这个身份差异在法律层面的含义远大于技术层面。

对于面向全球用户的数字产品和 SaaS 来说,税务合规是最重的隐性成本。欧盟的 OSS(One Stop Shop)机制允许你只在一个成员国登记就可以覆盖全欧盟的 VAT 申报,但前提是你得先完成登记。英国、澳大利亚、加拿大、日本各有各的门槛,有些国家还有年销售额起征点。这些规则每个季度都在变。

Quaderno 的一篇分析指出,使用 PSP 时「你仍然是法律意义上的卖家,必须自行承担税务义务」,而 MoR 模式把这个负担从你身上完全移走了3。对于一个人或小团队来说,这基本上是把一份兼职会计的工作外包给了支付平台。

案例一:税务合规的坑——用 Stripe 直连的全球噩梦

场景

我一开始用的是 Stripe。接入非常快,半天就跑通了支付流程。Stripe Checkout 的 API 设计确实出色,文档清晰,SDK 完善。问题出现在我认真看税务报表的时候。

我的产品在德国有 30% 的用户。德国对数字服务征收 19% 的 MwSt(Mehrwertsteuer,增值税)。Stripe Tax 能帮我自动在结账时向德国用户加收 19%,但 Stripe 不会替我向德国税务局交这笔钱。我需要在德国做税务登记,按季度提交 VAT 申报,把收上来的税款缴到德国财政部。

翻车

我当时犯了一个错误:以为 Stripe Tax 的「自动计算」等于「自动合规」。上线第一个月收到了来自欧盟国家用户的约 €2,400 的付款,其中包含大约 €380 的 VAT。我一分钱都没有申报和缴纳。三个月后收到了一封来自欧盟税务代理的邮件。

修复

后来我评估了两条路:一是用 Stripe Tax + 税务代理服务(比如 Avalara 或 Quaderno),每年大约 $2,000–$5,000 的会计费用;二是切换到 Paddle 这样的 MoR 方案,让平台直接处理税务。

我算了一笔账。按每月 €3,000 的海外收入计算,Stripe 的手续费是 2.9% + $0.30(约 $117/月),加上 Stripe Tax 的 0.5%($15/月),再加上税务代理费(约 $200/月),总成本大约 $332/月。Paddle 的 5% + $0.50 费率下,同样的收入手续费是 $200/月,但包含了全球税务处理。纯看账面,Paddle 反而更便宜。

最终我选择了在 Stripe 上保留本地用户的支付(美国和加拿大),把其他地区的用户路由到 Paddle。这个方案的复杂度在于要维护两套支付通道和用户路由逻辑。

代码对比:税务处理方式

❌ 错误做法:假设支付平台替你处理了税务合规

// 只调 Stripe Checkout,以为加税就完事了
async function createCheckoutSession(priceId: string, customerId: string) {
  // Stripe Tax 的 automatic_tax 只是在结账时向用户多收一笔税
  // 它不会替你向任何税务管辖区申报或缴纳
  const session = await stripe.checkout.sessions.create({
    customer: customerId,
    line_items: [{ price: priceId, quantity: 1 }],
    mode: 'subscription',
    automatic_tax: { enabled: true }, // ← 这只是计算,不是合规
    success_url: `${BASE_URL}/success`,
    cancel_url: `${BASE_URL}/pricing`,
  })
  // 问题:收到的税款停留在你的 Stripe 账户里
  // 你没有在任何国家做税务登记,也没有申报
  return session.url
}

✅ 正确做法:明确区分「计算」和「合规」,两条路选一条

// 方案 A:用 MoR(Paddle),税务全权委托
async function createPaddleSubscription(priceId: string, customerEmail: string) {
  // Paddle 作为 MoR,自动处理 VAT/GST/销售税的计算、申报和缴纳
  // 你收到的结算金额已经是扣除税款后的净收入
  const transaction = await paddle.transactions.create({
    items: [{ price: { id: priceId }, quantity: 1 }],
    customer: { email: customerEmail },
    // 不需要任何税务配置——Paddle 全权负责
  })
  return transaction
}
 
// 方案 B:用 PSP(Stripe),但搭配税务代理服务
async function createStripeSessionWithCompliance(priceId: string, customerId: string) {
  const session = await stripe.checkout.sessions.create({
    customer: customerId,
    line_items: [{ price: priceId, quantity: 1 }],
    mode: 'subscription',
    automatic_tax: { enabled: true },
    // 关键:你还需要自己完成以下步骤
    // 1. 在每个有税务义务的管辖区注册
    // 2. 按季度/月度申报
    // 3. 把代收的税款缴纳给对应税务局
    success_url: `${BASE_URL}/success`,
    cancel_url: `${BASE_URL}/pricing`,
  })
  return session.url
}

这两段代码表面上只差了几行注释,但它们背后代表的合规工作量差了数量级。方案 A 里,你的代码不需要关心任何税务逻辑;方案 B 里,你的代码只负责触发支付,但代码之外的合规工作可能占到每周 5-10 小时。

决策流程图:我该怎么选

下面这个流程图是我在选型过程中总结出来的决策路径。核心判断从公司主体开始,然后看目标市场,再看产品类型。

流程图画布 · 115%
Mermaid 流程图加载中...

这个流程图的关键分支在第二层:有没有海外公司主体。这个问题直接决定了你能不能用 Stripe 的 PSP 模式。没有美国公司或欧洲公司,Stripe 的注册审核就比较难通过,而 MoR 方案因为 Paddle 或 Lemon Squeezy 自己是法律意义上的卖家,它们对供应商的资质要求更宽松。

需要注意的是,2025 年 Stripe 收购了 Lemon Squeezy。收购之后 Lemon Squeezy 暂停了新的独立开发者入驻申请。如果你现在才开始选型,Lemon Squeezy 可能不是一个可选项——至少短期内不是。这个变化也提醒了一件事:依赖单一 MoR 平台有生态风险

案例二:Webhook 幂等性——支付集成中最容易被忽略的坑

场景

不管用 Stripe 还是 Paddle,你都需要处理 Webhook。支付平台在交易状态变化时会向你的服务器推送事件——支付成功、订阅续费、退款完成等等。这些事件可能因为网络重试被推送多次。

翻车

我最初写的 Webhook 处理器是这样的:收到 checkout.session.completed 事件就直接给用户发货(激活订阅)。结果有一次 Stripe 因为超时重试了这个事件,我的系统给用户激活了两次订阅权益,而且第二次触发了计费周期的错误计算。更糟的是,payment_intent.succeededcheckout.session.completed 几乎同时到达,两个事件都触发了发货逻辑。

修复

Stripe 的官方文档明确说了:「Stripe 可能会发送重复的 Webhook 事件」4。正确的做法是基于事件的 ID 做幂等性检查——在处理任何事件之前,先查一下这个事件 ID 是不是已经处理过了。

代码对比:Webhook 处理

❌ 错误做法:不做幂等性检查,直接处理事件

// app/api/webhooks/stripe/route.ts
export async function POST(req: Request) {
  const event = await stripe.webhooks.constructEvent(
    await req.text(),
    req.headers.get('stripe-signature')!,
    process.env.STRIPE_WEBHOOK_SECRET!
  )
 
  // 问题:没有检查事件是否已处理
  // 同一个事件可能被推送多次(网络重试、平台重发)
  switch (event.type) {
    case 'checkout.session.completed':
      // 每次收到都执行发货,重复事件 = 重复发货
      await activateSubscription(event.data.object.customer)
      break
    case 'customer.subscription.deleted':
      await revokeSubscription(event.data.object.customer)
      break
  }
  return new Response('ok')
}

✅ 正确做法:基于事件 ID 做幂等性检查

export async function POST(req: Request) {
  const event = await stripe.webhooks.constructEvent(
    await req.text(),
    req.headers.get('stripe-signature')!,
    process.env.STRIPE_WEBHOOK_SECRET!
  )
 
  // 先查这个事件是否已经处理过
  const existing = await db.webhookEvents.findUnique({
    where: { eventId: event.id }  // event.id 是 Stripe 给的唯一标识
  })
  if (existing) {
    // 已处理过,直接返回 200,不重复执行
    return new Response('ok')
  }
 
  // 在事务中:记录事件 + 执行业务逻辑
  // 用数据库事务保证原子性,防止并发处理同一个事件
  await db.$transaction(async (tx) => {
    await tx.webhookEvents.create({
      data: { eventId: event.id, type: event.type, processedAt: new Date() }
    })
 
    switch (event.type) {
      case 'checkout.session.completed':
        await activateSubscription(tx, event.data.object.customer)
        break
      case 'customer.subscription.deleted':
        await revokeSubscription(tx, event.data.object.customer)
        break
      case 'invoice.payment_failed':
        await handlePaymentFailure(tx, event.data.object.customer)
        break
    }
  })
  return new Response('ok')
}

Paddle 的 Webhook 机制也类似,事件类型不同(比如 transaction.completedsubscription.created),但幂等性处理的原则完全一样。MoR 和 PSP 在这个层面的技术要求没有区别——你都得保证同一个事件不会导致重复的业务操作。

Stigg 的一篇工程博客总结了一个实用的建议:对所有 Webhook 事件都先存一张事件日志表,即使你当前不需要这个事件类型。这样当你日后需要排查问题时,有完整的事件时间线可以追溯5

深度功能对比

定价模型对比

费用项目StripePaddleLemon Squeezy
基础费率2.9% + $0.305% + $0.505% + $0.50
跨境交易+1.5%包含包含
货币转换+1%包含包含
Stripe Tax / 税务自动化+0.5%包含包含
实际全球到账综合费率约 4.9%–5.9%约 5%–5.5%约 5%–5.5%
订阅计费附加费+0.5%(Billing)包含包含
拒付争议费$15/笔包含包含

这张表揭示了一个常见的误判:Stripe 的 2.9% 看起来比 Paddle 的 5% 低很多,但加上跨境、货币转换和税务工具的费用之后,面向全球用户时两者的实际成本几乎一样

GlobalSolo 2026 年的一篇综合评测给出了更详细的计算:对于一个月收入 $3,000、平均客单价 $29 的 SaaS 产品,Stripe 的综合成本(含税务代理)约 $332/月,Paddle 约 $200/月。只有当你的用户绝大部分在同一国家(不需要跨境和货币转换),Stripe 才能体现出费率优势6

功能矩阵对比

功能StripePaddleLemon Squeezy
API 质量与文档业界标杆良好简洁
订阅管理(升降级/按比例计费)完整,需自行配置原生支持原生支持
客户门户(自助管理订阅)Stripe Customer PortalPaddle 内置内置
许可证管理需第三方支持多席位许可内置许可证密钥
联盟营销需第三方需第三方内置联盟系统
Webhook 事件类型200+50+30+
沙盒测试环境完整有 sandbox有 sandbox
提现周期48 小时滚动15–30 天双周或月结
争议处理自行申诉平台代为处理平台代为处理

合规责任对比

合规事项Stripe(PSP)Paddle(MoR)Lemon Squeezy(MoR)
VAT/GST 计算Stripe Tax 辅助(+0.5%)自动自动
VAT/GST 申报自行或委托代理平台负责平台负责
VAT/GST 缴纳自行缴纳平台缴纳平台缴纳
发票开具自行(或用 Stripe Invoicing)平台开具平台开具
PCI DSS 合规你负责(Stripe 提供组件降低范围)平台负责平台负责
退款处理你决定并操作你发起,平台执行你发起,平台执行
数据隐私(GDPR 等)你负责用户数据平台作为卖家承担主要责任平台作为卖家承担主要责任

适用场景对比

场景推荐方案原因
面向北美的 B2B SaaS,有高客单价Stripe费率优势在大金额上明显;$299/月的产品省 $9/月
面向全球的独立开发者 SaaSPaddleMoR 免去多国税务,企业级订阅功能齐全
数字产品 + 软件许可证混合销售Lemon Squeezy(如仍可申请)许可证管理和联盟系统内置
没有海外公司主体Paddle注册门槛低,以 MoR 身份承担卖家责任
需要高度定制支付流程StripeAPI 灵活度最高,支持自定义 checkout、复杂计费模型
初期验证阶段,月收入 < $1,000Paddle零合规负担,快速上线优先

案例三:平台迁移——Lemon Squeezy 被收购后的 Plan B

场景

2025 年 Stripe 收购了 Lemon Squeezy 之后,一批依赖 Lemon Squeezy 做支付和许可证管理的独立开发者被迫考虑迁移。这不是一个假设场景,而是真实发生在社区里的事。

翻车

有些开发者的系统深度耦合了 Lemon Squeezy 的特定 API——直接用 Lemon Squeezy 的许可证密钥接口做软件激活,用它的 Checkout 链接做跳转,用它的 Webhook 事件驱动用户权限变更。当需要迁移到 Paddle 或其他方案时,发现业务逻辑和 Lemon Squeezy 的 API 结构绑死了,迁移工作量几乎等于重写整个支付模块。

修复

合理的做法是在业务代码和支付平台 SDK 之间加一层抽象。不是每个项目都需要完整的 Strategy 模式,但至少应该把支付相关的逻辑集中到一个模块里,对外暴露统一的接口。

代码对比:支付模块抽象

❌ 错误做法:业务代码直接耦合特定平台 API

// 散落在各个业务模块中的支付调用
// 用户激活模块
async function onPaymentSuccess(lemonSqueezyWebhook: any) {
  const licenseKey = await lemonSqueezy.licenseKeys.get(
    lemonSqueezyWebhook.meta.custom_data.license_key_id
  )
  await db.users.update({
    where: { email: lemonSqueezyWebhook.meta.custom_data.email },
    data: {
      plan: 'pro',
      // 直接依赖 Lemon Squeezy 的许可证密钥结构
      licenseKey: licenseKey.data.attributes.serial_key,
      activatedAt: new Date(),
    }
  })
}
 
// 退款处理——也在不同文件
async function onRefund(lemonSqueezyWebhook: any) {
  const order = await lemonSqueezy.orders.get(
    lemonSqueezyWebhook.meta.order_id
  )
  await revokeAccess(order.data.attributes.user_email)
  // 还要处理许可证停用
  await lemonSqueezy.licenseKeys.deactivate(order.data.attributes.license_key_id)
}

✅ 正确做法:定义统一接口,隔离平台差异

// lib/payment/provider.ts — 统一支付接口
interface PaymentProvider {
  createCheckout(params: {
    priceId: string
    customerEmail: string
    metadata?: Record<string, string>
  }): Promise<{ checkoutUrl: string; sessionId: string }>
 
  handleWebhook(event: RawWebhookEvent): Promise<NormalizedPaymentEvent>
 
  // 统一退款接口——不同平台内部实现不同
  refund(params: {
    transactionId: string
    amount?: number
    reason?: string
  }): Promise<{ refundId: string; status: 'pending' | 'completed' }>
}
 
// lib/payment/normalized-event.ts — 统一事件模型
type NormalizedPaymentEvent =
  | { type: 'payment.success'; transactionId: string; customerId: string; amount: number }
  | { type: 'payment.refunded'; transactionId: string; customerId: string }
  | { type: 'subscription.activated'; customerId: string; planId: string; licenseKey?: string }
  | { type: 'subscription.cancelled'; customerId: string; endsAt: Date }
 
// lib/payment/paddle-provider.ts — Paddle 实现
class PaddleProvider implements PaymentProvider {
  async handleWebhook(event: RawWebhookEvent): Promise<NormalizedPaymentEvent> {
    // 把 Paddle 特有的事件结构转换成统一模型
    // 业务代码只需要处理 NormalizedPaymentEvent
    // ...
  }
}
 
// lib/payment/stripe-provider.ts — Stripe 实现
class StripeProvider implements PaymentProvider {
  async handleWebhook(event: RawWebhookEvent): Promise<NormalizedPaymentEvent> {
    // 把 Stripe 特有的事件结构转换成统一模型
    // ...
  }
}
 
// 业务代码——只依赖抽象,不依赖具体平台
async function onPaymentEvent(event: NormalizedPaymentEvent) {
  switch (event.type) {
    case 'subscription.activated':
      await db.users.update({
        where: { id: event.customerId },
        data: { plan: event.planId, licenseKey: event.licenseKey }
      })
      break
    case 'payment.refunded':
      await revokeAccess(event.customerId)
      break
  }
}

这种抽象层的价值在正常运行时不太明显——毕竟只是多了一层转发。但当你需要迁移支付平台、同时运行 A/B 测试、或者像 Lemon Squeezy 用户那样被迫切换时,这层抽象能帮你把迁移范围控制在 lib/payment/ 目录里,不用去改业务模块的代码。

上线检查清单

以下是我在实际接入支付后总结的检查项,按阶段分组。每一项都是踩坑后补上的。

接入前

  1. 确认公司主体与开户地址——决定你能注册 Stripe(需要公司实体)还是只能走 MoR(个人可申请 Paddle)。
  2. 明确目标市场的税务义务——列出用户最多的前 10 个国家,确认每个国家对数字服务的 VAT/GST 起征点。
  3. 确定产品计费模型——月订阅、年订阅、一次性买断、按用量计费,不同模型对支付平台的功能要求不同。
  4. 准备银行账户与提现路径——确认平台支持的提现方式(电汇、Payoneer、Wise)和到账周期。

开发中

  1. Webhook 处理器必须实现幂等性——基于事件 ID 去重,用数据库事务保证原子性。
  2. 处理所有关键事件类型——至少覆盖:支付成功、支付失败、订阅激活、订阅取消、退款完成、争议发起。
  3. 实现重试与降级逻辑——Webhook 推送失败时平台会重试,你的处理器必须能正确处理重复事件而不崩溃。
  4. 测试 Sandbox 环境的边界情况——过期信用卡、3DS 验证失败、部分退款、订阅降级按比例计费。

上线时

  1. 准备四个关键页面——支付成功页、支付失败页、取消订阅确认页、账单管理页。
  2. 确认价格展示的币种和税费说明——向欧盟用户显示的价格是否含税?向美国用户显示的价格是否标注「+ tax」?
  3. 配置 Webhook 签名验证——不验证签名的 Webhook 端点是严重安全漏洞,任何人都可以伪造事件推送给你的服务器。
  4. 保留完整的支付事件日志——Webhook 原始 payload、处理结果、时间戳,用于客服排查和财务对账。

上线后

  1. 监控支付成功率与拒付率——拒付率超过 1% 可能导致支付账户被冻结。
  2. 定期检查税务合规状态——即使用了 MoR,也要确认平台是否正确处理了每个管辖区的申报。
  3. 建立退款与客服流程——用户发起退款时,你需要在多长时间内响应?MoR 和 PSP 的退款流程差异很大。

我的选择与理由

说了这么多对比和案例,最后记录一下我目前的方案,供同场景的开发者参考。

我的产品面向全球用户,没有海外公司主体,月收入在 $2,000–$5,000 之间。我选了 Paddle 作为主支付通道。理由是:

  • MoR 模式免去了我最担心的税务问题。我不想在 40 个国家做 VAT 登记。
  • Paddle 的订阅管理功能足够用。按比例计费、试用期、升降级、客户门户都有。
  • 提现周期虽然慢(15–30 天),但在我的现金流范围内可接受
  • 手续费 5% + $0.50 看起来高,但加上 Stripe 的隐性成本后差距不大

如果未来月收入超过 $20,000,我会重新评估。到了那个量级,成立美国公司、用 Stripe 的费率优势会更明显。但在早期阶段,把时间花在写产品代码上,而不是研究各国 VAT 规则上,这笔账算得过来。

早期不要同时接多个支付系统。先选一个能覆盖主要用户的方案上线收费,等收入稳定、团队有余力之后再考虑备用通道和支付优化。

参考资料

Footnotes

  1. Stripe. "What Is a Merchant of Record?" Stripe Resources. https://stripe.com/resources/more/merchant-of-record

  2. Paddle. "What Is a Merchant of Record (MoR) + Why Use One?" Paddle Blog. https://www.paddle.com/blog/what-is-merchant-of-record

  3. Quaderno. "Merchant of Record: Pros, Cons & When to Use One for SaaS." Quaderno Blog. https://quaderno.io/blog/merchant-of-record-pros-cons/

  4. Stripe. "Receive Stripe events in your webhook endpoint." Stripe Documentation. https://docs.stripe.com/webhooks

  5. Stigg. "Best practices I wish we knew when integrating Stripe webhooks." Stigg Blog. https://www.stigg.io/blog-posts/best-practices-i-wish-we-knew-when-integrating-stripe-webhooks

  6. GlobalSolo. "Stripe vs Paddle vs Lemon Squeezy vs Gumroad (2026)." GlobalSolo Blog. https://www.globalsolo.global/blog/stripe-vs-paddle-vs-lemon-squeezy-2026

评论 0

0 / 1000