PAYATHON 2026

WordPress 在 Chrome 88 中丢失 session

支付阿杰

结论

不要通过 URL 传递 WooCommerce session ID,也不要在回跳后手动重建登录 Cookie。更安全、可靠的做法是:

  1. 确认第三方支付页面是否通过跨站 POST 返回。
  2. 分开处理支付回调和浏览器回跳。
  3. 服务端验证支付结果后,通过 303 跳转到站内的 GET 页面。
  4. 只有业务确实需要在跨站请求中携带 Cookie 时,才考虑使用 SameSite=None; Secure,同时部署严格的 CSRF 防护和回调签名校验。

Chrome 88 前后对 SameSite Cookie 策略的执行比部分浏览器更严格。未明确指定 SameSite 的 Cookie 通常按 Lax 处理,而跨站 POST 请求一般不会携带这类 Cookie。第三方支付站点返回 WordPress 时,WooCommerce session Cookie 和登录 Cookie 因此可能暂时不可见。

先确认丢失的是 Session 还是其中一个字段

下面的检查只能说明 chosen_payment_method 不存在,不能据此判断整个登录状态或 WooCommerce session 已经丢失:

isset(WC()->session->chosen_payment_method)

应改用 WooCommerce session API,同时检查当前用户和 session Cookie:

$chosen_payment_method = WC()->session
    ? WC()->session->get('chosen_payment_method')
    : null;

error_log('Logged in: ' . (is_user_logged_in() ? 'yes' : 'no'));
error_log('Payment method: ' . var_export($chosen_payment_method, true));
error_log('Cookies: ' . wp_json_encode(array_keys($_COOKIE)));

不要在日志中记录 Cookie 的完整值,其中可能包含能够识别或接管会话的数据。

还要在 Chrome DevTools 的 Network 面板中检查返回请求:

  • 返回方式是 GET 还是 POST
  • Request Headers 中是否包含 Cookie
  • Application → Cookies 中 Cookie 的 SameSite、Secure、Domain 和 Path
  • Console 或 Issues 面板是否提示 Cookie 被阻止
  • 支付前后是否发生了 HTTP/HTTPS、www/非 www 或其他子域切换

推荐方案:跨站回调完成后再跳转到站内页面

如果第三方支付站点通过 POST 将用户送回 WordPress,回调入口不应依赖浏览器中的 WooCommerce session,而应根据订单号、一次性随机值和支付平台签名独立验证请求。

验证成功后,再使用 303 See Other 跳转到站内页面:

<?php

function handle_payment_return(): void
{
    // 示例字段名仅用于说明,实际名称以支付服务商协议为准。
    $order_id  = isset($_POST['order_id'])
        ? absint(wp_unslash($_POST['order_id']))
        : 0;

    $signature = isset($_POST['signature'])
        ? sanitize_text_field(wp_unslash($_POST['signature']))
        : '';

    $payload = $_POST;

    if (!$order_id || !verify_gateway_signature($payload, $signature)) {
        status_header(400);
        exit('Invalid payment callback');
    }

    $order = wc_get_order($order_id);

    if (!$order) {
        status_header(404);
        exit('Order not found');
    }

    // 支付状态更新必须设计为幂等操作。
    // 实际调用应根据支付服务商返回的交易状态决定。
    if (!$order->is_paid()) {
        $order->payment_complete();
    }

    $return_url = $order->get_checkout_order_received_url();

    wp_safe_redirect($return_url, 303);
    exit;
}

需要注意:

  • 第一次跨站 POST 即使不带 Cookie,也能根据签名和订单数据完成验证。
  • 303 会让浏览器使用 GET 请求访问站内结果页。
  • 顶层站内 GET 通常可以携带 SameSite=Lax Cookie。
  • 支付状态不能只依靠浏览器回跳,最好同时通过支付平台的服务端 webhook 或通知接口确认。

