PAYATHON 2026

uniapp多端商城中支付宝小程序端如何实现支付宝支付?

支付阿杰

结论

支付宝小程序不能直接复用 H5 的支付宝支付代码。H5 一般通过网页跳转、提交表单或唤起支付宝 App 完成支付;支付宝小程序则需要由服务端创建交易,再通过小程序中的 my.tradePay,或当前框架版本支持的 uni.requestPayment,调起支付宝收银台。

完整流程如下:

  1. 小程序向商城后端提交业务订单号。
  2. 后端校验订单,调用支付宝服务端接口创建交易。
  3. 后端把支付宝返回的 trade_no 传给小程序。
  4. 小程序调用 my.tradePay 发起支付。
  5. 后端接收支付宝异步通知,确认最终支付结果。

不能只根据前端回调判断支付是否成功。最终结果必须以支付宝异步通知或主动查询结果为准,并且通知内容需要经过后端验签。

H5 支付代码为什么不能直接复用

支付宝 H5 支付和支付宝小程序支付处在不同的运行环境中:

  • H5 支付通常调用 alipay.trade.wap.pay,由接口返回跳转地址或 HTML 表单。
  • 支付宝小程序运行在支付宝客户端内,不适合通过网页表单跳转支付。
  • 小程序需要取得支付宝交易号,再调用小程序支付 API 唤起收银台。
  • 商户私钥、签名和订单金额校验必须放在服务端,不能写进 uni-app 前端代码。

商城原有的业务订单、金额计算、支付记录和回调处理逻辑可以继续使用,但支付接口和前端唤起方式需要针对小程序单独适配。

接入前的准备

接入前需要完成支付宝开放平台的相关配置:

  • 创建支付宝小程序应用,或使用已有应用。
  • 为应用开通对应的支付能力并完成商户签约。
  • 配置应用公钥或支付宝公钥证书。
  • 将商户私钥保存在服务端,不能交给前端。
  • 配置支付异步通知地址 notify_url。
  • 确认小程序应用、支付宝商户账号和支付产品之间已经正确绑定。

支付宝开放平台的应用类型、签名模式和 SDK 版本不同,所需配置也可能不同。具体应以当前应用后台显示的能力和支付宝开放平台文档为准。

整体支付流程

支付宝小程序
    ↓ 提交商城订单号
商城后端
    ↓ 校验订单状态、金额和用户
支付宝服务端支付接口
    ↓ 返回 trade_no
商城后端
    ↓ 返回 trade_no
支付宝小程序
    ↓ my.tradePay({ tradeNO })
支付宝收银台
    ↓ 异步通知 notify_url
商城后端
    ↓ 验签并更新订单
小程序查询订单最终状态

前端只需提交商城自己的订单号,例如 orderId,不能由前端决定实际支付金额。后端收到请求后,应从数据库重新读取订单金额。

uni-app 前端实现

可以使用条件编译,为支付宝小程序编写单独的支付逻辑:

<script>
export default {
  methods: {
    async pay(orderId) {
      // #ifdef MP-ALIPAY
      await this.payByAlipayMiniProgram(orderId)
      // #endif

      // #ifdef H5
      await this.payByAlipayH5(orderId)
      // #endif
    },

    async payByAlipayMiniProgram(orderId) {
      try {
        const [requestError, response] = await uni.request({
          url: 'https://api.example.com/payment/alipay/mini/create',
          method: 'POST',
          data: {
            orderId
          },
          header: {
            Authorization: `Bearer ${uni.getStorageSync('token')}`
          }
        })

        if (requestError) {
          throw requestError
        }

        const result = response.data

        if (!result || result.code !== 0 || !result.data?.tradeNo) {
          throw new Error(result?.message || '创建支付宝交易失败')
        }

        const tradeNo = result.data.tradeNo

        my.tradePay({
          tradeNO: tradeNo,
          success: async (payResult) => {
            /*
             * 前端 success 仅表示支付流程返回,
             * 不能直接据此将商城订单标记为已支付。
             */
            await this.confirmOrderStatus(orderId)
          },
          fail: (error) => {
            console.error('支付宝支付调用失败:', error)
            uni.showToast({
              title: '支付未完成',
              icon: 'none'
            })
          }
        })
      } catch (error) {
        console.error('创建支付失败:', error)
        uni.showToast({
          title: error.message || '支付请求失败',
          icon: 'none'
        })
      }
    },

    async confirmOrderStatus(orderId) {
      const [error, response] = await uni.request({
        url: `https://api.example.com/orders/${orderId}/payment-status`,
        method: 'GET',
        header: {
          Authorization: `Bearer ${uni.getStorageSync('token')}`
        }
      })

      if (error) {
        uni.showToast({
          title: '支付结果确认中,请稍后查看订单',
          icon: 'none'
        })
        return
      }

      const paid = response.data?.data?.paid === true

      if (paid) {
        uni.showToast({
          title: '支付成功',
          icon: 'success'
        })

        uni.redirectTo({
          url: `/pages/order/detail?id=${encodeURIComponent(orderId)}`
        })
      } else {
        uni.showToast({
          title: '支付结果确认中',
          icon: 'none'
        })
      }
    },

    async payByAlipayH5(orderId) {
      // 保留商城原有的 H5 支付实现
    }
  }
}
</script>

