PAYATHON 2026

如何在巴基斯坦为 Chrome extension 集成自定义支付方案?

支付阿杰

结论

不必依赖 Google Merchant Account 或 Chrome 内购。可以通过“扩展程序 + 自建后端 + 外部支付页面 + 授权校验”完成收费:

  1. 用户在扩展中点击“购买”。
  2. 扩展打开你的网站或支付服务商托管的结账页。
  3. 支付服务商通过 webhook 将结果通知后端。
  4. 后端记录用户的订阅或永久授权。
  5. 用户登录扩展后,扩展向后端查询授权状态,并据此开放付费功能。

支付渠道可以这样选择:

  • 面向国际用户,可以使用允许巴基斯坦商户入驻的 Merchant of Record(MoR)或国际收单服务。
  • 面向巴基斯坦用户,可以接入 JazzCash、Easypaisa 或支持银行卡收款的本地支付网关。
  • 如果同时面向国内外用户,可以让国际渠道处理海外付款,本地网关处理巴基斯坦境内付款。

服务商是否接受巴基斯坦个人或企业,取决于公司类型、结算银行、币种和合规审核。接入前需要向服务商确认最新的支持范围。

为什么不能继续使用 Chrome 内购

Google Merchant Account 的国家限制无法通过修改扩展代码解决。用户能正常安装扩展,并不表示开发者能在当地开通 Google 的商户结算服务。

支付和授权也需要分开处理:

  • 支付渠道处理收款、退款、税务或拒付。
  • 后端判断某个用户是否拥有扩展的使用权。
  • Chrome extension 展示购买入口,并读取后端返回的授权结果。

不能在支付成功后只向浏览器本地写入一个 paid: true。本地数据可以被修改,授权状态必须以服务端记录为准。

支付方式怎么选

方案一:Merchant of Record

Merchant of Record 通常会代开发者处理支付、发票、部分税务和拒付,再把余额结算给开发者。这类服务适合向多个国家销售订阅或软件许可证。

选择服务商时,需要确认:

  • 是否允许巴基斯坦居民或巴基斯坦注册企业入驻;
  • 是否支持你的结算银行;
  • 是否接受 Chrome extension 或 SaaS 类产品;
  • 是否支持订阅、一次性付款和退款;
  • 是否提供 webhook;
  • 是否要求注册公司、税号或身份验证;
  • 平台费、支付手续费和汇率成本是多少。

Paddle、Lemon Squeezy 等是常见的同类方案,但它们支持的国家和审核政策可能变化。用户能够打开结账页面,不等于开发者一定能通过入驻审核。

方案二:本地支付网关

如果客户主要在巴基斯坦,可以接入 JazzCash、Easypaisa 或当地银行卡网关。常见流程如下:

Chrome extension
       │
       │ 创建订单
       ▼
你的后端 ──────► 支付网关
       │             │
       │ 返回付款 URL │
       ◄─────────────┘
       │
       ▼
浏览器打开托管结账页
       │
       ▼
支付网关通过 webhook 通知后端
       │
       ▼
后端激活用户授权

本地网关通常要求商户接受审核,并提供银行账户和相关经营资料。API 字段、签名算法及回调格式,应以所选网关的当前文档为准。

方案三:直接使用国际支付处理商

如果你在受支持的国家或地区拥有合法公司和银行账户,也可以使用 Stripe 等国际支付处理商。

不要通过虚假地址、借用他人身份或伪造公司所在地来绕过国家限制。这些做法可能导致资金冻结或账户关闭,还会带来税务和合规风险。

推荐的集成架构

后端至少需要提供以下接口:

POST /api/checkout
POST /api/webhooks/payment-provider
GET  /api/license
POST /api/logout

数据库可以保存以下数据:

users
- id
- email
- created_at

orders
- id
- user_id
- provider
- provider_order_id
- status
- amount
- currency

licenses
- id
- user_id
- product_id
- status
- expires_at

不要只根据邮箱判断授权状态。最好同时保存支付平台的客户 ID、订单 ID 或订阅 ID。

扩展端打开结账页

下面是一个 Manifest V3 示例:

{
  "manifest_version": 3,
  "name": "Example Extension",
  "version": "1.0.0",
  "permissions": ["storage", "identity"],
  "host_permissions": ["https://api.example.com/*"],
  "action": {
    "default_popup": "popup.html"
  }
}

用户点击购买按钮后,由后端创建订单:

async function startCheckout() {
  const token = await getAccessToken();

  const response = await fetch("https://api.example.com/api/checkout", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${token}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      productId: "extension-pro"
    })
  });

  if (!response.ok) {
    throw new Error("Unable to create checkout session");
  }

  const { checkoutUrl } = await response.json();

  const url = new URL(checkoutUrl);

  if (url.protocol !== "https:") {
    throw new Error("Invalid checkout URL");
  }

  await chrome.tabs.create({ url: url.href });
}