verify_gateway_signature() 必须按照支付服务商的官方签名规则实现,不能只比较订单号、金额或 URL 参数。

使用一次性 state,不要使用 Session ID

如果需要将浏览器回跳与某笔支付关联,可以在发起支付前生成高强度、短期有效且只能使用一次的随机 state。URL 中只传递这个随机值,不要传递 WordPress 登录 Cookie、WooCommerce session ID 或 PHP session ID。

$state = bin2hex(random_bytes(32));

// 数据库存储哈希值,避免数据库泄漏后直接暴露可用 token。
$state_hash = hash('sha256', $state);

store_checkout_state([
    'state_hash' => $state_hash,
    'order_id'   => $order_id,
    'expires_at' => time() + 10 * MINUTE_IN_SECONDS,
    'used'       => false,
]);

$return_url = add_query_arg(
    ['state' => rawurlencode($state)],
    home_url('/payment/return/')
);

回跳时进行验证:

$state = isset($_REQUEST['state'])
    ? sanitize_text_field(wp_unslash($_REQUEST['state']))
    : '';

$record = find_checkout_state(hash('sha256', $state));

if (
    !$record ||
    $record['used'] ||
    $record['expires_at'] < time()
) {
    status_header(400);
    exit('Invalid or expired state');
}

// 还应验证支付平台签名、订单号、金额和商户信息。
mark_checkout_state_as_used($record);

URL 可能被写入浏览器历史、代理日志和分析系统。因此,即使使用 state,也必须保证它短期有效、不可预测且只能使用一次。它只能关联请求,不能单独证明“用户已经登录”或“支付成功”。

何时使用 SameSite=None

如果支付流程运行在第三方 iframe 中,或者业务必须在跨站 POST 的第一次请求中读取 Cookie,可能需要使用:

SameSite=None; Secure

例如,可以这样设置自定义的关联 Cookie:

setcookie('checkout_correlation', $opaque_token, [
    'expires'  => time() + 600,
    'path'     => '/',
    'domain'   => '',
    'secure'   => true,
    'httponly' => true,
    'samesite' => 'None',
]);

这个 Cookie 不能替代 WooCommerce session Cookie。不要参照这段代码重写 WordPress 登录 Cookie,也不要假定当前 WooCommerce 版本一定提供某个 Cookie 配置钩子。相关接口会随 WordPress、WooCommerce 及支付插件的版本变化,应以实际安装版本的源码和文档为准。

SameSite=None 允许 Cookie 出现在更多跨站场景中,因此必须满足以下条件:

  • 全站使用 HTTPS
  • Cookie 同时设置 Secure
  • 状态变更请求使用 nonce、一次性 state 或同等强度的 CSRF 防护
  • 支付回调验证服务商签名
  • 校验订单号、金额、币种和商户身份
  • 回调处理具有幂等性,可以安全处理重复通知

还需要排查的配置问题

如果 Cookie 在支付前后确实从浏览器存储中消失,而不是仅在跨站请求中没有发送,还要检查:

  • WordPress Address 和 Site Address 是否完全一致
  • 是否在 http:// 和 https:// 之间切换
  • 是否在 example.com 和 www.example.com 之间切换
  • 反向代理是否正确传递 HTTPS 状态
  • CDN、缓存插件或安全插件是否改写 Set-Cookie
  • 回调页面是否被页面缓存
  • Cookie 的 Domain 和 Path 是否覆盖回跳地址
  • 服务器时间是否正确,Cookie 是否被误判为过期
  • WooCommerce、支付网关插件和主题中是否存在过时的自定义 session 逻辑

最稳妥的设计是让支付确认完全不依赖浏览器 session。服务端回调负责更新订单,浏览器回跳只展示结果。这样,即使 Chrome 因 SameSite 策略没有在第一次请求中发送 Cookie,也不会导致订单状态错误,更不需要暴露 session ID。

备注:内容仅供参考。