PAYATHON 2026

获取对账单成功但访问 bill_download_url 提示 error

支付老李

结论

接口已经调用成功。浏览器提示 error,通常是因为下载地址输出到页面时被 HTML 解析,URL 中的:

&timestamp=

被错误显示为:

×tamp=

浏览器把其中的 &times 识别成了 HTML 实体 ×,导致下载参数损坏。示例响应里已经能看到这个问题:

...20210409.csv.zip×tamp=1618215335&token=...

正确的地址应该是:

...20210409.csv.zip&timestamp=1618215335&token=...

不要从浏览器渲染后的 print_r() 结果中复制链接。应直接读取接口返回的原始 bill_download_url 并下载文件。

原因说明

bill_download_url 是一个带查询参数的临时下载地址,各参数之间使用 & 分隔。URL 直接输出到 HTML 页面后,浏览器可能会把 &timestamp 开头的 &times 当作 HTML 实体,于是:

&timestamp=1618215335

会显示成:

×tamp=1618215335

支付宝账单下载服务收到的参数不完整,便可能返回 error。

账单下载地址通常还有时效限制。即使 URL 格式正确,获取后长时间没有下载,也可能因为地址过期而失败。具体有效期应以当前支付宝接口文档和实际响应为准。链接过期后,需要重新调用接口获取。

正确下载方式

1. 不要复制页面上显示的 URL

直接从 SDK 返回对象中读取字段:

$downloadUrl =
    $response
        ->alipay_data_dataservice_bill_downloadurl_query_response
        ->bill_download_url;

读取后立即通过程序下载,不要先用 print_r() 输出,再从浏览器页面复制。

2. 使用 cURL 下载账单

<?php

$result = $response->alipay_data_dataservice_bill_downloadurl_query_response;

if ((string) $result->code !== '10000') {
    throw new RuntimeException(
        sprintf(
            '获取账单下载地址失败:code=%s, msg=%s',
            $result->code ?? '',
            $result->msg ?? ''
        )
    );
}

$downloadUrl = (string) $result->bill_download_url;
$savePath = __DIR__ . '/alipay_bill.csv.zip';

$fileHandle = fopen($savePath, 'wb');

if ($fileHandle === false) {
    throw new RuntimeException('无法创建账单文件');
}

$curl = curl_init($downloadUrl);

curl_setopt_array($curl, [
    CURLOPT_FILE            => $fileHandle,
    CURLOPT_FOLLOWLOCATION  => true,
    CURLOPT_CONNECTTIMEOUT  => 10,
    CURLOPT_TIMEOUT         => 120,
    CURLOPT_FAILONERROR     => false,
]);

$success = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$error = curl_error($curl);

curl_close($curl);
fclose($fileHandle);

if ($success === false || $httpCode < 200 || $httpCode >= 300) {
    @unlink($savePath);

    throw new RuntimeException(
        sprintf(
            '下载账单失败:HTTP %d,cURL error=%s',
            $httpCode,
            $error
        )
    );
}

echo '账单已保存至:' . $savePath;

保存文件时要使用 wb,以二进制方式写入,否则 ZIP 文件在部分运行环境中可能损坏。

3. 如果必须在页面中展示链接

输出到 HTML 时需要转义,避免浏览器把查询参数解析成实体:

echo '<a href="'
    . htmlspecialchars($downloadUrl, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')
    . '">下载支付宝对账单</a>';

如果只是排查原始值,可以改为输出纯文本:

header('Content-Type: text/plain; charset=UTF-8');
var_dump($downloadUrl);

也可以查看页面源代码,不要只看浏览器渲染后的文字。

临时修正示例

如果已经确认问题只是复制 URL 时将 &timestamp 变成了 ×tamp,可以临时修正:

$downloadUrl = str_replace('×tamp=', '&timestamp=', $downloadUrl);

这种方式只适合排查问题,不能代替对接口响应的正确处理。更可靠的方式仍是直接读取 SDK 返回的原始字段,因为 URL 中可能还有其他必须原样保留的编码内容。

注意事项

  • code = 10000 只表示成功获取了下载地址,不代表经过任意处理或复制后的 URL 都能正常下载。
  • 不要对整个下载地址直接执行 urlencode(),否则 ://、?、&、= 等结构也会被编码,地址将无法使用。
  • 不要手动拼接或解码 token、fileId 等参数,应完整使用接口返回值。
  • 获取 URL 后应尽快下载。如果提示地址失效,请重新调用 alipay.data.dataservice.bill.downloadurl.query 获取新地址。
  • 如果程序中的原始字符串已经是 ×tamp=,需要检查中间环节是否经过富文本编辑器、HTML 解析、实体解码或人工复制。
  • 下载失败时,应同时记录 HTTP 状态码和响应正文。如果 URL 参数正确、地址刚刚生成却仍然失败,再检查账单日期、账单类型、应用权限,以及支付宝账号中是否存在对应账单。

备注:内容仅供参考。