扫码支付后,支付宝账单标题乱码且无法显示收付方名称
结论
账单标题乱码通常不是支付宝客户端造成的,而是商户创建扫码支付订单时,对 subject、body 等文本字段使用了错误的字符编码,或进行了重复编码。构造请求时应统一使用 UTF-8,并确保参与签名的内容与实际发送的内容完全一致。
如果账单标题显示正常,却没有显示真实的收款方或付款方名称,问题还可能与商户账户资料、接口传参规则或支付宝的隐私展示策略有关。修改账单标题并不能强制显示交易对手的真实名称。
常见原因
1. 请求字符集不一致
接口虽然声明了 charset=utf-8,程序却使用 GBK、GB2312 或系统默认编码处理 subject,支付宝收到的中文便可能变成乱码。
这类问题常见于以下情况:
- Java 调用
String.getBytes(),依赖操作系统的默认字符集; - PHP 文件没有使用 UTF-8 编码;
- 从数据库读取数据时发生了转码;
- 网关、反向代理或 HTTP 客户端修改了请求编码;
- 接口声明的
charset与签名前实际使用的编码不同。
2. 中文被重复 URL 编码
例如,原始标题是:
测试商品
正常进行一次 URL 编码后可能是:
%E6%B5%8B%E8%AF%95%E5%95%86%E5%93%81
如果业务代码先手动执行 URLEncoder.encode(),SDK 或 HTTP 客户端随后又编码一次,支付宝可能会把编码字符串本身当作标题。因此,账单中会出现 %E6...、%25E6... 或其他异常内容。
3. 签名与发送时使用了不同的文本
有些程序使用原始中文生成签名,之后又对参数转码。也有程序先编码再签名,但 SDK 发送请求时再次编码。即使接口没有直接返回签名错误,字段内容也可能出现异常。
需要遵守的原则是:
参与签名的业务内容 = 实际提交的业务内容
具体的签名过程应交给对应语言的支付宝 SDK。不要先自行拼接或转换编码,再把处理后的内容交给 SDK。
4. 订单标题字段填写不正确
扫码支付接口通常使用 subject 表示订单标题。如果该字段传入的是商户内部编码、转义后的 JSON、Base64 文本或带有不可见字符的内容,账单自然无法显示预期名称。
subject 只是订单标题,并不代表收款方或付款方的实名信息。
5. 名称受账户资料或隐私规则影响
如果标题没有乱码,只是没有显示完整的收付款方名称,可能存在以下原因:
- 收款账户的商户名称或签约资料配置不正确;
- 当前交易产品只显示商户简称或订单标题;
- 个人账户名称经过了脱敏处理;
- 支付宝客户端不同页面采用不同的展示规则;
- 支付宝会根据交易类型、账户类型和风控策略生成账单展示信息。
账单最终显示哪些字段由支付宝决定。传入 subject 并不意味着账单一定会显示真实的交易对手名称。
解决步骤
第一步:检查创建订单时的原始参数
调用接口前,记录以下内容:
subject的原始值;body的原始值;charset;- 请求的
Content-Type; - 使用的 SDK 和接口名称;
- 数据进入支付模块前是否已经进行了 URL 编码。
日志中不要记录付款码、密钥、完整用户标识等敏感信息。
可以先用一个简单标题测试:
扫码支付测试订单
如果简单中文仍然乱码,问题很可能出在字符集或请求构造过程中。
第二步:统一使用 UTF-8
Java 代码不要依赖默认字符集:
import java.nio.charset.StandardCharsets;
String subject = "扫码支付测试订单";
byte[] bytes = subject.getBytes(StandardCharsets.UTF_8);
String normalizedSubject = new String(bytes, StandardCharsets.UTF_8);
使用官方 SDK 时,一般可以直接传入 Java String,不需要先转换为字节,也不需要执行 URL 编码:
AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
model.setOutTradeNo("ORDER_202609090001");
model.setTotalAmount("0.01");
model.setSubject("扫码支付测试订单");
request.setBizModel(model);
接口类和模型类应以项目实际使用的支付宝 SDK 为准。
PHP 源文件、配置文件和数据库连接也应统一使用 UTF-8:
$bizContent = [
'out_trade_no' => 'ORDER_202609090001',
'total_amount' => '0.01',
'subject' => '扫码支付测试订单',
];
$request->setBizContent(
json_encode($bizContent, JSON_UNESCAPED_UNICODE)
);
JSON_UNESCAPED_UNICODE 可以让日志和调试信息更易读,但 \uXXXX 本身是合法的 JSON 表示形式,不一定会导致乱码。真正需要检查的是,数据在读取、序列化和发送过程中是否始终使用 UTF-8。
第三步:删除不必要的手动编码
使用 SDK 时,通常不要这样处理:
String subject = URLEncoder.encode(
"扫码支付测试订单",
StandardCharsets.UTF_8
);
model.setSubject(subject);
直接传入原始文本即可:
model.setSubject("扫码支付测试订单");
如果没有使用 SDK,需要自行构造表单请求,只在 HTTP 表单序列化阶段编码一次:
String formValue = URLEncoder.encode(
"扫码支付测试订单",
StandardCharsets.UTF_8
);
不要再次对已经编码的 formValue 执行 URLEncoder.encode()。
第四步:检查数据库与中间服务
如果订单标题来自数据库,可以先检查:
SHOW VARIABLES LIKE 'character_set%';
SHOW VARIABLES LIKE 'collation%';
数据库、表、字段和连接所用的字符集应保持一致。MySQL 项目通常建议使用 utf8mb4。JDBC 连接也需要确认启用了 Unicode:
jdbc:mysql://127.0.0.1:3306/payment?useUnicode=true&characterEncoding=UTF-8
消息队列、缓存、网关和其他中间服务也需要逐一检查,确认它们没有把 UTF-8 字节按其他字符集重新解释。
第五步:创建新订单验证
修复编码问题后,需要创建一笔新订单进行验证。历史账单通常保留交易创建时提交的信息。即使程序或商户资料已经修改,已生成的账单标题也不一定会自动更新。
建议分别测试以下标题来源和内容:
- 纯中文标题;
- 中英文混合标题;
- 带有括号、连字符等常用符号的标题;
- 不经过数据库、直接写入代码的标题;
- 从数据库读取的标题。
这些测试可以帮助判断乱码发生在业务数据源、支付程序还是请求传输阶段。
如何判断问题属于哪一类
账单显示 %E6%B5%8B...,通常说明 URL 编码后的内容被当作普通文本提交了。
显示类似 æµè¯ 的内容,通常说明 UTF-8 字节被当作 ISO-8859-1 或其他字符集解释。
如果显示 ???,中文可能在进入支付接口前就被不支持中文的字符集替换了,原始信息往往已经丢失。
如果订单标题中的中文正常,只是收付款方名称被隐藏、缩写或脱敏,应重点检查商户签约资料和支付宝的展示规则,不必继续修改字符编码。
注意事项
subject应填写真实、简洁的订单标题,不要包含付款码、用户隐私或内部敏感信息。- 不要使用
subject冒充收款方名称,账单中的交易对手信息并不完全由该字段控制。 - 优先使用支付宝官方 SDK 处理参数序列化和签名。
- 字符编码必须在签名前调整完成。
- 排查时可以记录标题及其 UTF-8 十六进制值,但不要输出应用私钥、支付宝公钥、付款码或完整请求签名。
- 如果请求参数确认无误,新交易仍然持续出现乱码,应整理接口名称、请求时间、支付宝交易号、脱敏后的请求参数和问题截图,再通过支付宝开放平台的技术支持渠道核查具体的账单展示规则。
备注:内容仅供参考。