my.tradePay 是支付宝小程序的原生 API,相关代码只能编译到 MP-ALIPAY 环境。否则在 H5、微信小程序等平台中会出现 my is not defined。

如果当前 uni-app 版本明确支持支付宝小程序的 uni.requestPayment,也可以改用统一接口,但需要按照对应版本的文档传入 orderInfo。不同版本对支付宝小程序支付参数的封装可能有差异,因此在实际项目中,直接使用 my.tradePay 往往更清楚。

服务端创建支付宝交易

服务端应通过支付宝官方 SDK 调用适用于小程序场景的交易创建接口。下面的代码只展示基本处理结构,应用配置、SDK 初始化方式和具体响应类型,需要根据项目使用的支付宝 SDK 版本调整。

@PostMapping("/payment/alipay/mini/create")
public ApiResponse<CreatePaymentResponse> createPayment(
        @RequestBody CreatePaymentRequest request,
        LoginUser loginUser) throws Exception {

    // 1. 从数据库读取订单,不能使用前端传入的金额
    Order order = orderService.getById(request.getOrderId());

    if (order == null || !order.getUserId().equals(loginUser.getId())) {
        throw new BusinessException("订单不存在");
    }

    if (order.isPaid()) {
        throw new BusinessException("订单已经支付");
    }

    if (order.isClosed()) {
        throw new BusinessException("订单已关闭");
    }

    // 2. 生成或读取稳定的商户支付单号
    String outTradeNo = paymentService.getOrCreateOutTradeNo(order);

    AlipayTradeCreateRequest alipayRequest =
            new AlipayTradeCreateRequest();

    alipayRequest.setNotifyUrl(
            "https://api.example.com/payment/alipay/notify"
    );

    AlipayTradeCreateModel model = new AlipayTradeCreateModel();
    model.setOutTradeNo(outTradeNo);
    model.setTotalAmount(order.getPayAmount().toPlainString());
    model.setSubject("商城订单-" + order.getOrderNo());

    /*
     * buyerId 应由用户在支付宝小程序中授权后,
     * 由服务端使用 authCode 换取并绑定到商城用户。
     *
     * 部分新接口或应用配置可能使用其他用户标识字段,
     * 应以实际采用的支付宝接口模型为准。
     */
    model.setBuyerId(loginUser.getAlipayUserId());

    alipayRequest.setBizModel(model);

    AlipayTradeCreateResponse alipayResponse =
            alipayClient.execute(alipayRequest);

    if (!alipayResponse.isSuccess()) {
        throw new BusinessException(
                "创建支付宝交易失败:" + alipayResponse.getSubMsg()
        );
    }

    return ApiResponse.success(
            new CreatePaymentResponse(alipayResponse.getTradeNo())
    );
}

常用的业务参数包括:

{
  "out_trade_no": "商城支付单号",
  "total_amount": "实际支付金额",
  "subject": "订单标题",
  "buyer_id": "支付宝用户标识"
}

out_trade_no 必须唯一,并且在同一笔支付中保持稳定。用户对同一笔商城订单重复点击支付时,后端不应每次都创建新的支付单,否则容易引发重复交易和对账问题。

获取支付宝用户标识

如果交易创建接口要求提供支付宝用户标识,可以先在小程序端获取授权码:

my.getAuthCode({
  scopes: ['auth_user'],
  success: async ({ authCode }) => {
    const [error, response] = await uni.request({
      url: 'https://api.example.com/auth/alipay',
      method: 'POST',
      data: {
        authCode
      }
    })

    if (error) {
      console.error('支付宝授权失败:', error)
      return
    }

    console.log('登录或绑定结果:', response.data)
  },
  fail: (error) => {
    console.error('用户授权失败:', error)
  }
})

