如何将 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,验证请求会失败。需要设置连接超时和总超时,并根据目标用户与部署地区评估其他验证码服务。
验证码失败或验证服务不可用时,支付操作应默认停止,不能绕过验证继续执行。
备注:内容仅供参考。
