鸿蒙使用java生成的orderInfo发起支付宝支付返回6001
结论
支付宝返回 6001,标准含义是“用户中途取消支付”。但如果支付页面根本没有正常打开,调用后就立即返回 6001,不能直接判定是用户主动取消。此时应检查:
- 鸿蒙端是否原样传递了
orderInfo orderInfo是否经过 URL 解码、重复编码,或在拼接时被破坏- 服务端签名密钥、
app_id与支付宝应用配置是否匹配 - 鸿蒙端使用的支付宝支付 SDK 是否支持当前系统和设备环境
- 支付过程中是否发生页面拉起失败、应用切换失败或生命周期异常
从题目给出的字符串来看,其格式符合 Java SDK 生成的 App 支付请求结构。不过,仅凭字符串格式无法确认签名是否正确。排查时先确认支付宝收银台有没有出现,再查看完整的支付结果对象和服务端签名过程。
先确认 6001 出现在哪个阶段
已经进入支付宝收银台
如果支付宝收银台已经正常打开,之后用户点击返回、关闭页面或取消付款,返回 6001 是正常结果。
客户端应将它作为取消状态处理,而不是系统异常:
if (result.resultStatus === '6001') {
// 用户取消支付
}
支付结果的字段名称应以项目当前接入的鸿蒙支付宝 SDK 为准。
没有进入支付宝收银台
如果调用支付接口后立即返回 6001,或者页面一闪而过,需要继续排查参数传递、SDK 集成和应用切换过程。此时,6001 可能只是客户端最终收到的取消状态,具体原因一般需要从支付结果中的 memo、resultMemo 或 SDK 日志中查找。
建议记录以下信息:
console.info(`resultStatus=${result.resultStatus}`);
console.info(`memo=${result.memo ?? result.resultMemo ?? ''}`);
console.info(`result=${result.result ?? ''}`);
不要只打印 resultStatus,否则可能遗漏有用的错误信息。
鸿蒙端必须原样传递 orderInfo
服务端生成的 orderInfo 已经是最终的支付请求字符串,其中 biz_content、sign、timestamp 等字段已经完成编码。鸿蒙端收到后应直接传给支付宝 SDK,不要再做任何处理。
以下操作都可能破坏签名:
// 错误:对整个 orderInfo 再编码
const value = encodeURIComponent(orderInfo);
// 错误:提前解码
const value = decodeURIComponent(orderInfo);
// 错误:按 & 拆分后重新排序或拼接
const value = rebuildOrderInfo(orderInfo);
// 错误:把 + 自动转换为空格
const value = orderInfo.replace(/\+/g, ' ');
正确的处理方式是:
const orderInfo: string = response.data.orderInfo;
// 将 orderInfo 原样传给当前支付宝鸿蒙 SDK 的支付方法
payWithAlipay(orderInfo);
这里的 payWithAlipay 只表示支付调用的位置。实际 API 名称、参数和返回结构,应以项目所用的支付宝鸿蒙 SDK 文档为准。
还要特别注意示例中的这些内容:
sign=...%2F...%2B...%3D%3D
timestamp=2024-12-25+09%3A56%3A33
%2F、%2B、%3D 以及时间中的 +,都是最终请求字符串的一部分。任何中间层对它们进行解码或二次编码,都可能造成验签失败。
服务端应使用 sdkExecute 生成请求字符串
App 支付请求应由服务端创建,并通过 Java SDK 的 sdkExecute 生成最终 orderInfo。不要自行排序、拼接或签名。
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeAppPayModel;
import com.alipay.api.request.AlipayTradeAppPayRequest;
import com.alipay.api.response.AlipayTradeAppPayResponse;
import java.math.BigDecimal;
public class AlipayOrderService {
public String createOrderInfo() throws Exception {
String gateway = "https://openapi.alipay.com/gateway.do";
String appId = "你的支付宝应用APP_ID";
String appPrivateKey = "你的应用私钥";
String alipayPublicKey = "支付宝公钥";
AlipayClient alipayClient = new DefaultAlipayClient(
gateway,
appId,
appPrivateKey,
"json",
"UTF-8",
alipayPublicKey,
"RSA2"
);
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setOutTradeNo("唯一商户订单号");
model.setProductCode("QUICK_MSECURITY_PAY");
model.setSubject("订单标题");
model.setTotalAmount(new BigDecimal("0.01").toPlainString());
model.setTimeoutExpress("30m");
AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
request.setBizModel(model);
request.setNotifyUrl("https://example.com/alipay/notify");
AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
if (!response.isSuccess()) {
throw new IllegalStateException(
"生成支付宝订单失败,code=" + response.getCode()
+ ", msg=" + response.getMsg()
+ ", subCode=" + response.getSubCode()
+ ", subMsg=" + response.getSubMsg()
);
}
return response.getBody();
}
}
服务端直接将 response.getBody() 作为普通字符串返回:
{
"orderInfo": "alipay_sdk=...&app_id=...&biz_content=...&sign=..."
}
不要先把 response.getBody() 转成参数对象,再交给客户端重新拼接。
检查密钥和应用配置
服务端的各项配置必须对应同一个支付宝应用:
app_id是当前支付宝开放平台应用的 App ID- 签名使用该应用对应的应用私钥
- Java SDK 中配置的是支付宝公钥,不是应用公钥
- 签名算法与
sign_type=RSA2一致 - 沙箱应用、沙箱网关和沙箱账号必须配套使用
- 正式应用应使用正式网关和正式应用密钥
如果应用密钥已经重新生成,而服务端仍在使用旧私钥,签名也会无效。
可以使用支付宝开放平台提供的验签工具,验证生成订单时的原始待签名内容和签名值。验证时要分清编码后的请求字符串与编码前的待签名字符串,不能直接把整个 URL 编码结果当作待签名内容。
对比服务端和鸿蒙端收到的字符串
排查期间可以临时记录以下信息:
String orderInfo = response.getBody();
System.out.println("orderInfo.length=" + orderInfo.length());
System.out.println("orderInfo=" + orderInfo);
鸿蒙端也记录收到的字符串及其长度:
console.info(`orderInfo.length=${orderInfo.length}`);
console.info(`orderInfo=${orderInfo}`);
对比时重点查看:
- 字符串长度是否一致
- 开头或结尾是否多出引号
&是否被转换为&%2B、%2F、%3D是否发生变化+是否变成空格- 是否出现
%252F、%252B等重复编码 - 字符串中是否混入换行、制表符或首尾空格
例如,下面这些都不是原始的 orderInfo:
"alipay_sdk=...&app_id=..."
alipay_sdk=...&app_id=...
alipay_sdk=...&sign=abc%252Fdef
如果必须在生产环境中排查,不宜长期记录完整签名和订单参数。可以只记录字符串长度和哈希,用来判断传输链路中是否发生了变化:
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.HexFormat;
public static String sha256(String value) throws Exception {
byte[] digest = MessageDigest.getInstance("SHA-256")
.digest(value.getBytes(StandardCharsets.UTF_8));
return HexFormat.of().formatHex(digest);
}
只有服务端和客户端针对原始字符串计算出的 SHA-256 完全一致,才能说明传输过程中没有修改内容。
检查订单本身
还应确认以下业务条件:
- 每次创建订单时,
out_trade_no都保持唯一 total_amount使用字符串小数,例如"0.01"- 订单没有过期、关闭或已经支付
- 设备时间与服务端时间基本准确
- 不要反复使用历史
orderInfo进行测试 notify_url可以通过公网 HTTPS 访问subject不为空,且内容符合支付宝接口要求
题目中的时间是 2024-12-25 09:56:33。如果它只是脱敏后的历史示例,不影响排查;如果实际测试仍在重复使用这段旧 orderInfo,则应重新创建订单并生成新的请求字符串。
检查鸿蒙 SDK 集成和页面拉起
如果服务端与客户端的参数完全一致,但仍然无法进入收银台,应检查:
- 当前支付宝 SDK 是否明确支持项目使用的 HarmonyOS 版本
- SDK 是否初始化成功
- 应用签名、包名或 App Linking 配置是否与支付宝开放平台配置一致
- 真机是否安装了可用的支付宝客户端
- 调用支付时,页面是否仍处于有效的前台状态
- 支付调用是否被重复触发
- 从支付宝切回应用时,支付结果是否被生命周期代码覆盖
- SDK 所需的权限、依赖和混淆配置是否完整
可以增加防重复点击逻辑:
let paying = false;
async function startPay(orderInfo: string) {
if (paying) {
return;
}
paying = true;
try {
const result = await payWithAlipay(orderInfo);
console.info(`pay result=${JSON.stringify(result)}`);
} finally {
paying = false;
}
}
其中 payWithAlipay 需要替换为当前 SDK 提供的真实支付接口。
不要仅凭客户端结果确认支付成功
无论客户端返回 9000、6001 还是其他状态,最终订单状态都要由服务端确认。服务端需要:
- 验证支付宝异步通知的签名。
- 校验
app_id、商户订单号、金额和收款方信息。 - 查询支付宝订单状态,处理通知延迟或客户端结果异常。
- 确认交易成功后,再将业务订单更新为已支付。
客户端收到 6001 时,可以提示“支付已取消”。如果用户反馈已经扣款,则不能直接重新创建订单或再次支付,应先让服务端查询原订单状态。
推荐排查顺序
- 确认支付宝收银台是否真正打开。
- 输出完整返回对象,查看
memo或resultMemo。 - 对比服务端生成的值与鸿蒙端收到的值,确认
orderInfo完全一致。 - 删除客户端对
orderInfo的编码、解码、参数拆分和重新拼接逻辑。 - 使用
sdkExecute重新生成一笔全新的订单。 - 核对
app_id、应用私钥、支付宝公钥、RSA2和运行环境。 - 检查鸿蒙支付宝 SDK 的兼容性与应用拉起配置。
- 如果仍未解决,结合同一笔订单的 SDK 日志、支付结果详情和支付宝服务端错误信息继续定位。
这类问题通常不在 Java 生成的字符串格式本身,而是 orderInfo 在接口序列化、网络传输或传给鸿蒙端 SDK 之前,经历了二次编码、解码或重组。先确认服务端输出与客户端入参逐字符一致,往往就能很快缩小排查范围。
备注:内容仅供参考。
