PAYATHON 2026

如何在巴西绕过 Chrome Web Store Payments 收费?

支付小周

结论

可以不使用 Chrome Web Store Payments,改用独立的支付和授权系统,但仍须遵守商店规则。

可以采用以下方案:

  1. 在 Chrome Web Store 免费发布应用或扩展。
  2. 通过第三方支付服务收款。
  3. 要求用户登录账号。
  4. 在自己的服务器上保存试用期、付款状态和授权信息。
  5. 应用启动或用户使用高级功能时,向服务器查询授权状态。

这套架构适用于免费增值、30 天试用和一次性买断。Chrome Web Store Payments 已不适合用作新项目的收费基础,传统 Chrome Apps 也在逐步退出支持。对于新项目,通常应优先考虑 Chrome 扩展、PWA 或普通 Web 应用。

推荐的实现方式

不要只把”已付费”状态保存在 localStorage、Cookie 或扩展配置中。这些数据可能被用户清除或修改,也无法在多台设备之间同步授权。

更可靠的结构是:

Chrome 扩展或 Web 应用
        │
        ├── 用户登录
        │
        ├── 跳转到第三方支付页面
        │
        └── 调用授权查询接口
                    │
                    ▼
                自有服务器
        ├── 用户账户
        ├── 试用开始时间
        ├── 支付订单
        ├── 授权状态
        └── 支付平台 Webhook
                    ▲
                    │
              第三方支付服务

支付平台处理银行卡、当地支付方式、退款和支付结果,服务器则判断用户能否使用完整功能。

选择支付平台时,需要确认该服务当前是否支持巴西商户,同时考虑目标用户所在国家、结算币种和税务要求。相关条件可能变化,接入前应查看服务商最新的商户支持范围。

免费版与付费解锁

可以为每个用户保存一条授权记录。最简单的数据模型如下:

{
  "userId": "user_123",
  "plan": "lifetime",
  "status": "active",
  "trialStartedAt": "2026-09-01T00:00:00Z",
  "trialEndsAt": "2026-10-01T00:00:00Z",
  "purchasedAt": "2026-09-15T12:30:00Z"
}

服务端根据这条记录返回用户当前可用的功能:

{
  "authenticated": true,
  "license": {
    "status": "active",
    "plan": "lifetime"
  },
  "features": [
    "basic",
    "export",
    "cloudSync"
  ]
}

客户端负责展示结果,最终的授权判断应尽量由服务器完成。

30 天试用的服务端判断

下面是一个简化的 Node.js 示例。实际项目应从数据库读取用户信息,而非使用内存对象。

const TRIAL_DURATION_MS = 30 * 24 * 60 * 60 * 1000;

function getEntitlement(user) {
  if (user.licenseStatus === "active") {
    return {
      access: "full",
      reason: "paid"
    };
  }

  const trialStartedAt = new Date(user.trialStartedAt).getTime();
  const trialEndsAt = trialStartedAt + TRIAL_DURATION_MS;
  const now = Date.now();

  if (now < trialEndsAt) {
    return {
      access: "full",
      reason: "trial",
      trialEndsAt: new Date(trialEndsAt).toISOString()
    };
  }

  return {
    access: "limited",
    reason: "trial_expired"
  };
}

不要根据客户端时间判断试用期,否则用户修改系统时间后可能延长试用。试用开始时间和当前时间都应以服务器记录为准。

授权查询接口

可以使用 Express 提供一个简单的授权接口:

import express from "express";

const app = express();

app.get("/api/entitlement", requireAuthentication, async (req, res) => {
  const user = await database.users.findById(req.user.id);

  if (!user) {
    return res.status(404).json({
      error: "USER_NOT_FOUND"
    });
  }

  const entitlement = getEntitlement(user);

  res.json({
    authenticated: true,
    entitlement
  });
});

其中,requireAuthentication 和 database 需要由项目自行实现。身份认证可以使用安全的会话 Cookie、OAuth 或短期访问令牌。

客户端可以这样查询授权:

async function loadEntitlement() {
  const response = await fetch("https://example.com/api/entitlement", {
    method: "GET",
    credentials: "include"
  });

  if (!response.ok) {
    throw new Error(`Entitlement request failed: ${response.status}`);
  }

  return response.json();
}

