PAYATHON 2026

如何在钉钉小程序中接入支付宝支付?

支付阿杰

结论

钉钉小程序可以唤起支付宝付款,但支付能力不能只在小程序前端接入。完整流程包括:

  1. 在支付宝开放平台开通所需的支付产品,完成商户签约。
  2. 配置支付宝应用、公私钥和异步通知地址。
  3. 由业务服务端创建支付宝订单,生成支付参数。
  4. 小程序调用钉钉支付 API,唤起支付宝收银台。
  5. 服务端接收支付宝异步通知,完成验签并更新订单状态。

是否付款成功,必须以服务端收到的异步通知或主动查单结果为准,不能只看小程序回调。

接入前需要确认的条件

开发前先确认以下事项:

  • 当前钉钉应用类型是否支持支付宝支付。
  • 支付场景属于企业内部应用、第三方企业应用,还是面向个人用户的小程序。
  • 支付宝商户账号是否已完成实名认证和支付产品签约。
  • 钉钉应用与支付宝应用是否需要关联,是否要单独申请支付权限。
  • 目标客户端是否支持该支付 API,包括 Android、iOS、PC 和 Web 端的支持范围。
  • 当前钉钉客户端版本是否符合接口要求。

应用类型、开放平台配置和客户端版本不同,可用的支付接口也可能不同。常见接口名是 dd.tradePay,实际接入时应查看钉钉开放平台中对应应用类型的最新支付文档。如果控制台或文档中的接口名称、入参格式与本文示例不同,以对应文档为准。

一、完成支付宝侧配置

在支付宝开放平台创建应用,并完成支付相关配置,通常包括:

  • 获取支付宝应用的 app_id。
  • 开通适合当前业务的支付产品。
  • 设置应用公钥,保存支付宝公钥或支付宝公钥证书。
  • 配置签名算法,通常使用 RSA2。
  • 配置支付异步通知地址 notify_url。
  • 发布或上线支付宝应用。
  • 确认签约商户与实际收款主体一致。

私钥只能保存在服务端,不能写入钉钉小程序代码、配置文件或前端请求参数。

使用证书模式时,还需要妥善保存并配置:

  • 应用公钥证书
  • 支付宝公钥证书
  • 支付宝根证书

密钥模式和证书模式采用不同的验签方式,不能混用。

二、完成钉钉侧配置

进入钉钉开放平台中的对应应用,检查并配置支付能力。具体操作取决于应用类型,可能包括:

  • 申请支付接口权限。
  • 配置应用安全域名或服务器出口信息。
  • 配置服务端接口调用凭证。
  • 关联支付宝开放平台应用或商户主体。
  • 将应用发布为可测试或正式使用的版本。
  • 将测试人员加入应用可见范围。

如果控制台中找不到支付能力入口,先检查应用类型、主体认证状态和支付能力的开放范围,再开始编写前端代码。

三、设计服务端订单

建议先在业务系统中创建订单,再请求支付宝创建支付交易。订单至少需要保存:

  • 业务订单号
  • 支付金额
  • 商品标题
  • 用户或企业标识
  • 支付宝交易号
  • 当前支付状态
  • 创建时间和支付完成时间
  • 异步通知原始数据
  • 退款状态

订单状态不能只有“成功”和“失败”,至少可以设计为:

CREATED     已创建
PAYING      支付处理中
PAID        已支付
CLOSED      已关闭
REFUNDING   退款处理中
REFUNDED    已退款

业务订单号必须唯一。支付金额应由服务端根据商品和订单数据计算,不能信任小程序提交的金额。

四、服务端创建支付宝订单

小程序将商品或订单信息提交给业务服务端。服务端完成校验后,再调用支付宝接口生成支付参数。

下面用 Node.js 伪代码说明整体结构。支付宝 SDK 不同版本的类名和方法签名可能不同,请根据项目实际使用的官方 SDK 调整。

import express from "express";

const app = express();
app.use(express.json());

