PAYATHON 2026

如何从第三方支付网站重定向回 React 应用

支付老李

结论

React 应用可以把前端路由设置为支付完成后的返回地址,例如:

https://example.com/payment/result

但不能只根据浏览器重定向携带的参数判断支付是否成功。支付流程通常还需要一个后端异步通知地址,例如:

https://api.example.com/payments/webhook

前端返回地址用于展示支付结果。后端通知地址用于接收支付平台的签名通知、验证支付状态并更新订单。字段名称和回调规则要以支付平台的 API 文档为准。

推荐的处理流程

一般可以按以下流程处理:

  1. React 请求自己的后端创建支付订单。
  2. 后端调用第三方支付 API,生成支付链接或支付会话。
  3. React 将浏览器跳转到第三方支付页面。
  4. 用户完成支付后,支付平台向后端发送通知。
  5. 支付平台将用户重定向到 React 的结果页面。
  6. React 携带订单号向后端查询最终支付状态。

支付平台对这两个地址的命名可能不同:

  • return_url、success_url 或 redirect_url:浏览器返回 React 应用的地址。
  • notify_url、callback_url 或 webhook_url:服务器接收支付结果通知的地址。

具体名称并不统一,请以接入平台的文档为准。

React 中发起支付

不要在 React 中保存支付平台的密钥。前端只调用自己的后端,支付订单由后端创建。

async function startPayment(orderId) {
  const response = await fetch("/api/payments/create", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ orderId }),
  });

  if (!response.ok) {
    throw new Error("创建支付订单失败");
  }

  const { paymentUrl } = await response.json();

  window.location.assign(paymentUrl);
}

如果支付平台要求提交表单,而不是直接访问 URL,也可以按照平台文档动态提交表单。签名计算仍应在后端完成。

后端创建支付订单

下面用 Express 展示基本结构。接口名称和参数仅用于说明流程,需要替换成支付平台实际要求的字段。

import express from "express";

const app = express();

app.use(express.json());

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

  // 应先从数据库读取订单,并在后端确定金额。
  const order = await findOrderById(orderId);

  if (!order || order.status !== "pending") {
    return res.status(400).json({ error: "订单不可支付" });
  }

  const paymentSession = await paymentProvider.createPayment({
    merchantOrderId: order.id,
    amount: order.amount,
    returnUrl: `https://example.com/payment/result?orderId=${encodeURIComponent(order.id)}`,
    notifyUrl: "https://api.example.com/payments/webhook",
  });

  res.json({
    paymentUrl: paymentSession.paymentUrl,
  });
});

金额、商品信息和订单状态必须由后端读取和校验,不能直接信任 React 提交的数据。

配置 React 返回页面

使用 React Router 时,可以添加一个支付结果路由:

import { BrowserRouter, Routes, Route } from "react-router-dom";
import PaymentResult from "./PaymentResult";

export default function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/payment/result" element={<PaymentResult />} />
      </Routes>
    </BrowserRouter>
  );
}

结果页面从 URL 获取订单号,再向后端查询状态:

import { useEffect, useState } from "react";
import { useSearchParams } from "react-router-dom";

export default function PaymentResult() {
  const [searchParams] = useSearchParams();
  const [status, setStatus] = useState("loading");
  const orderId = searchParams.get("orderId");

  useEffect(() => {
    if (!orderId) {
      setStatus("invalid");
      return;
    }

    fetch(`/api/payments/status?orderId=${encodeURIComponent(orderId)}`)
      .then((response) => {
        if (!response.ok) {
          throw new Error("查询支付状态失败");
        }

        return response.json();
      })
      .then((data) => setStatus(data.status))
      .catch(() => setStatus("error"));
  }, [orderId]);

  if (status === "loading") return <p>正在确认支付结果……</p>;
  if (status === "paid") return <p>支付成功。</p>;
  if (status === "pending") return <p>支付结果确认中,请稍后刷新。</p>;
  if (status === "invalid") return <p>缺少订单信息。</p>;

  return <p>暂时无法确认支付结果。</p>;
}

即使返回 URL 中带有 status=success,也不能直接把订单标记为已支付,因为用户可以自行修改 URL 参数。

后端接收支付通知

支付平台通常会向后端发送异步通知。后端收到通知后,需要验证签名,并向支付平台查询或核对交易信息:

app.post(
  "/payments/webhook",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    try {
      const event = paymentProvider.verifyWebhook(
        req.body,
        req.headers
      );

      if (event.type === "payment.succeeded") {
        const payment = event.data;

        await markOrderAsPaid({
          orderId: payment.merchantOrderId,
          transactionId: payment.transactionId,
          amount: payment.amount,
        });
      }

      res.sendStatus(200);
    } catch {
      res.sendStatus(400);
    }
  }
);

接入时必须使用支付平台提供的签名验证方式。部分平台要求返回指定文本,而不是普通的 HTTP 200 响应。

callback URL 应该填写什么

如果支付平台要求填写用户支付后的跳转地址,应填写 React 中已经配置并可公开访问的路由:

https://example.com/payment/result

如果要求填写支付结果通知地址,则应填写后端接口:

https://api.example.com/payments/webhook

支付平台通常无法访问开发环境中的 http://localhost:3000,部分平台也只接受 HTTPS 地址。测试时可以使用平台提供的沙箱环境,并配合临时 HTTPS 隧道或已经部署的测试域名。

注意事项

  • 使用 HTTPS,并在支付平台后台配置允许访问的域名。
  • 支付密钥、签名密钥和商户私钥只能保存在后端。
  • 回调处理应具备幂等性,避免重复通知导致重复入账或发货。
  • 验证通知签名,并核对订单号、金额、币种和商户身份。
  • 不能只依赖前端跳转。用户可能关闭页面,网络也可能中断,而后端通知仍可正常到达。
  • 不要在返回 URL 中放入完整订单信息或敏感数据。
  • 使用 BrowserRouter 时,服务器必须把 /payment/result 等前端路由回退到 index.html。否则用户从支付网站直接返回时可能遇到 404。
  • 如果后端还没有收到通知,结果页可以显示“确认中”,并在短时间内轮询订单状态。

备注:内容仅供参考。