PAYATHON 2026

移动端支付与二维码扫码支付如何获取支付结果?

支付小周

结论

无论用户在移动端直接支付,还是用另一部手机扫描二维码支付,最终结果都要以服务端确认的订单状态为准。

  • 客户端 SDK 返回的结果只适合用来更新页面提示,不能作为款项到账的最终依据。
  • 服务端应接收支付平台发送的异步通知,完成验签后更新订单状态。
  • 移动端或二维码展示端应向业务服务端查询支付状态。业务服务端也可以通过 WebSocket、SSE 等方式主动推送结果。
  • 如果异步通知迟迟没有到达,服务端可以按需调用支付平台的订单查询接口补查。
  • 客户端不应直接查询支付平台,也不能只根据客户端回调执行发货、充值或开通服务等操作。

“SDK 自动轮询服务端”并不是所有支付 SDK 都具备的机制,具体要看所使用的 SDK。即使 SDK 返回支付成功,业务系统仍需以服务端记录的订单状态为准。

为什么不能直接相信客户端结果

客户端支付回调只能说明支付流程在客户端的执行情况,实际使用中可能遇到这些问题:

  • 用户已经支付成功,但客户端被关闭、断网,或者没有收到回调。
  • 客户端仍显示支付处理中,服务端稍后才收到成功通知。
  • SDK 返回成功时,业务服务端还没有核对订单金额、商户号等信息。
  • 客户端环境并不可信,回调参数可能被篡改或伪造。
  • 支付平台的异步通知可能发送失败,也可能晚于客户端查询到达。

因此,客户端回调可以用来更新交互状态,例如显示”正在确认支付结果”。发货、充值、开通服务等业务处理则应由服务端执行。

移动端支付结果的推荐处理流程

常见的处理流程是:

  1. 移动端请求业务服务端创建订单。
  2. 服务端调用支付平台的下单接口,保存商户订单号和初始状态。
  3. 服务端将支付参数返回给移动端。
  4. 移动端调用支付 SDK。
  5. SDK 回调后,移动端向业务服务端查询订单状态。
  6. 支付平台向服务端发送异步通知。
  7. 服务端完成验签、金额校验和幂等处理,再更新订单状态。
  8. 如果订单长时间停留在处理中,服务端可以调用支付平台的订单查询接口补查。

客户端查询的是业务系统自己的接口,例如:

GET /api/payment/orders/{orderNo}/status

服务端可以返回统一的状态数据:

{
  "orderNo": "ORDER_202609130001",
  "status": "PAID",
  "paidAt": "2026-09-13T14:30:00+08:00"
}

订单状态至少可以区分为:

PENDING      待支付
PROCESSING   支付处理中
PAID         支付成功
CLOSED       订单已关闭
FAILED       支付失败
REFUNDED     已退款

收到 SDK 回调后,客户端可以在短时间内轮询业务服务端:

async function waitForPayment(orderNo) {
  const intervals = [1000, 1500, 2000, 3000, 5000];

  for (const delay of intervals) {
    const response = await fetch(
      `/api/payment/orders/${encodeURIComponent(orderNo)}/status`
    );
    const result = await response.json();

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

    if (["CLOSED", "FAILED", "REFUNDED"].includes(result.status)) {
      throw new Error(`Payment ended with status: ${result.status}`);
    }

    await new Promise(resolve => setTimeout(resolve, delay));
  }

  return { orderNo, status: "PROCESSING" };
}

轮询超时并不等于支付失败。页面可以提示”支付结果确认中”,并允许用户稍后刷新订单状态。

二维码展示端如何获取结果

二维码支付与移动端支付遵循同一原则。区别在于,发起支付和完成支付的设备不是同一台。

可以按以下流程处理:

  1. 二维码展示端向业务服务端创建支付订单。
  2. 服务端调用支付平台接口,获取二维码内容。
  3. 展示端显示二维码并记录业务订单号。
  4. 用户使用另一台手机扫码支付。
  5. 支付平台向业务服务端发送异步通知。
  6. 服务端验签并更新订单状态。
  7. 二维码展示端从业务服务端获取最新状态。
  8. 确认支付成功后,展示端停止轮询、关闭二维码或跳转到成功页面。

