钉钉小程序调用 dd.pay 支付提示系统繁忙
结论
问题很可能是支付产品用错了。dd.pay 需要服务端生成的支付宝 App 支付订单串,而当前参数是:
method=alipay.trade.wap.pay
product_code=QUICK_WAP_WAY
这组参数用于手机网站支付(WAP),通常不能直接传给 dd.pay 的 info。按照当前 dd.pay 接入文档,应改用 App 支付对应的参数:
method=alipay.trade.app.pay
product_code=QUICK_MSECURITY_PAY
签名也不能放在前端。这样做既会暴露支付宝应用私钥,也可能因 URL 编码、参数排序或字符串变化导致验签失败。正确流程是由服务端创建订单并完成签名,再把完整订单串返回给小程序。小程序只负责调用 dd.pay。
当前参数中的问题
1. method 和 product_code 与调用场景不匹配
当前使用的是:
method=alipay.trade.wap.pay
product_code=QUICK_WAP_WAY
这是手机网站支付参数,通常用于在浏览器中跳转到支付宝收银台。dd.pay 属于客户端原生支付能力,需要接收适用于客户端支付的订单信息。
如果 dd.pay 接收的是支付宝订单串,应使用:
method=alipay.trade.app.pay
product_code=QUICK_MSECURITY_PAY
不要只手工替换这两个字段。建议通过支付宝服务端 SDK 调用 App 支付接口,让 SDK 生成最终的 orderString。
2. notify_url 存在明显风险
描述中的字段多了一个空格:
notify_url= http://test.yylx.wang/appapi/cartpay/notify_ali
如果这个空格也参与了签名,可能导致参数不合法或验签失败。正确形式应类似:
notify_url=https://test.yylx.wang/appapi/cartpay/notify_ali
还需要注意:
- 地址前后不能有空格。
- 建议使用公网可访问的 HTTPS 地址。
- 该地址不能依赖登录状态、Cookie 或前端路由。
- 支付宝服务器必须能够直接访问该地址。
- 回调接口需要按照支付宝异步通知规则验签。
- 回调处理成功后,需要返回规定的成功响应。
notify_url必须在签名前确定,完成签名后不能修改。
当前接入场景是否强制禁用 HTTP,应以对应支付宝产品的最新配置要求为准。即使测试环境允许使用 HTTP,生产环境也应使用 HTTPS。
3. 不能先编码再随意修改订单串
支付参数通常要经过两个阶段:
- 使用原始参数生成待签名字符串。
- 对签名结果和参数值进行 URL 编码,组成最终订单串。
以下操作容易出错:
- 对整个订单串重复执行
encodeURIComponent。 - 先进行 URL 编码,再用编码后的内容计算签名。
- 完成签名后修改
biz_content、notify_url或timestamp。 - 将空格编码为
+后,又在另一层被解码成普通空格。 - 手工拼接时遗漏
&、=,或者引入换行和空格。 - JSON 序列化前后的字段内容发生变化。
- Base64 签名中的
+、/、=没有正确编码。
最稳妥的做法是让支付宝官方服务端 SDK 完成排序、签名和订单串生成,不要自行拼接。
4. biz_content 没有明显语法错误,但支付产品需要调整
业务参数可以写成:
{
"out_trade_no": "8_241107161738381",
"product_code": "QUICK_MSECURITY_PAY",
"subject": "付费课程",
"timeout_express": "30m",
"total_amount": "0.01"
}
如果 body 为空,建议直接省略,以免不同 SDK 或序列化逻辑对空值的处理方式不同。
还要确认以下几点:
out_trade_no在商户侧唯一。total_amount使用字符串形式,单位为元。subject不能为空。- 每次请求都生成当前时间的
timestamp。 - 服务器时间准确,避免时间偏差过大。
如果示例中的 2024-11-07 只是历史问题记录,不会产生影响。如果系统仍在复用这条订单串,就必须重新生成订单,不能继续使用旧时间戳和旧签名。
正确调用流程
钉钉小程序
↓ 请求创建订单
业务服务端
↓ 调用支付宝 App 支付接口并使用应用私钥签名
生成完整 orderString
↓ 返回给小程序
钉钉小程序调用 dd.pay
↓
支付宝完成支付
↓ 异步通知
业务服务端验签并更新订单状态
前端不能接触支付宝应用私钥,也不要修改服务端返回的订单串。
服务端生成订单示例
以下示例使用支付宝 Java SDK 的常见调用方式。具体的 SDK 类名和版本,应以项目实际依赖为准。
AlipayClient alipayClient = new DefaultAlipayClient(
"https://openapi.alipay.com/gateway.do",
alipayAppId,
merchantPrivateKey,
"json",
"UTF-8",
alipayPublicKey,
"RSA2"
);
AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setOutTradeNo("8_241107161738381");
model.setSubject("付费课程");
model.setTotalAmount("0.01");
model.setProductCode("QUICK_MSECURITY_PAY");
model.setTimeoutExpress("30m");
request.setBizModel(model);
request.setNotifyUrl(
"https://test.yylx.wang/appapi/cartpay/notify_ali"
);
// sdkExecute 通常用于生成客户端支付所需的已签名订单串
AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
if (!response.isSuccess()) {
throw new IllegalStateException(
response.getCode() + ": " + response.getSubMsg()
);
}
String orderString = response.getBody();
接口只需把 orderString 返回给小程序:
{
"orderString": "app_id=...&biz_content=...&method=alipay.trade.app.pay&...&sign=..."
}
不要把 merchantPrivateKey 返回给前端,也不要将其写入客户端日志。
小程序调用示例
async function createAndPay() {
try {
const result = await requestCreateOrder();
dd.pay({
info: result.orderString,
success(res) {
console.log("dd.pay success:", res);
// 客户端结果只能用于页面展示。
// 最终支付状态应由服务端查询或异步通知确认。
},
fail(err) {
console.error("dd.pay fail:", JSON.stringify(err));
}
});
} catch (err) {
console.error("create order failed:", err);
}
}
服务端返回的 orderString 应原样传给 info,不要再次执行:
encodeURIComponent(orderString)
也不要解析后重新排序、拼接或编码。
为什么前端加签会造成问题
前端加签有两个严重问题。
首先,应用私钥会被打包进小程序代码。即使代码经过混淆,攻击者仍可能提取私钥并伪造支付请求。一旦支付宝应用私钥进入前端,就应视为已经泄露,并立即更换密钥。
其次,前端运行环境可能改变待签名内容,例如:
+ 变成空格
%2F 被重新解码
JSON 字段顺序变化
中文被重复编码
notify_url 多出空格
这些变化会让实际提交的参数与签名时使用的字符串不一致,最终导致验签失败。此时客户端有时只会显示“系统繁忙”之类的通用提示。
建议的排查顺序
- 将接口改为
alipay.trade.app.pay。 - 将
product_code改为QUICK_MSECURITY_PAY。 - 把签名逻辑迁移到服务端,立即作废并重新生成已进入前端的私钥。
- 使用支付宝官方服务端 SDK 生成完整订单串。
- 删除
notify_url前的空格,并改用公网 HTTPS 地址。 - 确认支付宝应用已经开通对应支付产品,签约主体和应用配置正确。
- 确认签名私钥与支付宝开放平台配置的公钥匹配,并使用
RSA2。 - 将 SDK 返回的订单串原样传入
dd.pay.info,不要二次编码。 - 在
fail回调中完整记录错误对象,包括错误码、错误信息和请求时间。 - 同时检查支付宝服务端 SDK 响应中的
code、sub_code、msg和sub_msg。
如果改成由服务端生成 App 支付订单串后仍然报错,需要继续核对当前钉钉小程序的 dd.pay 文档要求、应用支付权限,以及支付宝应用与钉钉应用之间的配置关系。“系统繁忙”只是客户端的通用提示,不能据此判断具体原因。最终应以 dd.pay 失败回调和支付宝服务端响应中的错误码为准。
备注:内容仅供参考。
