PAYATHON 2026

能否将 Flattr 用于 Paywall 下载并检查登录状态?

支付阿杰

结论

Flattr 不适合充当 Paywall 或一次性购买系统。不能要求用户必须完成一次 Flattr 才能下载内容,也不应在下载过程中默认替用户 Flattr 平台,再让用户自行取消。

Flattr 是用户主动表达支持的方式,不是固定金额的支付凭证。旧版 API 即使能在技术上发起 Flattr,也不代表这种用法符合服务条款。如果要采用“支持后解锁下载”的模式,应先取得 Flattr 官方的书面许可。否则,应通过正规的支付服务处理内容解锁,并把 Flattr 作为独立且可选的支持入口。

网站也无法可靠地直接判断用户是否已登录 flattr.com。应通过 OAuth 授权确认用户是否已将 Flattr 账户连接到你的平台。

为什么不适合作为 Paywall

一次 Flattr 与普通支付交易有明显区别:

  • 内容提供方通常无法设定固定金额。
  • 用户可能已经 Flattr 过同一对象,再次操作未必会产生一笔新的支持。
  • API 请求成功只表示 Flattr 接受了操作,不代表用户完成了一笔固定金额的购买。
  • 用户撤销授权、额度状态改变或服务规则调整,都可能影响结果。
  • 通过下载按钮触发 Flattr 时,必须让用户明确知情并主动同意,不能在后台隐藏执行。

允许用户提前试听可以改善体验,但不会改变流程本身的性质。只要用户“不 Flattr 就不能下载”,这仍然是用 Flattr 换取访问权限。

推荐的流程

更稳妥的做法是将下载和 Flattr 支持分开:

  1. 用户访问内容页面并试听。
  2. 用户通过正常支付渠道购买下载权限,或者直接免费下载。
  3. 页面另外提供“通过 Flattr 支持创作者”按钮。
  4. 用户点击后,可以清楚看到自己将要支持的对象。
  5. 只有用户明确确认,服务器才能调用 Flattr API。

如果还希望用户支持平台,可以提供第二个默认不选中的选项,例如:

[ ] 同时通过 Flattr 支持本平台

不要默认勾选,也不要把这项支持藏在“下载”按钮背后。明确的 opt-in 比 opt-out 更能说明这是用户主动做出的决定。

如何判断用户是否连接了 Flattr

浏览器不能读取 Flattr 的登录 Cookie。不同域名受到同源策略和 Cookie 安全属性的限制,Flattr 也不应向第三方网站暴露用户的登录状态。

应使用 OAuth 流程:

  1. 用户点击“连接 Flattr”。
  2. 平台将用户重定向到 Flattr 的 OAuth 授权页面。
  3. Flattr 要求用户登录并确认授权。
  4. Flattr 将用户重定向回平台的回调地址。
  5. 平台在服务器端用授权码换取 access token。
  6. 平台保存连接状态,并通过受保护的 API 验证 token。

下面是示意代码。端点名称沿用 Flattr 历史上的 OAuth/API 设计,实际接入前必须核对目标服务的当前文档:

app.get("/connect/flattr", (req, res) => {
  const state = createAndStoreCsrfState(req.session);

  const authorizationUrl = new URL(
    "https://flattr.com/oauth/authorize"
  );

  authorizationUrl.searchParams.set("response_type", "code");
  authorizationUrl.searchParams.set("client_id", process.env.FLATTR_CLIENT_ID);
  authorizationUrl.searchParams.set(
    "redirect_uri",
    "https://example.com/oauth/flattr/callback"
  );
  authorizationUrl.searchParams.set("state", state);

  res.redirect(authorizationUrl.toString());
});

处理回调时,必须验证 state,并在服务器端交换 token:

app.get("/oauth/flattr/callback", async (req, res) => {
  if (!isValidCsrfState(req.session, req.query.state)) {
    return res.status(400).send("Invalid OAuth state");
  }

  const token = await exchangeAuthorizationCode({
    code: req.query.code,
    redirectUri: "https://example.com/oauth/flattr/callback"
  });

  await saveEncryptedAccessToken(req.session.userId, token.access_token);
  res.redirect("/account");
});

应用中的“已登录”最好改为更准确的“已连接 Flattr”:

async function getFlattrConnection(userId) {
  const token = await loadAccessToken(userId);

  if (!token) {
    return { connected: false };
  }

  const response = await fetch("https://api.flattr.com/rest/v2/user", {
    headers: {
      Authorization: `Bearer ${token}`
    }
  });

  if (response.status === 401 || response.status === 403) {
    return { connected: false, reauthorizationRequired: true };
  }

  if (!response.ok) {
    throw new Error(`Flattr API returned ${response.status}`);
  }

  return {
    connected: true,
    profile: await response.json()
  };
}

这段流程只能确认 access token 是否仍然有效,不能判断用户此刻是否在另一个浏览器标签页中登录了 Flattr。这是两个不同的状态。

发起 Flattr 时的安全要求

如果已经取得官方许可,并决定继续使用相关 API,应从服务器端发起调用,同时满足以下要求:

  • 每次调用都必须由用户明确操作触发。
  • 按钮应清楚说明用户将 Flattr 哪个对象。
  • 不得在页面加载、OAuth 回调或开始下载时自动调用。
  • access token 只能保存在服务器端,并且应加密存储。
  • OAuth 回调必须验证 state,防止 CSRF。
  • 不要把 access token 放进 URL、前端代码或下载链接。
  • 正确处理 401、403、限流和网络错误。
  • 防止用户重复点击造成重复请求。
  • 下载授权不能只依赖前端变量或按钮状态。

还需要注意服务现状

Flattr 的产品形态、API 可用性和服务条款曾多次变化。上面的 OAuth 端点和 REST v2 示例只用于说明旧版集成方式,不能据此认定这些接口现在仍然可用。

正式开发前,需要确认三件事:

  1. Flattr 当前是否仍向你的账户提供所需 API。
  2. 当前条款是否明确允许使用 Flattr 解锁内容。
  3. 自动发起或组合发起多个 Flattr 是否需要额外授权。

如果没有得到明确许可,最安全的实现方式是用支付服务处理付费下载,并将 Flattr 作为完全自愿、独立提供且需要用户明确确认的支持功能。

备注:内容仅供参考。