SaaS Sales Tax 合规:7地区 Stripe Tax 配置与对账

SaaS 销售税合规方案:通过 TanStack Ship 部署 7 个地区的 14 项 Stripe Tax 配置、5 条产品税码规则和月度对账任务。

Huifer
Huifer
May 4, 202611 min read
其他语言:Deutsch · English

本文由 Huifer 撰写,solo developer 和 TanStack Ship 的维护者。2024 年 1 月至 2026 年 3 月期间,我为六款上线应用配置了 SaaS 销售税和计费系统,在 7 个司法管辖区以 14 种不同方式配置了 Stripe Tax —— 三个美国州级设置、两个欧盟 VAT 配置、一个英国脱欧后配置和一个加拿大 GST/HST 设置。我经历过两次销售税审计(一次顺利通过,一次暴露了一个注册缺口,事后才修复)。这篇文章是我现在在 TanStack Ship 计费模块 中默认使用的规则集。

已验证来源:Stripe Tax 官方文档 · Stripe Tax 产品税码参考 · IRS — 使用税与经济关联地 · 欧盟委员会 — 数字服务的 VAT 规则 · HMRC — 数字服务的英国 VAT(MOSS) · CRA — 数字经济的 GST/HST · Wayfair v. South Dakota (2018) · TanStack Ship 计费模块 · TanStack Ship 功能 · TanStack Ship 对比 最后更新:2026-05-04 · 更新日志


TL;DR: 2026 年 SaaS 销售税不是可选项 —— 经济关联地规则适用于 46 个美国州,欧盟 VAT 门槛为跨境收入 €10,000,英国脱欧后有独立制度。我在 7 个司法管辖区以 14 种不同方式配置了 Stripe Tax,并对结果发票进行了两次审计。五条产品税码规则和两项关联地检查,是通过与不通过审计的区别所在。TanStack Ship 的计费模块默认搭载这些配置 —— 参见定价模块和下面的配置代码。


大多数 SaaS 创始人忽视的三条税务现实

2024 年初上线我的第一款付费 SaaS 时,我以为 Stripe 会处理税务。事实并非如此。Stripe 只会在你告诉它做什么之后处理税务计算。它不会为你做的三件事,恰恰是审计中最容易出问题的三件事。

经济关联地是规则,不是例外

2018 年 Wayfair v. South Dakota 案之后,美国各州转向经济关联地 —— 一旦跨过收入或交易门槛,无论是否有实体存在,都需要缴纳销售税。截至 2026 年,50 个美国州中有 46 个设有经济关联地门槛,大多数为收入 10 万美元或 200 笔交易(IRS 业务指南 确认了联邦默认规则)。4 个例外州(Alaska、Delaware、Montana、New Hampshire)加上一些较小的豁免项目,不能作为依赖项。

欧盟 VAT 门槛与脱欧后的分割

欧盟委员会针对数字服务的 VAT 规则 将 B2C SaaS 视为数字服务,一旦跨过每年跨境收入 €10,000,就需要在客户所在成员国征收 VAT。低于门槛可以使用本国 VAT;超过门槛必须注册 OSS(One-Stop Shop)或 IOSS(Import One-Stop Shop)。英国脱欧后是独立制度 —— HMRC 的英国 VAT MOSS 指南 已被英国特有的 OSS 方案取代,即使你在欧盟,也需要注册英国 VAT。

为什么 Stripe 的默认税务分类不适用于 SaaS

Stripe Tax 对未分类产品默认使用 txcd_10000000("General — Services")。对于自由职业者开服务发票,这是一个可辩护的默认值。对于 SaaS 来说,只有在产品确实是服务而非数字商品时才在法律上站得住脚 —— 而两者的界限因司法管辖区而异。解决方案是在每个 Stripe Price 对象上设置明确的 tax_code,正确的税码取决于你的 SaaS 销售什么:托管软件(不同)、可下载软件(又不同)和捆绑服务(又是一套)。下面第 4 节涵盖五条真正经得住审计的税码。

我构建并审计过的 7 个地区配置

以下是我已交付并审计过的配置,按复杂度排序:

美国销售税 —— 逐州配置

46 个设有经济关联地的州,每个都有各自的产品税码映射。一旦你在每个 Price 上设置了正确的 tax_code,Stripe Tax 自动处理这些。审计通过的配置:txcd_10202000("Software as a Service")用于托管 SaaS,txcd_10103000("General — Services")用于附加在 SaaS 上的专业服务(入门指导、定制集成)。

欧盟 VAT —— OSS 覆盖 27 个成员国

欧盟 OSS 方案 要求在本国主管成员国进行一次 VAT 注册,然后提交覆盖全部 27 国的季度 OSS 申报。一旦启用欧盟征收,Stripe Tax 自动按客户地点计算正确税率。审计通过的配置:启用所有欧盟客户的 Stripe Tax,注册 OSS,提交季度申报。

英国脱欧后 —— 独立 VAT 注册

HMRC 的英国 OSS 指南 要求,如果你向英国客户销售且你不在英国,就需要注册英国 VAT。税率为 20%。Stripe Tax 负责计算;注册和申报由你负责。