展示端可以轮询业务服务端,但不应直接轮询支付平台:

function startQrPaymentPolling(orderNo) {
  const timer = setInterval(async () => {
    try {
      const response = await fetch(
        `/api/payment/orders/${encodeURIComponent(orderNo)}/status`
      );
      const result = await response.json();

      if (result.status === "PAID") {
        clearInterval(timer);
        showPaymentSuccess(result);
      } else if (["CLOSED", "FAILED"].includes(result.status)) {
        clearInterval(timer);
        showPaymentClosed(result);
      }
    } catch (error) {
      // 短暂网络异常不应立即判定支付失败
      console.error("Failed to query payment status", error);
    }
  }, 2000);

  return () => clearInterval(timer);
}

如果需要减少轮询请求,可以改用 WebSocket 或 SSE。展示端订阅订单状态,服务端收到支付通知后主动推送结果:

const socket = new WebSocket(
  `wss://example.com/ws/payments?orderNo=${encodeURIComponent(orderNo)}`
);

socket.onmessage = event => {
  const result = JSON.parse(event.data);

  if (result.status === "PAID") {
    showPaymentSuccess(result);
    socket.close();
  }
};

使用 WebSocket 后,页面在重新连接或刷新时仍应调用订单查询接口。连接中断可能导致推送消息丢失。

服务端异步通知的处理要点

服务端收到异步通知后,至少要完成以下检查:

  • 验证通知签名和证书。
  • 检查商户订单号是否存在。
  • 检查商户号、应用标识等是否属于当前系统。
  • 核对实付金额、币种与原订单是否一致。
  • 确认通知中的支付状态确实为成功。
  • 通过事务或唯一约束保证幂等。
  • 可靠保存支付结果后,再向支付平台返回成功响应。

伪代码示例:

@Transactional
public void handlePaymentNotification(PaymentNotification notification) {
    verifySignature(notification);

    PaymentOrder order = paymentOrderRepository
        .findByOrderNoForUpdate(notification.getOrderNo())
        .orElseThrow(() -> new IllegalArgumentException("Order not found"));

    verifyMerchant(notification, order);
    verifyAmountAndCurrency(notification, order);

    if (order.getStatus() == PaymentStatus.PAID) {
        return;
    }

    if (!notification.isPaymentSuccessful()) {
        return;
    }

    order.markPaid(
        notification.getTransactionId(),
        notification.getPaidAt()
    );

    paymentOrderRepository.save(order);
    publishPaymentSucceededEvent(order);
}

主动查询支付平台的时机

异步通知通常是确认支付结果的主要渠道,但通知不一定能按时到达。遇到以下情况时,服务端可以主动查询支付平台:

  • 客户端报告 SDK 支付成功,但本地订单仍是 PENDING。
  • 二维码展示端等待较长时间后仍未收到结果。
  • 异步通知验签失败或处理异常,需要通过对账确认。
  • 定时任务发现订单长时间停留在中间状态。
  • 日终对账发现业务订单与支付平台记录不一致。

主动查询需要设置频率限制和超时时间,不能把每次客户端轮询都直接转成对支付平台的查询。服务端应先查询本地数据库,只有满足补查条件时才访问支付平台。

注意事项

  • 客户端显示”取消支付”,通常只说明用户退出了当前支付流程,不一定表示订单最终失败。
  • 二维码需要设置有效期。二维码过期后应停止轮询并关闭订单,具体能否关闭取决于支付平台。
  • 订单号必须唯一。刷新二维码时,要明确复用原订单还是创建新订单。
  • 支付成功后的发货、充值和记账等操作必须支持幂等处理。
  • 支付平台的密钥、签名私钥和敏感查询凭证不能放在客户端。
  • 轮询可以使用固定间隔或指数退避,同时设置总时长。页面进入后台时可以暂停轮询,恢复后立即查询一次。
  • 推送可以改善结果更新的实时性,但不能替代订单查询、异步通知和对账机制。
  • 不同支付平台使用的回调字段、验签方式和订单查询接口并不相同,具体实现应以所接入平台的官方接口定义为准。

备注:内容仅供参考。