app.post("/api/payment/create", async (req, res) => {
  const { productId } = req.body;

  // 1. 从数据库读取商品,不接受前端直接指定最终金额
  const product = await findProductById(productId);
  if (!product || !product.enabled) {
    return res.status(400).json({
      code: "INVALID_PRODUCT",
      message: "商品不存在或不可购买"
    });
  }

  // 2. 创建本地业务订单
  const order = await createBusinessOrder({
    productId: product.id,
    amount: product.price,
    status: "CREATED"
  });

  // 3. 调用支付宝 SDK 创建支付参数
  const orderStr = await createAlipayOrder({
    outTradeNo: order.orderNo,
    totalAmount: product.price,
    subject: product.name,
    notifyUrl: "https://api.example.com/api/payment/alipay/notify"
  });

  await updateOrder(order.orderNo, {
    status: "PAYING"
  });

  res.json({
    code: "OK",
    data: {
      orderNo: order.orderNo,
      orderStr
    }
  });
});

createAlipayOrder 应通过支付宝官方 SDK 在服务端实现,并用应用私钥完成签名。服务端应向前端返回支付 API 所需的完整支付参数,例如 orderStr。不要把应用私钥或未签名的敏感参数交给前端处理。

五、小程序唤起支付宝支付

如果当前应用使用的钉钉支付接口是 dd.tradePay,可以这样调用:

async function pay(productId) {
  try {
    const response = await fetch("https://api.example.com/api/payment/create", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify({ productId })
    });

    const result = await response.json();

    if (result.code !== "OK") {
      throw new Error(result.message || "创建支付订单失败");
    }

    const { orderNo, orderStr } = result.data;

    dd.tradePay({
      orderStr,
      success(payResult) {
        // 前端回调只表示收银台调用结果,不能直接作为到账依据
        queryOrderStatus(orderNo);
      },
      fail(error) {
        console.error("唤起支付失败", error);
      }
    });
  } catch (error) {
    console.error("支付请求失败", error);
  }
}

部分支付接入方式返回支付宝交易号 tradeNO,而不是 orderStr。如果当前钉钉开放平台文档要求传入 tradeNO,服务端应先创建交易并返回该字段,再按照文档调用支付接口。例如:

dd.tradePay({
  tradeNO,
  success(result) {
    queryOrderStatus(orderNo);
  },
  fail(error) {
    console.error("支付失败", error);
  }
});

orderStr 和 tradeNO 对应不同的下单及唤起方式,不能互相替换。使用哪一种,取决于当前支付产品、钉钉接口定义和支付宝下单接口。

六、处理支付宝异步通知

付款完成后,支付宝会向 notify_url 发送异步通知。服务端收到通知后需要:

  1. 读取支付宝发送的原始表单参数。
  2. 使用支付宝公钥或证书验签。
  3. 校验 app_id、商户身份和交易状态。
  4. 根据 out_trade_no 查询本地订单。
  5. 校验金额、币种和订单内容。
  6. 以幂等方式更新订单状态。
  7. 按照支付宝协议返回成功响应。

示意代码如下:

app.post(
  "/api/payment/alipay/notify",
  express.urlencoded({ extended: false }),
  async (req, res) => {
    const params = req.body;

    const verified = verifyAlipaySignature(params);
    if (!verified) {
      return res.send("failure");
    }

    const order = await findOrderByOrderNo(params.out_trade_no);
    if (!order) {
      return res.send("failure");
    }

    if (params.app_id !== process.env.ALIPAY_APP_ID) {
      return res.send("failure");
    }

    if (String(params.total_amount) !== String(order.amount)) {
      return res.send("failure");
    }

    const paid =
      params.trade_status === "TRADE_SUCCESS" ||
      params.trade_status === "TRADE_FINISHED";

    if (paid) {
      // 必须保证重复通知不会产生重复发货、重复记账等问题
      await markOrderPaidOnce({
        orderNo: order.orderNo,
        alipayTradeNo: params.trade_no,
        paidAt: params.gmt_payment
      });
    }

    res.send("success");
  }
);

