PAYATHON 2026

支付宝小程序如何实现离线支付收款功能?

支付阿杰

结论

普通支付宝小程序无法在完全断网时完成收款。

支付宝支付需要由服务端创建交易,再由支付宝完成身份校验、风险控制、扣款和交易状态确认。整个过程依赖网络连接,无法通过小程序配置实现真正的离线支付。

如果商家另外签约了支付宝的特定线下受理产品或配套硬件,应按相应产品的接入文档操作。这类产品与普通小程序支付不同,无法只通过调用小程序 API 开启。

为什么不能离线完成支付

常规的小程序支付通常包括以下步骤:

  1. 小程序把商品、金额等信息提交给商家服务端。
  2. 商家服务端调用支付宝开放平台接口创建交易。
  3. 服务端把交易号或支付参数返回给小程序。
  4. 小程序调用 my.tradePay,拉起支付宝收银台。
  5. 支付宝执行扣款、风险控制和交易处理。
  6. 商家服务端接收异步通知,或主动查询交易的最终状态。

完全断网时,小程序既无法创建交易,也无法向支付宝确认支付结果。即使订单已经保存在本地,也只能算作”待支付订单”,不能视为已经收款。

在某些场景下,支付宝客户端可能允许用户展示付款码,但这不代表小程序可以离线收款。付款码是否可以离线受理、受理终端如何配置以及金额限制等,都取决于商户签约的产品和支付宝当时的风控规则。

推荐的处理方式

较稳妥的做法是把业务分成两个阶段:断网时记录订单,联网后完成支付。

1. 检查网络状态

进入收款页面或提交支付前,可以先检查当前网络状态。下面的判断主要用于改善交互体验,能否支付仍要以创建交易接口是否成功为准。

function checkNetwork() {
  return new Promise((resolve, reject) => {
    my.getNetworkType({
      success(res) {
        if (res.networkType === 'NOTREACHABLE') {
          reject(new Error('当前无网络连接'));
          return;
        }

        resolve(res.networkType);
      },
      fail() {
        reject(new Error('无法获取网络状态'));
      }
    });
  });
}

不同基础库环境返回的网络类型值可能不同。接入时需要核对当前的支付宝小程序文档,并在真机上测试。

2. 无网络时只保存业务订单

断网时可以在本地保存订单草稿,但不能把订单设为”支付成功”,也不能展示支付成功凭证。

function savePendingOrder(order) {
  const result = my.getStorageSync({
    key: 'pending_orders'
  });

  const pendingOrders = Array.isArray(result.data) ? result.data : [];

  pendingOrders.push({
    localOrderId: order.localOrderId,
    amount: order.amount,
    subject: order.subject,
    createdAt: Date.now(),
    status: 'WAITING_FOR_NETWORK'
  });

  my.setStorageSync({
    key: 'pending_orders',
    data: pendingOrders
  });
}

localOrderId 应由业务系统生成,并保证唯一。它只是本地订单号,不能代替支付宝交易号。

3. 恢复联网后创建交易

网络恢复后,再把本地订单提交给商家服务端。服务端必须重新校验支付金额、商品信息和订单状态,不能直接信任小程序传入的数据。

function createPayment(localOrderId) {
  return new Promise((resolve, reject) => {
    my.request({
      url: 'https://example.com/api/payment/create',
      method: 'POST',
      data: {
        localOrderId
      },
      success(res) {
        if (res.status === 200 && res.data && res.data.tradeNO) {
          resolve(res.data.tradeNO);
          return;
        }

        reject(new Error('创建支付交易失败'));
      },
      fail() {
        reject(new Error('网络请求失败'));
      }
    });
  });
}

这里的接口地址和响应结构只是业务示例。实际项目应根据使用的支付宝支付产品和服务端 SDK 进行调整。

4. 调用支付接口

服务端创建交易并返回 tradeNO 后,再调用 my.tradePay:

function startPayment(tradeNO) {
  my.tradePay({
    tradeNO,
    success(result) {
      // 前端返回结果只能用于页面提示。
      // 最终状态必须由服务端向支付宝查询或根据异步通知确认。
      console.log('支付调用结果:', result);
    },
    fail(error) {
      console.error('支付未完成:', error);
    }
  });
}

不能只根据 my.tradePay 的前端回调发货、核销或将订单标记为已支付。用户关闭页面、网络中断或回调丢失时,前端拿到的结果可能不完整。

5. 服务端确认最终结果

服务端应维护明确的订单状态,例如:

CREATED
WAITING_FOR_PAYMENT
PAYING
PAID
CLOSED
REFUNDED

支付结果应以支付宝异步通知或服务端主动查询的结果为准。收到异步通知后,还要按照支付宝开放平台的要求验证签名,并核对商户身份、订单号、金额和交易状态。

断网场景下的替代方案

可以根据具体业务选择以下处理方式:

  • 保存订单草稿,网络恢复后让用户继续支付。
  • 生成取货单、排队号等非支付凭证,等联网支付完成后再核销。
  • 收银设备断网时启用备用网络,如移动热点或双链路网络。
  • 如果经营场所确实需要离线受理,可向支付宝服务商或开放平台确认是否有能够签约的专用线下受理产品。

无论采用哪种方式,都不能把”本地记录成功”、“已展示付款码”或”已提交支付请求”当作资金已经到账。

注意事项

  • 不要在小程序中保存商户私钥、应用私钥或支付接口签名密钥。
  • 服务端必须根据订单重新计算并校验金额。
  • 上传本地订单时,应使用唯一的业务订单号,并在服务端实施幂等控制,防止重复创建交易。
  • 本地缓存可能被清理、修改或丢失,不能用作财务记账依据或支付凭证。
  • 网络恢复后不要自动发起扣款,应由用户明确确认支付。
  • 退款、撤销、关单和对账也需要联网处理。
  • 是否提供专用离线受理能力,以及适用行业和限制条件,应以商户实际签约权限和支付宝开放平台的当前文档为准。

备注:内容仅供参考。