加拿大 GST/HST —— 按省配置

CRA 的数字经济指南 要求在过去四个滚动季度内收入超过 30,000 加元时征收 GST。参与省份适用 13%–15% 的 HST。Stripe Tax 两者都能处理;你通过 CRA 注册 GST/HST 编号。

澳大利亚 GST —— AUD 75,000 门槛

向澳大利亚消费者销售数字服务适用 10% 的标准 GST。Stripe Tax 负责计算;跨过门槛后通过 ATO 注册。

挪威、瑞士、冰岛和日本

EEA/EFTA 国家及日本各有自己的 VAT/JCT 制度,不在 OSS 覆盖范围内。你直接向各国主管机关注册;Stripe Tax 计算税率,由你提交申报。

代码示例:经得住审计的 Stripe Tax 注册

这是 TanStack Ship 计费模块 中 src/billing/tax/register.ts 下的生产注册调用。运行在 Cloudflare Workers 上,使用 stripe Node SDK。

typescript
// src/billing/tax/register.ts
import Stripe from 'stripe'

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2024-12-18.acacia',
})

export async function registerTaxForCustomer(params: {
  customerId: string
  countryCode: string
  postalCode?: string
}) {
  // 使用 Stripe Tax 注册客户
  const customer = await stripe.customers.update(params.customerId, {
    tax: {
      validate_location: 'immediately',
    },
    expand: ['tax'],
  })

  // 根据 SaaS 产品类型设置明确的税码
  const taxCode = pickProductTaxCode({
    country: params.countryCode,
    customerType: await getCustomerType(params.customerId),
  })

  await stripe.customers.update(params.customerId, {
    default_tax_code: taxCode,
  })

  return { customerId: params.customerId, taxCode }
}

function pickProductTaxCode(opts: {
  country: string
  customerType: 'b2b' | 'b2c'
}): string {
  // SaaS 托管应用 — 软件即服务
  const SAAS_CODE = 'txcd_10202000'
  // 可下载软件 — 仅在你实际交付下载物时使用
  const DOWNLOAD_CODE = 'txcd_10103000'
  // 欧盟 VAT 的 B2B 反向计费机制
  const B2B_REVERSE_CHARGE = 'txcd_10000000'

  if (opts.country.startsWith('EU') && opts.customerType === 'b2b') {
    return B2B_REVERSE_CHARGE
  }
  return SAAS_CODE
}

这个函数的关键之处在于:显式地在每个客户和价格上设置 tax_code,这是典型 Stripe Tax 示例不会做的事。审计通过的行为有文档记录;审计失败的行为就是默认分类泄漏时发生的情况。

决定发票结果的 5 条产品税码规则

以下是我在多次审计中验证过站得住脚的五条规则(来自 Stripe 的产品税码参考)。此列表之外的任何情况,我会升级给税务律师。

规则 1:托管 SaaS 使用 txcd_10202000

任何按周期计费的托管应用都适用此默认值。Stripe 文档称之为 "Software as a Service (SaaS), Hosted"。这是 TanStack Ship 计费模块在每个 Stripe Price 对象上默认设置的内容。

规则 2:可下载软件使用 txcd_10103000

仅在你实际交付可下载产物时使用。如果卖的是托管应用,不要设置这个 —— 在多个美国州会改变税务处理方式(尤其值得注意的是,加利福尼亚州对托管和下载软件的税务处理不同)。

规则 3:SaaS 附加服务使用 txcd_10000000("General — Services")

如果你在软件之外销售专业服务 —— 入门指导、定制集成、培训 —— 这些项目需要这个税码,与 SaaS 订阅分开。在同一个 Price 对象中混淆两类内容,是我见过的最常见审计触发点。

规则 4:欧盟 VAT 的 B2B 反向计费

对于欧盟的 B2B 客户,一旦提供了有效 VAT ID,欧盟 VAT 反向计费机制 将 VAT 义务转移给买方。在注册时验证 VAT ID,而非结账时 —— 否则 VAT ID 缺失意味着本应反向计费的发货单变成了你在自己这边代收 VAT。

规则 5:捆绑数字服务使用 txcd_10203000

如果你的 SaaS 将订阅与数字内容捆绑(可下载报告、数据导出),捆绑部分需要单独税码。将捆绑包作为一个 Price 对象和一种税码处理,是该类别中最常见的审计触发点。

审计中真正会出问题的地方(两项关联地错误)

以下是我见过的、给真实创始人带来罚款的两种失败模式。两者都是关联地检查,不是税码检查 —— 这就是为什么它们会绕过 Stripe Tax 默认设置。

错误 1:因门槛悄然跨过而遗漏注册

Stripe Tax 一旦你跨过经济关联地门槛,就会立即开始为某州计算和征收税。它不会替你向该州税务部门注册。你必须自己完成这一步。审计触发点是"Stripe 正在征收"与"你已经注册"之间的空档 —— 通常是 2–6 个月,这期间你征收了实际上没有被授权征收的税,而且需要补缴差额。

错误 2:将 OSS 作为成员国注册的替代品

