如何使用 curl 自动登录 WebMoney 并完成支付
结论
curl 能跟随普通的 HTTP 重定向,但无法可靠地替代浏览器登录 WebMoney 并自动确认付款。WebMoney 的登录和支付通常包含动态表单、CSRF 防护、JavaScript、短信或二次验证,还需要用户确认。复制浏览器 Cookie 或模拟支付页面提交既不稳定,也可能触发风控、泄露账户凭据,甚至违反服务规则。
建议按以下方式处理:
- 使用
CURLOPT_FOLLOWLOCATION跟踪支付入口的普通重定向。 - 使用 Cookie 文件维持同一个 HTTP 会话。
- 不要通过抓取网页来模拟登录和点击付款。
- 如果需要自动付款,应使用 WebMoney 为相应账户提供并授权的官方接口,由服务端完成签名、认证和支付。
- 通过支付网关的回调接口确认结果,不要把最终跳转页面当作支付成功的依据。
为什么会看到 “Object Moved to HERE”
“Object Moved” 通常不是支付错误,而是服务器返回的重定向响应。响应一般包含类似内容:
HTTP/1.1 302 Found
Location: https://example.com/next-step
页面中的 HERE 是为不会自动跳转的客户端准备的链接。设置以下选项后,cURL 通常会根据 Location 请求下一个地址:
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_MAXREDIRS, 10);
如果请求仍停留在该页面,可能是以下原因:
- 跳转地址没有放在 HTTP
Location中,而是由 JavaScript 或 HTML 表单触发; - 后续请求没有携带上一步设置的 Cookie;
- 跨域跳转后,认证信息没有继续发送;
- 服务端要求提交隐藏字段、CSRF token 或一次性参数;
- 登录或支付流程要求浏览器执行 JavaScript;
- PHP 环境限制了重定向,或者重定向链出现循环。
可以先检查响应状态和最终 URL:
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$finalUrl = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
var_dump($statusCode, $finalUrl);
不要只检查响应正文中是否出现 “Object Moved”。实际的跳转地址位于响应头的 Location 字段中。
修正当前 cURL 请求
原代码有几个问题:
$content = curl_setopt(...)返回的是布尔值,不是响应内容;CURLOPT_COOKIEJAR只负责指定保存 Cookie 的文件,还需要通过CURLOPT_COOKIEFILE从该文件加载 Cookie;- 代码虽然定义了
$cookie,却没有通过CURLOPT_COOKIE使用; CURLOPT_HEADER => true会把响应头和正文混在一起;- 应明确设置
CURLOPT_POST; - 必须检查
curl_exec()的结果和 HTTP 状态码; - Cookie 文件应使用可写的绝对路径。
下面的代码可用于提交支付入口参数并跟踪普通重定向,但不能绕过登录或付款确认:
<?php
$url = 'https://merchant.web.money/lmi/payment.asp?lang=en-US';
$cookieFile = __DIR__ . '/webmoney-cookie.txt';
$data = [
'LMI_PAYEE_PURSE' => 'merchant purse address',
'LMI_SHOP_ID' => 'shop id',
'LMI_PAYMENT_AMOUNT' => '100',
'LMI_PAYMENT_NO' => '250515555',
'LMI_PAYMENT_DESC_BASE64'=> base64_encode('description of good'),
'LMI_RESULT_URL' => 'https://example.com/payment/result',
'LMI_FAIL_URL' => 'https://example.com/payment/fail',
'LMI_FAIL_METHOD' => 'POST',
'LMI_SUCCESS_URL' => 'https://example.com/payment/success',
'LMI_SUCCESS_METHOD' => 'LINK',
'LMI_FAST_PHONENUMBER' => '',
'LMI_PAYMENTFORM_SIGN' => 'signature-generated-for-this-payment',
];
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($data),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 10,
CURLOPT_COOKIEJAR => $cookieFile,
CURLOPT_COOKIEFILE => $cookieFile,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_USERAGENT => 'MyPaymentClient/1.0',
]);
$response = curl_exec($ch);
if ($response === false) {
$error = curl_error($ch);
$errno = curl_errno($ch);
curl_close($ch);
throw new RuntimeException(
sprintf('cURL failed (%d): %s', $errno, $error)
);
}
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$finalUrl = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
curl_close($ch);
printf("HTTP status: %d\n", $statusCode);
printf("Final URL: %s\n", $finalUrl);
printf("Content-Type: %s\n", $contentType ?? 'unknown');
// 仅用于诊断,生产环境不要直接输出可能包含敏感信息的页面。
// echo $response;
CURLOPT_FOLLOWLOCATION 只能处理 HTTP 3xx 跳转。如果页面使用以下结构,它不会自动继续:
<script>
location.href = "/next";
</script>
或者:
<form method="post" action="/next">
<input type="hidden" name="token" value="...">
</form>
虽然可以解析页面并继续提交,但这种方式用于登录和付款页面时很不稳定,不应作为正式的支付方案。
登录认证不能依赖复制 Cookie
代码中的 $cookie 看起来是从浏览器复制的登录状态。这种做法有不少问题:
- Cookie 可能包含账户会话凭据,一旦泄露,账户可能被接管;
- 会话可能与 IP、浏览器指纹、设备或短期 token 绑定;
- 登录状态可能随时过期或被服务端撤销;
- 支付页面可能要求 CSRF token 和一次性确认参数;
- 复用 Cookie 无法合法替代二次验证;
- 服务端可能将这种行为识别为异常登录。
CURLOPT_COOKIEJAR 不会自动导入浏览器中的 Cookie。它只保存当前 cURL 会话从服务器收到的 Cookie。如果要在多次 cURL 请求之间复用 Cookie,所有请求都应使用同一个文件:
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookieFile);
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookieFile);
Cookie 文件必须放在 Web 目录之外,并限制访问权限。不要把 Cookie、密码、私钥、验证码或签名密钥写入源码、日志或错误页面。
自动付款应使用官方接口
如果业务确实需要无人值守付款,应先确认 WebMoney 是否为当前账户类型和业务场景提供正式的付款接口。接口名称、认证方式和签名规则可能发生调整,具体实现应以当前官方文档及账户后台实际开放的能力为准。
典型的服务端支付流程如下:
- 在业务系统中创建唯一订单号。
- 通过官方接口提交收款方、金额、币种和订单信息。
- 按照官方要求,使用证书、密钥或签名算法认证请求。
- 如果接口要求账户持有人确认,则跳转到官方页面完成确认。
- 接收支付结果回调。
- 验证回调签名、金额、币种、收款账户和订单号。
- 必要时主动调用订单查询接口进行二次确认。
- 使用幂等逻辑更新本地订单状态。
如果官方接口仍要求短信、E-NUM、设备确认或其他交互步骤,就不能安全地将其改造成完全无人值守的脚本,人工授权环节仍需保留。
正确处理支付结果
LMI_SUCCESS_URL 只负责将浏览器带回网站。用户进入成功页面,并不代表付款已经真实入账。该地址可以被直接访问,也可能在回调尚未送达时提前打开。
订单状态应由服务端通知或官方订单查询结果决定。处理回调时,至少要检查以下内容:
- 发票号或订单号是否存在;
- 订单是否已经处理,避免重复入账;
- 收款钱包是否符合预期;
- 支付金额和币种是否完全一致;
- 通知签名是否有效;
- 支付状态是否明确为成功;
- 通知是否使用预期协议,并经过官方规定的验证流程。
示意代码如下。签名字段和算法必须替换为当前官方规范,不能自行猜测:
<?php
$orderNo = $_POST['LMI_PAYMENT_NO'] ?? '';
$amount = $_POST['LMI_PAYMENT_AMOUNT'] ?? '';
$purse = $_POST['LMI_PAYEE_PURSE'] ?? '';
$receivedSignature = $_POST['LMI_HASH'] ?? '';
$order = findOrderByNumber($orderNo);
if ($order === null) {
http_response_code(404);
exit('Unknown order');
}
if (!hash_equals($order->expectedAmount, $amount)) {
http_response_code(400);
exit('Amount mismatch');
}
if (!hash_equals($order->expectedPurse, $purse)) {
http_response_code(400);
exit('Purse mismatch');
}
$expectedSignature = buildSignatureAccordingToOfficialSpecification(
$_POST,
getPaymentSecret()
);
if (!hash_equals($expectedSignature, $receivedSignature)) {
http_response_code(403);
exit('Invalid signature');
}
markOrderPaidIdempotently($orderNo);
http_response_code(200);
echo 'OK';
buildSignatureAccordingToOfficialSpecification() 只是占位函数。字段顺序、字符编码、哈希算法、密钥位置,以及是否包含测试模式字段,都必须严格按照当前接口文档实现。
还需要修正的安全问题
不要在客户端提交或信任可以随意修改的金额、收款钱包和回调地址。这些字段应由服务端根据订单生成,并纳入完整签名。签名也不能长期硬编码:
'LMI_PAYMENTFORM_SIGN' => 'FFA58D...'
如果该值是真实签名,它通常只对应一组确定的订单参数。修改金额、订单号或描述后,应按接口规范重新生成签名。如果签名密钥已经出现在公开代码、日志或问题描述中,应立即轮换。
还应注意以下事项:
- 全程验证 TLS 证书,不要设置
CURLOPT_SSL_VERIFYPEER => false; - 每笔支付都使用唯一订单号;
- 设置连接超时和总请求超时;
- 限制重定向次数;
- 不要将完整支付页面、Cookie 或回调参数写入公开日志;
- 测试时使用官方测试环境或小额专用账户;
- 不要自动处理页面中的任意跳转地址,以免引入 SSRF 或凭据泄露风险。
cURL 适合请求支付入口、维护普通会话和接收接口响应,但不适合模拟个人用户登录后自动点击确认付款。正确配置并检查响应信息,可以解决普通的重定向问题。真正的自动支付则必须使用 WebMoney 当前正式支持的 API、认证方式和账户权限。
备注:内容仅供参考。
