PAYATHON 2026

如何使用 curl 自动登录 WebMoney 并完成支付

支付阿杰

结论

curl 能跟随普通的 HTTP 重定向,但无法可靠地替代浏览器登录 WebMoney 并自动确认付款。WebMoney 的登录和支付通常包含动态表单、CSRF 防护、JavaScript、短信或二次验证,还需要用户确认。复制浏览器 Cookie 或模拟支付页面提交既不稳定,也可能触发风控、泄露账户凭据,甚至违反服务规则。

建议按以下方式处理:

  1. 使用 CURLOPT_FOLLOWLOCATION 跟踪支付入口的普通重定向。
  2. 使用 Cookie 文件维持同一个 HTTP 会话。
  3. 不要通过抓取网页来模拟登录和点击付款。
  4. 如果需要自动付款,应使用 WebMoney 为相应账户提供并授权的官方接口,由服务端完成签名、认证和支付。
  5. 通过支付网关的回调接口确认结果,不要把最终跳转页面当作支付成功的依据。

为什么会看到 “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 可能包含账户会话凭据,一旦泄露,账户可能被接管;
  • 会话可能与 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 是否为当前账户类型和业务场景提供正式的付款接口。接口名称、认证方式和签名规则可能发生调整,具体实现应以当前官方文档及账户后台实际开放的能力为准。

典型的服务端支付流程如下:

  1. 在业务系统中创建唯一订单号。
  2. 通过官方接口提交收款方、金额、币种和订单信息。
  3. 按照官方要求,使用证书、密钥或签名算法认证请求。
  4. 如果接口要求账户持有人确认,则跳转到官方页面完成确认。
  5. 接收支付结果回调。
  6. 验证回调签名、金额、币种、收款账户和订单号。
  7. 必要时主动调用订单查询接口进行二次确认。
  8. 使用幂等逻辑更新本地订单状态。

如果官方接口仍要求短信、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、认证方式和账户权限。

备注:内容仅供参考。