PAYATHON 2026

钉钉小程序调用 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. 不能先编码再随意修改订单串

支付参数通常要经过两个阶段:

  1. 使用原始参数生成待签名字符串。
  2. 对签名结果和参数值进行 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 多出空格

这些变化会让实际提交的参数与签名时使用的字符串不一致,最终导致验签失败。此时客户端有时只会显示“系统繁忙”之类的通用提示。

建议的排查顺序

  1. 将接口改为 alipay.trade.app.pay。
  2. 将 product_code 改为 QUICK_MSECURITY_PAY。
  3. 把签名逻辑迁移到服务端,立即作废并重新生成已进入前端的私钥。
  4. 使用支付宝官方服务端 SDK 生成完整订单串。
  5. 删除 notify_url 前的空格,并改用公网 HTTPS 地址。
  6. 确认支付宝应用已经开通对应支付产品,签约主体和应用配置正确。
  7. 确认签名私钥与支付宝开放平台配置的公钥匹配,并使用 RSA2。
  8. 将 SDK 返回的订单串原样传入 dd.pay.info,不要二次编码。
  9. 在 fail 回调中完整记录错误对象,包括错误码、错误信息和请求时间。
  10. 同时检查支付宝服务端 SDK 响应中的 code、sub_code、msg 和 sub_msg。

如果改成由服务端生成 App 支付订单串后仍然报错,需要继续核对当前钉钉小程序的 dd.pay 文档要求、应用支付权限,以及支付宝应用与钉钉应用之间的配置关系。“系统繁忙”只是客户端的通用提示,不能据此判断具体原因。最终应以 dd.pay 失败回调和支付宝服务端响应中的错误码为准。

备注:内容仅供参考。