PAYATHON 2026

如何通过 FastSpring webhook 发送 siteURL 或 userID?

支付小周

结论

可以。在调用 fastspring.builder.checkout() 前,使用 Store Builder 的 fastspring.builder.tag() 将站点标识、用户标识等自定义数据附加到订单。订单完成后,这些 tags 会随相关的 FastSpring webhook 事件返回。

Webhook 是 FastSpring 服务器单独发起的请求。它不知道购买者来自哪个 WordPress 站点,也没有对应站点的登录会话,所以无法在 webhook handler 中通过 get_current_user_id() 获取购买时的用户。这些信息需要在创建结账会话时传入,或者通过内部订单记录关联。

在 WordPress 页面中传入数据

先用 PHP 获取当前站点和用户信息,再安全地输出给 JavaScript:

<?php
$checkout_context = [
    'siteURL' => home_url('/'),
    'userID'  => (string) get_current_user_id(),
];
?>

<script>
const checkoutContext = <?php echo wp_json_encode($checkout_context); ?>;

function dataPopupWebhookReceived(data) {
    // 这是 Store Builder 的前端回调,不是服务器端 FastSpring webhook。
    console.log('Store Builder event:', data);
}

function addVariationAndCheckout(variation) {
    fastspring.builder.reset();

    switch (variation) {
        case 'pcm-monthly':
            fastspring.builder.add('pcm-monthly');
            break;

        default:
            return;
    }

    fastspring.builder.tag({
        siteURL: checkoutContext.siteURL,
        userID: checkoutContext.userID
    });

    fastspring.builder.checkout();
}
</script>

调用顺序必须是:

fastspring.builder.reset();
fastspring.builder.add('pcm-monthly');
fastspring.builder.tag({ /* custom data */ });
fastspring.builder.checkout();

reset() 会清除当前 Store Builder 会话中的数据,因此 tags 不能在 reset() 之前设置。

在 webhook handler 中读取

FastSpring 发送的 JSON 结构取决于 webhook 事件类型和当前接口格式。对于包含订单 tags 的事件,通常可以从事件或订单对象的 tags 字段读取,例如:

<?php

function handle_fastspring_webhook(WP_REST_Request $request) {
    $payload = $request->get_json_params();

    // 字段路径应以 FastSpring 实际发送的 webhook payload 为准。
    $tags = $payload['tags'] ?? [];

    $site_url = isset($tags['siteURL'])
        ? esc_url_raw($tags['siteURL'])
        : null;

    $user_id = isset($tags['userID'])
        ? absint($tags['userID'])
        : 0;

    // 根据订单号、站点标识和用户标识处理授权。
    // 不要仅凭这些 tags 判定付款成功。

    return new WP_REST_Response(['received' => true], 200);
}

如果实际 payload 中的 tags 位于嵌套对象中,就需要按照真实结构调整,例如:

$tags = $payload['order']['tags'] ?? [];

可以先记录一份经过敏感信息脱敏的测试 webhook payload,确认所订阅事件中 tags 的确切位置。

更稳妥的关联方式

直接传递 siteURL 和 WordPress userID 很方便,但不能将其作为可信的授权依据,因为浏览器端 JavaScript 可以被修改。更可靠的方式是生成一个不可预测的关联令牌:

<?php
$checkout_token = wp_generate_uuid4();

update_option(
    'pcm_checkout_' . $checkout_token,
    [
        'site_url' => home_url('/'),
        'user_id'  => get_current_user_id(),
        'created'  => time(),
    ],
    false
);
?>

<script>
const checkoutToken = <?php echo wp_json_encode($checkout_token); ?>;

fastspring.builder.tag({
    checkoutToken: checkoutToken
});
</script>

Webhook 收到 checkoutToken 后,再从自己的数据库中查询对应的站点和用户,不要完全信任浏览器提交的 siteURL 或 userID。

注意事项

  • dataPopupWebhookReceived() 是浏览器中的 Store Builder 回调,并不是发送到 WordPress REST API 的服务器端 webhook。
  • webhook 请求不包含 WordPress 登录 Cookie,因此其中的 get_current_user_id() 通常返回 0。
  • 必须校验 FastSpring webhook 的签名或认证信息,并按照当前 FastSpring 配置使用相应的验证方式。
  • 验证事件真实性、付款状态、产品 ID、订单号和金额后,才能发放授权。
  • 不要将密码、访问令牌、私钥或其他敏感信息放入 tags。
  • userID 只在单个 WordPress 安装中有意义。接入多站点或多个客户站点时,需要同时保存站点标识。最好使用后台生成的 installation ID,不要将可能变化的域名作为唯一主键。
  • webhook 可能重复发送。handler 应使用 FastSpring 事件 ID 或订单 ID 做幂等处理,避免重复创建许可证或重复开通服务。

备注:内容仅供参考。