PAYATHON 2026

如何通过 Facebook credits API / Facebook payment API 处理支付

支付阿杰

结论

Facebook 并没有通用的 credits API 或 payment API,应用无法通过这类接口向用户转账、发放 Facebook Credits,也不能直接从用户账户中扣除 Credits。

Facebook Credits 已于 2013 年停止使用,旧版 Credits API、支付对话框和回调接口都不适用于新应用。目前常见的做法是:

  1. 由应用维护自己的虚拟积分系统;
  2. 通过合规的第三方支付渠道收款;
  3. 支付成功后,由服务端更新用户的积分余额;
  4. 如果应用运行在 Facebook、Messenger、WhatsApp 或 Meta Quest 等平台内,还要遵守相应产品现行的支付政策。

Graph API 的访问令牌不能用于转移资金,也不能直接修改用户的 Facebook 或 Meta 账户余额。

为什么找不到可用的 API

“Facebook Credits”是 Facebook 早期推出的虚拟货币,主要用于平台内的游戏和应用。开发者不能自行铸造 Credits。用户购买 Credits 后,应用只能通过当时规定的支付流程收取。

这套系统已经退出。网上仍然可以找到类似下面的旧代码:

FB.ui({
  method: 'pay',
  action: 'purchaseitem',
  product: 'https://example.com/product.html'
});

还有 payments_get_items、payments_status_update 等旧接口名称。它们都是历史接口,不能说明现在仍可使用。即使某些旧文档或示例页面还能打开,也不代表新应用可以接入。

Meta Pay 面向消费者提供支付体验,不是任意应用都能调用的通用收付款 API。

推荐的实现方式

应用可以维护自己的虚拟积分,但“支付记录”和“积分余额”都应保存在服务端,不能只依赖前端状态。

常见流程是:

  1. 用户在应用中选择积分套餐;
  2. 服务端创建支付订单;
  3. 用户通过支付服务商付款;
  4. 支付服务商向服务端发送 webhook;
  5. 服务端验证 webhook 的签名和支付状态;
  6. 服务端使用数据库事务记录付款并增加用户积分;
  7. 返回最新余额。

免费赠送积分不需要调用 Facebook API。服务端可以按照签到、活动奖励或人工补偿等业务规则增加积分,同时保存可审计的流水。

数据模型示例

不要只修改用户表中的余额数字。至少需要保存账户、流水和支付订单:

CREATE TABLE credit_accounts (
    user_id BIGINT PRIMARY KEY,
    balance BIGINT NOT NULL DEFAULT 0
);

CREATE TABLE credit_transactions (
    id BIGINT PRIMARY KEY,
    user_id BIGINT NOT NULL,
    amount BIGINT NOT NULL,
    type VARCHAR(32) NOT NULL,
    reference_id VARCHAR(128) NOT NULL,
    created_at TIMESTAMP NOT NULL,
    UNIQUE (type, reference_id)
);

CREATE TABLE payment_orders (
    id VARCHAR(128) PRIMARY KEY,
    user_id BIGINT NOT NULL,
    amount_minor BIGINT NOT NULL,
    currency CHAR(3) NOT NULL,
    credits BIGINT NOT NULL,
    status VARCHAR(32) NOT NULL
);

amount_minor 表示货币的最小单位,例如人民币的分。积分最好使用整数,以免出现浮点数误差。

支付成功后发放积分

下面是一个与具体支付服务商无关的服务端结构示例。verifyWebhook() 必须按照实际支付平台的签名规范实现,不能直接信任请求正文。

app.post('/webhooks/payment', rawBodyMiddleware, async (req, res) => {
  let event;

  try {
    event = verifyWebhook({
      rawBody: req.body,
      signature: req.headers['payment-signature']
    });
  } catch {
    return res.status(400).send('Invalid signature');
  }

  if (event.type !== 'payment.succeeded') {
    return res.sendStatus(200);
  }

  const providerPaymentId = event.data.paymentId;
  const orderId = event.data.orderId;

  await database.transaction(async (tx) => {
    const order = await tx.paymentOrders.lockForUpdate(orderId);

    if (!order) {
      throw new Error('Order not found');
    }

    if (order.status === 'paid') {
      return;
    }

    if (
      event.data.amountMinor !== order.amount_minor ||
      event.data.currency !== order.currency
    ) {
      throw new Error('Payment amount mismatch');
    }

    await tx.creditTransactions.insert({
      user_id: order.user_id,
      amount: order.credits,
      type: 'payment',
      reference_id: providerPaymentId
    });

    await tx.creditAccounts.increment(
      order.user_id,
      order.credits
    );

    await tx.paymentOrders.update(orderId, {
      status: 'paid'
    });
  });

  res.sendStatus(200);
});

UNIQUE (type, reference_id) 和订单状态检查用于保证幂等性。支付服务商可能重复发送 webhook。缺少幂等控制时,同一笔付款可能导致积分被重复发放。

免费赠送积分

应用赠送积分时,同样应通过服务端流水处理:

async function grantCredits(userId, credits, campaignId) {
  if (!Number.isInteger(credits) || credits <= 0) {
    throw new Error('Invalid credit amount');
  }

  await database.transaction(async (tx) => {
    await tx.creditTransactions.insert({
      user_id: userId,
      amount: credits,
      type: 'campaign_reward',
      reference_id: campaignId
    });

    await tx.creditAccounts.increment(userId, credits);
  });
}

campaignId 应当唯一标识一次奖励资格,防止用户重复领取。

接收或消费积分

用户消费应用积分时,需要在数据库事务中锁定账户并检查余额:

async function spendCredits(userId, credits, purchaseId) {
  await database.transaction(async (tx) => {
    const account = await tx.creditAccounts.lockForUpdate(userId);

    if (!account || account.balance < credits) {
      throw new Error('Insufficient credits');
    }

    await tx.creditTransactions.insert({
      user_id: userId,
      amount: -credits,
      type: 'purchase',
      reference_id: purchaseId
    });

    await tx.creditAccounts.increment(userId, -credits);
  });
}

扣减操作不能只在浏览器或移动端执行,否则用户可能篡改请求、重复提交或绕过余额检查。

使用 Facebook 登录时的关系

Facebook Login 只负责身份认证。应用可以将 Facebook 返回的用户标识映射到内部用户 ID,积分仍保存在应用自己的数据库中:

Facebook Login
      ↓
应用内部用户 ID
      ↓
应用积分账户与交易流水

不要将访问令牌当作支付授权,也不要在客户端使用 App Secret。支付结果必须由服务端通过已验证签名的 webhook 确认。

注意事项

  • 不要尝试创建、转移或修改所谓的 Facebook Credits,该产品已经停止。
  • 不要根据客户端显示的“支付成功”页面直接发放积分。
  • 金额、币种、订单号和商品内容都应由服务端校验。
  • 支付 webhook 必须验证签名并实现幂等处理。
  • 发生退款或拒付后,应记录反向积分流水,不要直接删除原交易。
  • 如果积分可以兑换现金、转让给其他用户或用于购买现实商品,可能涉及支付、税务、反洗钱或储值监管要求。
  • 在移动应用内销售数字内容时,还需要检查 Apple App Store 和 Google Play 的应用内购买规则。
  • Meta 各产品的支付能力和审核政策可能发生变化。如果应用必须在某个 Meta 产品内完成交易,应以该产品当前的官方开发文档和审核后台实际开放的能力为准。

新应用无需继续寻找 Facebook Credits API。可行的架构是由 Facebook Login 负责登录,支付服务商负责收款,应用自己的服务端负责管理订单、积分账户和交易流水。

备注:内容仅供参考。