const result = await loadEntitlement();

if (result.entitlement.access === "full") {
  enablePremiumFeatures();
} else {
  enableLimitedFeatures();
}

如果请求来自 Chrome 扩展,还需要在 manifest.json 中声明服务器域名权限:

{
  "manifest_version": 3,
  "name": "Example Extension",
  "version": "1.0.0",
  "host_permissions": [
    "https://example.com/*"
  ]
}

支付成功后的授权激活

用户从支付页面跳转到”支付成功”页面,并不能作为激活授权的依据。跳转地址可以伪造,浏览器端参数也可以修改。

应当接收支付平台发出的 Webhook,并验证它的签名:

app.post(
  "/webhooks/payment",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    let event;

    try {
      event = verifyWebhookSignature(
        req.body,
        req.headers,
        process.env.PAYMENT_WEBHOOK_SECRET
      );
    } catch {
      return res.status(400).send("Invalid webhook signature");
    }

    if (event.type === "payment.completed") {
      const orderId = event.data.orderId;
      const userId = event.data.metadata.userId;

      await database.transaction(async (transaction) => {
        const alreadyProcessed =
          await transaction.webhookEvents.exists(event.id);

        if (alreadyProcessed) {
          return;
        }

        await transaction.licenses.activate({
          userId,
          plan: "lifetime",
          orderId
        });

        await transaction.webhookEvents.insert({
          eventId: event.id
        });
      });
    }

    res.sendStatus(200);
  }
);

verifyWebhookSignature、事件名称和字段结构必须根据所选支付平台的官方 SDK 实现。示例中的名称仅用于说明架构,不对应任何支付平台的真实 API。

支付服务可能重复发送同一个事件,因此 Webhook 处理还必须具备幂等性。

一次性买断的设计

一次性付款的许可证可以保存为:

{
  "plan": "lifetime",
  "status": "active"
}

销售页面需要明确说明”永久使用”的具体含义,例如:

  • 用户是永久使用已购买版本,还是也能使用以后发布的所有版本?
  • 是否包含云存储、同步等会持续产生服务器成本的功能?
  • 退款或拒付后如何处理许可证?
  • 产品停止运营后,依赖服务器验证的功能是否还能使用?

如果高级功能完全在本地运行,可以让服务器签发带数字签名的许可证,使用户在短期离线时仍可使用。用于生成许可证的私钥不能嵌入扩展。

Chrome 扩展中的限制

扩展代码会分发到用户设备,用户理论上可以检查并修改客户端逻辑。因此,只在客户端限制功能最多能增加破解难度,无法提供绝对保护。

安全边界可以按以下方式划分:

  • 导出额度、云端计算、同步和受保护数据应由服务器强制检查。
  • 客户端只控制界面显示,不能作为可靠的授权依据。
  • 不要把支付密钥、数据库凭据或许可证签名私钥写入扩展。
  • 不要通过服务器下载并执行远程 JavaScript 来解锁功能。扩展代码必须遵守 Chrome Web Store 关于远程托管代码的政策。
  • 缓存授权时应设置合理的有效期,缓存过期后重新查询服务器。

发布前需要检查的事项

使用外部收费不等于可以忽略 Chrome Web Store 的政策。提交前应检查当时有效的开发者计划政策,特别是支付披露、用户数据、隐私政策、远程代码和误导性行为等要求。

还需要处理以下事项:

  • 清楚展示价格、试用期限和退款规则。
  • 说明免费功能和付费功能的范围。
  • 提供恢复购买或重新绑定账号的机制。
  • 验证 Webhook 签名,所有通信都使用 HTTPS。
  • 为登录和授权接口设置限流及会话保护。
  • 根据实际经营方式,处理巴西及用户所在地的税务、发票和消费者保护要求。
  • 如果支付平台不直接承担相关义务,应咨询当地会计或法律专业人士。

更稳妥的落地方案是”商店免费分发 + 第三方支付 + 自有账户与服务端授权”。这种方式可以同时支持受限免费版、30 天试用和一次性买断,也不依赖开发者所在国家是否受到 Chrome Web Store Payments 支持。

备注:内容仅供参考。