示例中的 verifyAlipaySignature 和 markOrderPaidOnce 需要结合实际使用的 SDK、数据库和事务机制实现。

验签前不要修改通知参数,也不要进行多余的编码转换,否则可能造成验签失败。

七、提供订单查询接口

用户付款后,可能遇到这些情况:

  • 支付已经成功,但小程序回调丢失。
  • 用户付款后立即关闭收银台。
  • 网络中断,前端没有收到结果。
  • 支付宝异步通知尚未到达。
  • 异步通知暂时处理失败。

因此,小程序返回业务页面后,应向自己的服务端查询订单状态:

async function queryOrderStatus(orderNo) {
  const response = await fetch(
    `https://api.example.com/api/payment/status?orderNo=${encodeURIComponent(orderNo)}`
  );

  const result = await response.json();

  if (result.data.status === "PAID") {
    showPaymentSuccess();
    return;
  }

  showPaymentPending();
}

如果订单长时间停留在 PAYING 状态,服务端可以调用支付宝查单接口核实最终结果。前端不能直接调用支付宝查单接口,因为相关签名凭证必须保存在服务端。

安全与业务注意事项

不要以前端回调作为支付凭证

dd.tradePay 的成功回调可能只表示支付流程正常返回。是否真正付款,应以服务端验签后的异步通知或支付宝查单结果为准。

金额必须在服务端确认

前端可以提交商品编号、优惠券编号或订单编号,但最终金额要由服务端重新计算,不能直接把前端传入的价格作为收款金额。

金额建议使用精确的十进制类型,或按最小货币单位存储,避免让二进制浮点数直接参与账务计算。

异步通知必须支持幂等

支付宝可能重复发送通知。更新订单、发放权益、增加余额或触发发货时,必须保证同一个订单只会被成功处理一次。

必须校验通知内容

完成验签后,还要核对:

  • app_id
  • out_trade_no
  • trade_no
  • total_amount
  • seller_id 或实际收款主体
  • trade_status

这样可以避免签名合法的通知被错误关联到其他订单。

不要在小程序中保存私钥

支付宝应用私钥、证书私钥和服务端密钥都不能打包进小程序。小程序代码可能被下载和分析,放在前端的长期密钥都应视为已经泄露。

正确处理取消和处理中状态

用户取消付款,不代表订单永久失败。服务端可以保留业务订单,允许用户在有效期内再次付款。订单状态不明确时,应先查单,再决定关闭订单还是重新发起支付。

配置可访问的通知地址

notify_url 必须是支付宝服务器能够访问的 HTTPS 地址。异步通知不能依赖钉钉登录状态,也不能要求浏览器提供 Cookie。

区分沙箱与生产环境

如果开发阶段使用支付宝沙箱,应用、网关、账号和密钥必须属于同一套沙箱环境。上线时要整体切换到生产配置,避免混用沙箱密钥、生产 app_id 和生产网关。

推荐的完整调用流程

用户点击支付
    ↓
钉钉小程序请求业务服务端
    ↓
服务端创建本地订单
    ↓
服务端调用支付宝接口并完成签名
    ↓
服务端返回 orderStr 或 tradeNO
    ↓
小程序调用钉钉支付 API
    ↓
用户在支付宝收银台完成付款
    ↓
支付宝向服务端发送异步通知
    ↓
服务端验签、核对金额并更新订单
    ↓
小程序查询服务端订单状态
    ↓
展示支付成功或处理中

如果调用时出现“接口不存在”“无权限”或“参数错误”,可以依次检查应用类型、支付权限、客户端版本、支付宝产品签约状态,以及当前接口要求的是 orderStr 还是 tradeNO。这些配置不匹配,是钉钉小程序接入支付宝支付时最常见的问题。

备注:内容仅供参考。