PAYATHON 2026

如何将 Google reCaptcha 集成到现有支付表单

支付老李

结论

问题出在服务端验证代码:

  • 代码定义了 $secret_key,发送请求时却使用了未定义的 $secret。
  • http_get() 不是 PHP 的通用内置函数,只有安装了相关扩展或项目自行实现后才能使用。
  • $info 通常保存的是请求信息,不是 Google 返回的 JSON 正文。
  • 请求没有设置超时。如果服务器无法访问 Google,页面可能长时间没有响应。
  • 代码没有处理网络错误,也没有检查 HTTP 状态码和 JSON 解析结果。

可以改用 cURL,通过 POST 请求调用 siteverify。服务端确认 success 为真后,再进入支付流程。

前端表单

reCAPTCHA 元素需要放在实际提交的 <form> 中:

<script src="https://www.google.com/recaptcha/api.js" async defer></script>

<form method="post" action="/payment.php">
    <!-- 支付表单字段 -->

    <div
        class="g-recaptcha"
        data-sitekey="YOUR_SITE_KEY">
    </div>

    <button type="submit">提交支付</button>
</form>

site key 可以出现在浏览器端,secret key 则只能保存在服务端,不能写入 HTML 或 JavaScript。

服务端验证

以下代码以 PHP 和 reCAPTCHA v2 checkbox 为例:

<?php

session_start();

$captchaToken = $_POST['g-recaptcha-response'] ?? '';

if ($captchaToken === '') {
    $_SESSION['form_error'] = '请先完成人机验证。';
    header('Location: /payment-form.php');
    exit;
}

$secretKey = getenv('RECAPTCHA_SECRET_KEY');

if (!$secretKey) {
    http_response_code(500);
    exit('服务器未配置 reCAPTCHA。');
}

$postData = http_build_query([
    'secret'   => $secretKey,
    'response' => $captchaToken,
    'remoteip' => $_SERVER['REMOTE_ADDR'] ?? '',
]);

$curl = curl_init('https://www.google.com/recaptcha/api/siteverify');

curl_setopt_array($curl, [
    CURLOPT_POST            => true,
    CURLOPT_POSTFIELDS      => $postData,
    CURLOPT_RETURNTRANSFER  => true,
    CURLOPT_CONNECTTIMEOUT  => 5,
    CURLOPT_TIMEOUT         => 10,
    CURLOPT_HTTPHEADER      => [
        'Content-Type: application/x-www-form-urlencoded',
    ],
]);

$responseBody = curl_exec($curl);

if ($responseBody === false) {
    $curlError = curl_error($curl);
    curl_close($curl);

    error_log('reCAPTCHA request failed: ' . $curlError);
    http_response_code(503);
    exit('暂时无法完成人机验证,请稍后重试。');
}

$httpStatus = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);

if ($httpStatus !== 200) {
    error_log('reCAPTCHA returned HTTP status: ' . $httpStatus);
    http_response_code(503);
    exit('暂时无法完成人机验证,请稍后重试。');
}

$result = json_decode($responseBody, true);

if (!is_array($result) || !array_key_exists('success', $result)) {
    error_log('Invalid reCAPTCHA response: ' . $responseBody);
    http_response_code(502);
    exit('人机验证服务返回了无效响应。');
}

if ($result['success'] !== true) {
    $errorCodes = $result['error-codes'] ?? [];
    error_log('reCAPTCHA rejected: ' . implode(', ', $errorCodes));

    $_SESSION['form_error'] = '人机验证失败,请重新验证后再提交。';
    header('Location: /payment-form.php');
    exit;
}

/*
 * reCAPTCHA 验证通过。
 * 只有从这里开始,才执行订单创建和支付操作。
 */
processPayment();

如果表单页面通过其他方式显示错误,也可以返回 JSON,不必重定向:

if ($result['success'] !== true) {
    http_response_code(422);

    header('Content-Type: application/json; charset=utf-8');
    echo json_encode([
        'success' => false,
        'message' => '人机验证失败,请重试。',
    ], JSON_UNESCAPED_UNICODE);

    exit;
}

原代码中的具体错误

下面两行使用的变量名不一致:

$secret_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
array("secret" => $secret, "response" => $captcha)

应统一使用 $secret_key:

array(
    'secret'   => $secret_key,
    'response' => $captcha,
)

修正变量名后,仍不建议继续使用来源不明的 http_get()。如果该函数来自旧版 HTTP 扩展,还要确认扩展是否已经安装、参数格式是否正确,以及它返回的是响应正文还是请求状态信息。

生产环境应开启错误日志。页面没有显示错误,不代表程序没有报错,也可能是因为关闭了 display_errors。包含密钥或支付数据的错误信息不能直接展示给用户。

支付表单的额外注意事项

reCAPTCHA 只能减少自动化垃圾提交,不能代替支付安全措施。支付流程还需要注意:

  • 验证必须在服务端完成,不能只依靠 JavaScript 的判断结果。
  • 只有验证成功后才能创建支付请求,以免无效提交产生订单或扣款。
  • 支付接口应使用幂等键,防止重复提交造成重复扣款。
  • reCAPTCHA token 有效期短且只能使用一次,不应缓存后重复使用。
  • 可以检查返回结果中的 hostname,确认它属于自己的站点。
  • 使用 reCAPTCHA v3 时,还要校验 score 和 action,不能只检查 success。
  • 提交接口还需要频率限制、CSRF 防护、蜜罐字段和服务端字段校验。
  • 如果服务器所在网络无法稳定访问 Google,验证请求会失败。需要设置连接超时和总超时,并根据目标用户与部署地区评估其他验证码服务。

验证码失败或验证服务不可用时,支付操作应默认停止,不能绕过验证继续执行。

备注:内容仅供参考。