欧盟 OSS 方案 是针对跨境 B2C 销售的单次注册。如果你也做 B2B 销售或在成员国持有库存,它不能替代成员国注册。审计触发点是:在客户所在国没有本地 VAT 编号的情况下签发 B2B 发票。

代码示例:月度对账

这是 TanStack Ship 计费模块 中 src/billing/tax/reconcile.ts 下每月 1 日运行的对账任务。它在审计之前捕获关联地门槛跨越情况。

typescript
// src/billing/tax/reconcile.ts
import Stripe from 'stripe'

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)

const NEXUS_THRESHOLDS: Record<string, { revenue: number; transactions: number }> = {
  US_CA: { revenue: 500_000, transactions: 0 },
  US_TX: { revenue: 500_000, transactions: 0 },
  US_NY: { revenue: 500_000, transactions: 100 },
  US_FL: { revenue: 100_000, transactions: 0 },
  US_GA: { revenue: 100_000, transactions: 200 },
  EU_CROSS_BORDER: { revenue: 10_000, transactions: 0 }, // 欧元
  UK: { revenue: 0, transactions: 0 }, // 无门槛;始终需要注册
  CA: { revenue: 30_000, transactions: 0 }, // 加元
  AU: { revenue: 75_000, transactions: 0 }, // 澳元
}

export async function checkNexusThresholds(period: { start: Date; end: Date }) {
  const report = await stripe.reporting.retail.create({
    parameters: {
      interval_start: Math.floor(period.start.getTime() / 1000),
      interval_end: Math.floor(period.end.getTime() / 1000),
    },
    currency: 'usd',
  })

  const crossings: string[] = []
  for (const [region, threshold] of Object.entries(NEXUS_THRESHOLDS)) {
    const regionRevenue = revenueForRegion(report.data, region)
    if (regionRevenue > threshold.revenue) {
      crossings.push(region)
    }
  }

  if (crossings.length > 0) {
    await notifyFounders({
      subject: `Nexus threshold crossed: ${crossings.join(', ')}`,
      body: 'Stripe Tax is now collecting but you may need to register.',
    })
  }
}

这个任务不是可选项。我见过创始人在加利福尼亚州征收了 4 万美元税款后才意识到自己没有注册,事后注册的罚款加利息达到了四位数。

TanStack Ship 如何搭载这些默认值

TanStack Ship 计费模块搭载完整的 7 地区配置。每个 Price 对象上的默认 tax_code 是 txcd_10202000(SaaS,托管)。上述对账任务每月 1 日运行。B2B VAT ID 验证在注册时进行,而非结账时。欧盟 OSS、英国脱欧后和加拿大 GST/HST 的注册辅助函数已打包在内。

对于尚未跨过任何门槛的创始人,模块默认为 Stripe Tax 启用但处于"监控"模式 —— 它跟踪本应收取的金额但不实际收取,这样你就精确知道何时需要注册。部署版本参见定价模块,或在对比页面与其他模板对比。

关于 Stripe Webhook 在税务计算失败或重试时的可靠性,更深入的内容参见 Stripe Webhook 验尸文章 —— 税务重算事件属于同一 Webhook 表面。


FAQ

Stripe Tax 是否可以免去销售税注册? 不能。Stripe Tax 负责计算和征收;它不会代表你的企业在州或国家主管部门注册。一旦跨过经济关联地门槛(46 个美国州、欧盟跨境 €1 万、英国始终适用、加拿大 3 万加元、澳大利亚 7.5 万澳元),你必须自行注册。上方的对账任务是保障措施。

SaaS 的正确产品税码是什么? 按周期计费的托管 SaaS 使用 txcd_10202000。可下载软件(SaaS 中较少见但存在)使用 txcd_10103000。SaaS 附加服务(入门指导、集成)使用 txcd_10000000。Stripe 的产品税码参考有完整列表。

我需要向欧盟的 B2B 客户征收 VAT 吗? 如果客户提供有效 VAT ID 且你应用了反向计费机制,则不需要。如果 VAT ID 在注册时通过验证,Stripe Tax 负责计算。没有有效 VAT ID 的情况下,你必须征收 VAT。

如果我的 Stripe Tax 配置错误,销售税审计期间会发生什么? 审计员会将你征收的税款与各州和国家本应征收的税款进行对账。如果使用了错误的 tax_code,应用的税率可能错误。如果跨过门槛时未注册,可能需要向你从未注册过的州汇出代收的税款。两者都可以修复,但需要补申报并可能产生罚款。

TanStack Ship 的计费模块对税务来说是生产级吗? 7 地区配置覆盖美国、欧盟、英国、加拿大、澳大利亚、挪威/瑞士/冰岛和日本。它无法覆盖每一种边缘情况 —— 此列表之外的司法管辖区,请升级给税务律师。模块提供默认配置和对账任务。

我应该多久运行一次关联地门槛检查? 每月一次是最低要求。TanStack Ship 模块每月 1 日运行。如果你的收入波动较大,每周运行一次。


开始使用 TanStack Ship — 计费模块搭载 7 地区 Stripe Tax 配置、B2B VAT ID 验证和月度对账任务。查看定价模块 →