支付宝退款时报错 BATCH NO FORMAT ERROR 如何解决?
结论
BATCH NO FORMAT ERROR 表示支付宝无法按照当前退款接口的规则解析退款批次号。常见原因是 batch_no 的长度、字符类型或日期部分不符合要求,也可能是参数在拼接、编码或签名过程中发生了变化。
使用旧版批量退款接口时,batch_no 通常由两部分组成:
退款日期(yyyyMMdd,8 位) + 流水号(3~24 位)
例如:
20260913001
202609131234567890
可以先用 ^\d{11,32}$ 检查常见格式,具体要求仍以当前接入接口的官方参数说明为准。
常见原因
1. 批次号长度不正确
以下值通常不符合旧版批量退款接口的规则:
202609131 // 日期后只有 1 位流水号
2026091301 // 日期后只有 2 位流水号
202609131234567890123456789012345 // 总长度过长
2. 包含非数字字符
批次号中不要加入连字符、空格、下划线或中文字符:
2026-09-13-001
20260913_001
20260913 001
退款20260913001
全角数字、不可见空格和换行符等字符不易被肉眼发现,也需要检查。
3. 日期部分格式错误
日期应按 yyyyMMdd 生成,不能使用以下格式:
yyyy-MM-dd
yyyy/MM/dd
MMddyyyy
例如,2026 年 9 月 13 日应写成:
20260913
日期本身必须真实有效。批次号应采用哪个时区,日期是否必须是退款提交当天,需要根据所用接口的规则确定。
4. 批次号重复
批次号通常需要保持唯一。重试同一批退款时,应先确认原请求的状态,不要随意复用或更换批次号,以免重复提交或无法正确查询原退款结果。
格式错误和重复错误通常是两类问题,但排查时都要检查。
5. 实际发送值与程序生成值不一致
常见情况包括:
- 参数在
trim之前参与签名,发送时又被修改。 - 表单或框架自动进行了二次 URL 编码。
- 从 Excel 读取时被转换成科学计数法。
- 使用整数类型保存,导致前导零丢失。
- 字符串拼接时混入空格、制表符或换行。
- 日志记录的是编码前参数,实际发送的却是另一个值。
batch_no 应始终按字符串处理。
解决步骤
第一步:确认使用的退款接口
先核对请求中的 API 名称和对应的接口文档。
旧版批量退款接口可能包含 batch_no、batch_num、detail_data 等参数。新版单笔退款接口通常使用商户订单号、支付宝交易号和退款请求号等参数,不一定包含 batch_no。
如果当前 SDK 或接口要求使用 out_request_no,却仍按照旧接口的规则构造 batch_no,就需要根据当前接口的参数定义进行调整,不能混用两套规则。
第二步:输出批次号的原始值和长度
排查日志中至少应记录:
batch_no=[20260913001]
length=11
用方括号包住参数值,方便发现首尾空格。日志中不要输出私钥、完整签名或其他敏感信息。
第三步:校验日期、流水号和字符范围
如果使用的是旧版批量退款格式,可以按下面的方式校验:
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.ResolverStyle;
import java.util.regex.Pattern;
public final class AlipayBatchNoValidator {
private static final Pattern BATCH_NO_PATTERN =
Pattern.compile("^\\d{11,32}$");
private static final DateTimeFormatter DATE_FORMATTER =
DateTimeFormatter.ofPattern("uuuuMMdd")
.withResolverStyle(ResolverStyle.STRICT);
public static boolean isValid(String batchNo) {
if (batchNo == null || !BATCH_NO_PATTERN.matcher(batchNo).matches()) {
return false;
}
try {
LocalDate.parse(batchNo.substring(0, 8), DATE_FORMATTER);
return true;
} catch (Exception e) {
return false;
}
}
}
生成批次号时,可以将日期与商户侧的唯一流水号拼接:
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
String date = LocalDate.now()
.format(DateTimeFormatter.BASIC_ISO_DATE);
String sequence = "001234567890";
String batchNo = date + sequence;
不要只使用每天从 001 开始的内存计数器。应用重启、多实例部署或并发请求都可能导致编号重复。可以改用数据库序列、分布式 ID,或其他能保证商户侧唯一性的数字流水号,同时确保总长度符合接口限制。
第四步:检查关联参数
调用旧版批量退款接口时,还需要检查:
batch_num是否为合法整数。batch_num是否与detail_data中的退款明细数量一致。detail_data的字段分隔符和记录分隔符是否正确。- 退款金额是否采用接口要求的小数格式。
- 订单号、交易号、退款理由等字段是否包含未正确编码的特殊字符。
- 请求字符集和签名字符集是否一致。
- 参与签名的原始参数值是否与最终发送值完全一致。
以下参数仅作示意,具体字段和分隔符必须以实际接口文档为准:
batch_no=20260913001
batch_num=1
detail_data=<原交易号>^10.00^协商退款
不要直接把示意值用于生产环境,其中的交易号必须替换为真实且符合接口要求的值。
第五步:对比签名前后的请求
建议依次记录:
1. 业务代码生成的 batch_no
2. 参与签名的 batch_no
3. URL 编码前的 batch_no
4. HTTP 请求实际发送的 batch_no
正常情况下,这四处的业务值应该一致。URL 编码只会改变参数的传输形式,不应改变解码后的原始批次号。
PHP 校验示例
<?php
function isValidBatchNo(?string $batchNo): bool
{
if ($batchNo === null || !preg_match('/^\d{11,32}$/', $batchNo)) {
return false;
}
$datePart = substr($batchNo, 0, 8);
$date = DateTimeImmutable::createFromFormat('!Ymd', $datePart);
$errors = DateTimeImmutable::getLastErrors();
if ($date === false) {
return false;
}
if ($errors !== false &&
($errors['warning_count'] > 0 || $errors['error_count'] > 0)) {
return false;
}
return $date->format('Ymd') === $datePart;
}
$batchNo = '20260913001';
if (!isValidBatchNo($batchNo)) {
throw new InvalidArgumentException('Invalid Alipay batch_no format');
}
注意事项
BATCH NO FORMAT ERROR经过网关、SDK 或日志转换后,可能显示为空格或下划线不同的形式。排查时仍应重点检查退款批次号及其关联参数。- 不要只检查界面上显示的参数值,还要核对最终发往支付宝网关的原始请求。
- 不要用浮点数或普通整数保存批次号,尤其是在 JavaScript、Excel 或数据库数值字段中。
- 修正格式并重新提交前,先查询原退款请求是否已经受理,避免重复退款。
- 不同年代、不同产品使用的支付宝退款接口,参数并不完全相同。如果接口文档中没有
batch_no,应按当前 API 的退款请求号规则处理,不要套用旧版批量退款格式。
备注:内容仅供参考。