服务端收到 authCode 后,通过支付宝服务端授权接口换取用户身份信息,再把支付宝用户标识与商城账号绑定。

authCode 有时效限制,通常也只能按照规定使用。它不能长期保存在前端,更不能直接作为支付交易号传给 my.tradePay。

处理异步通知

支付完成后,支付宝会请求服务端配置的 notify_url。后端至少需要完成以下校验和处理:

  1. 使用支付宝公钥或证书验证通知签名。
  2. 检查 app_id 是否属于当前应用。
  3. 检查 out_trade_no 是否存在。
  4. 对比 total_amount 和数据库中的订单金额,确保两者完全一致。
  5. 检查商户身份相关字段。
  6. 确认交易状态属于支付成功状态。
  7. 在数据库事务中更新支付单和商城订单。
  8. 保证重复通知也能安全处理,即接口需要具备幂等性。
  9. 处理成功后,按照支付宝协议返回指定的成功响应。

伪代码如下:

@PostMapping("/payment/alipay/notify")
public String receiveNotify(HttpServletRequest request) {
    Map<String, String> params = extractAlipayParams(request);

    boolean valid = AlipaySignature.rsaCheckV1(
            params,
            alipayPublicKey,
            "UTF-8",
            "RSA2"
    );

    if (!valid) {
        return "failure";
    }

    String outTradeNo = params.get("out_trade_no");
    String tradeNo = params.get("trade_no");
    String tradeStatus = params.get("trade_status");
    BigDecimal totalAmount =
            new BigDecimal(params.get("total_amount"));

    Payment payment = paymentService.getByOutTradeNo(outTradeNo);

    if (payment == null) {
        return "failure";
    }

    if (payment.getAmount().compareTo(totalAmount) != 0) {
        return "failure";
    }

    if ("TRADE_SUCCESS".equals(tradeStatus)
            || "TRADE_FINISHED".equals(tradeStatus)) {
        paymentService.markPaidIdempotently(
                payment.getId(),
                tradeNo,
                params
        );
    }

    return "success";
}

示例中的验签参数只用于说明处理方式。证书模式、公钥模式以及不同 SDK 版本使用的验签方法可能不同,实际代码必须与项目配置保持一致。

查询支付结果

用户完成支付后,可能马上关闭页面,也可能遇到支付宝异步通知尚未到达后端的情况。因此,前端收到支付回调后,可以继续查询商城后端的订单状态:

async function queryPaymentStatus(orderId) {
  const [error, response] = await uni.request({
    url: `https://api.example.com/orders/${orderId}/payment-status`,
    method: 'GET'
  })

  if (error) {
    throw error
  }

  return response.data
}

如果商城后端暂时仍显示未支付,前端可以在短时间内轮询几次,但要限制轮询次数和间隔。必要时,后端也可以调用支付宝交易查询接口进行补偿确认。

常见问题和注意事项

商户私钥不能放进小程序

小程序代码可以被下载和分析。商户私钥、应用私钥以及支付宝 SDK 的服务端签名逻辑,都必须保留在后端。

不能相信前端传入的金额

前端只提交商城订单号。后端需要重新读取商品价格、优惠、运费和实际应付金额,同时检查订单所属用户和订单状态。

不能只根据 my.tradePay 回调发货

前端回调可能因网络中断、用户退出页面或其他异常流程而不完整。发货、充值和积分发放等操作,只能在服务端确认真实支付结果后执行。

注意参数名称

支付宝小程序调用 my.tradePay 时使用以下参数:

{
  tradeNO: '支付宝交易号'
}

参数名是 tradeNO。它的值是支付宝交易号,不是商城的 orderId,也不是商户订单号 out_trade_no。

控制重复支付

支付按钮需要避免连续点击,后端也要检查以下内容:

  • 订单是否已经支付;
  • 支付单是否已经创建;
  • 原交易是否还能继续支付;
  • 是否需要先关闭旧交易,再创建新交易。

前端防抖只能改善操作体验。真正的重复支付控制必须由服务端完成。

为不同平台拆分支付接口

后端可以按照支付场景拆分接口,例如:

/payment/alipay/h5/create
/payment/alipay/mini/create
/payment/wechat/mini/create

各支付渠道可以共用订单和支付记录,但创建交易所需的参数、接口返回值和前端唤起方式应分别处理。这样既不会影响现有的 H5 支付,也方便以后增加退款、关闭交易和对账功能。

备注:内容仅供参考。