checkoutUrl 必须由后端生成。不要在扩展中保存支付网关的 secret key、webhook secret 或商户私钥。

后端创建支付订单

以下代码展示了一种与具体网关无关的 Node.js 结构:

import express from "express";

const app = express();

app.post("/api/checkout", authenticateUser, async (req, res) => {
  const { productId } = req.body;

  if (productId !== "extension-pro") {
    return res.status(400).json({ error: "Unknown product" });
  }

  const internalOrder = await createPendingOrder({
    userId: req.user.id,
    productId,
    amount: 1000,
    currency: "PKR"
  });

  const paymentSession = await paymentProvider.createCheckout({
    orderId: internalOrder.id,
    amount: internalOrder.amount,
    currency: internalOrder.currency,
    successUrl: "https://example.com/payment/success",
    cancelUrl: "https://example.com/payment/cancel"
  });

  await saveProviderOrderId(
    internalOrder.id,
    paymentSession.providerOrderId
  );

  res.json({
    checkoutUrl: paymentSession.checkoutUrl
  });
});

其中,paymentProvider.createCheckout() 只是占位接口,需要替换为支付服务商的官方 SDK 或 HTTP API。商品金额必须由后端计算,不能相信扩展传入的价格。

使用 webhook 激活授权

用户被跳转到成功页面,并不能证明付款已经成功。后端应通过经过签名验证的 webhook 确认付款结果:

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

    try {
      event = verifyWebhookSignature({
        rawBody: req.body,
        signature: req.headers["x-provider-signature"],
        secret: process.env.PAYMENT_WEBHOOK_SECRET
      });
    } catch {
      return res.status(400).send("Invalid signature");
    }

    if (event.type === "payment.succeeded") {
      const order = await findOrderByProviderId(event.orderId);

      if (!order) {
        return res.status(404).send("Order not found");
      }

      if (
        event.amount !== order.amount ||
        event.currency !== order.currency
      ) {
        return res.status(400).send("Payment mismatch");
      }

      await markOrderPaidOnce(order.id);
      await activateLicense({
        userId: order.userId,
        productId: order.productId
      });
    }

    if (
      event.type === "subscription.cancelled" ||
      event.type === "payment.refunded"
    ) {
      await updateLicenseFromPaymentEvent(event);
    }

    res.sendStatus(200);
  }
);

各个平台使用的签名头、事件名称和验签方法并不相同。上面的名称只用于说明架构,不能代替服务商提供的官方实现。

Webhook 处理还需要保证幂等性。同一个事件可能发送多次,重复通知不能创建多份授权。

扩展查询授权状态

扩展启动或用户登录后,从服务器读取当前授权状态:

async function refreshLicense() {
  const token = await getAccessToken();

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

  if (!response.ok) {
    throw new Error("Unable to verify license");
  }

  const license = await response.json();

  await chrome.storage.local.set({
    licenseCache: {
      active: license.active,
      expiresAt: license.expiresAt,
      checkedAt: Date.now()
    }
  });

  return license;
}

服务端接口可以返回:

{
  "active": true,
  "plan": "pro",
  "expiresAt": "2027-01-01T00:00:00Z"
}

本地缓存只能用来减少请求,或提供短暂的离线宽限期,不能作为最终的授权依据。对于高价值功能,关键操作仍应由服务器验证。

用户身份如何绑定

常见的绑定方式有三种:

  • 用户在网站和扩展中登录同一个账号;
  • 使用 chrome.identity 完成 OAuth 登录;
  • 用户购买后获得许可证密钥,再到扩展中激活。

与许可证密钥相比,账号登录通常更方便管理订阅、换设备和找回权限。如果使用许可证密钥,应限制可激活的设备数量,并允许用户通过网站撤销旧设备。

不要把 Chrome Web Store 的安装记录当作稳定且可验证的付费身份。

发布和安全注意事项

  • 付款页面应部署在 HTTPS 网站上,不要在扩展页面中收集银行卡号。
  • 优先使用支付服务商托管的结账页,以缩小 PCI 合规范围。
  • 密钥、签名 secret 和商户凭证只能保存在后端。
  • 必须验证 webhook 签名、金额、币种、订单状态和订单归属。
  • 需要为退款、拒付、订阅取消和付款过期设计授权回收流程。
  • 清楚展示价格、计费周期、自动续费规则、退款政策和联系方式。
  • 准备隐私政策,说明扩展会收集哪些账号和支付相关数据。
  • 不要从远程服务器加载并执行 JavaScript。付费功能同样必须遵守 Chrome Web Store 对远程代码和扩展行为的要求。
  • 上线前再次核对 Chrome Web Store 的最新政策,以及所选支付商目前对巴基斯坦商户的准入条件。

较稳妥的实施顺序是先建立账号和许可证后端,再选择一家能够合法结算到你的巴基斯坦个人或企业账户,并且提供 webhook 的支付商。以后即使更换支付渠道,也不必重写扩展的授权系统。

备注:内容仅供参考。