PAYATHON 2026

如何在 React component 中运行 library callback function

支付小周

结论

这 4 个函数通常不需要由 React 主动调用。你需要把它们注册给支付 library,再由支付流程在失败、取消、完成或超时时触发。

如果 library 支持传入 callback,可以直接使用 React component 中定义的函数。如果它只识别固定的全局函数名,则需要在 component 挂载时将这些函数注册到 window。支付完成后,更新 React state 即可显示成功 dialog。

推荐方式:将 callback 传给支付 library

如果支付 library 的初始化 API 接受 callback 配置,可以这样写:

import { useState } from 'react';

function Payment() {
  const [paymentStatus, setPaymentStatus] = useState('idle');

  function errorCallback(error) {
    console.error('Payment error:', error);
    setPaymentStatus('error');
  }

  function cancelCallback() {
    console.log('Payment cancelled');
    setPaymentStatus('cancelled');
  }

  function completeCallback(result) {
    console.log('Payment complete:', result);
    setPaymentStatus('complete');
  }

  function timeoutCallback() {
    console.log('Payment timeout');
    setPaymentStatus('timeout');
  }

  function startPayment() {
    // 以下名称和参数仅为结构示例,
    // 请替换成该 payment gateway 实际提供的 API。
    window.PaymentGateway.open({
      onError: errorCallback,
      onCancel: cancelCallback,
      onComplete: completeCallback,
      onTimeout: timeoutCallback,
    });
  }

  return (
    <div>
      <button type="button" onClick={startPayment}>
        Pay now
      </button>

      {paymentStatus === 'complete' && (
        <div role="dialog" aria-modal="true" aria-labelledby="payment-title">
          <h2 id="payment-title">支付成功</h2>
          <p>付款已经完成。</p>

          <button
            type="button"
            onClick={() => setPaymentStatus('idle')}
          >
            关闭
          </button>
        </div>
      )}
    </div>
  );
}

export default Payment;

支付完成时,completeCallback 会执行:

setPaymentStatus('complete');

state 更新后,React 会重新渲染 component,成功 dialog 随即显示。

其中的 PaymentGateway.open、onComplete 等名称只是示例。问题中没有说明具体使用哪个支付服务及其 API,因此需要根据该 library 的文档替换这些名称。

如果 library 要求全局 callback

一些旧式支付 library 不接受函数引用,只会调用页面上的 completeCallback、errorCallback 等全局函数。React component 中声明的函数不会自动成为全局函数,需要手动挂载到 window:

import { useEffect, useState } from 'react';

function Payment() {
  const [paymentStatus, setPaymentStatus] = useState('idle');
  const [paymentError, setPaymentError] = useState(null);

  useEffect(() => {
    window.errorCallback = function errorCallback(error) {
      console.error('Payment error:', error);
      setPaymentError(error);
      setPaymentStatus('error');
    };

    window.cancelCallback = function cancelCallback() {
      console.log('Payment cancelled');
      setPaymentStatus('cancelled');
    };

    window.completeCallback = function completeCallback(result) {
      console.log('Payment complete:', result);
      setPaymentStatus('complete');
    };

    window.timeoutCallback = function timeoutCallback() {
      console.log('Payment timeout');
      setPaymentStatus('timeout');
    };

    return () => {
      delete window.errorCallback;
      delete window.cancelCallback;
      delete window.completeCallback;
      delete window.timeoutCallback;
    };
  }, []);

  function closeDialog() {
    setPaymentStatus('idle');
    setPaymentError(null);
  }

  return (
    <section>
      {paymentStatus === 'complete' && (
        <div role="dialog" aria-modal="true" aria-labelledby="success-title">
          <h2 id="success-title">支付成功</h2>
          <p>订单付款已完成。</p>
          <button type="button" onClick={closeDialog}>
            确定
          </button>
        </div>
      )}

      {paymentStatus === 'error' && (
        <div role="alert">
          支付失败,请稍后重试。
          {paymentError?.message && <p>{paymentError.message}</p>}
        </div>
      )}
    </section>
  );
}

export default Payment;

component 挂载后,useEffect 会注册这些全局函数。component 卸载时,清理函数会将它们删除,防止 callback 残留或重复注册。

如果项目使用 TypeScript,还需要声明这些全局属性:

declare global {
  interface Window {
    errorCallback?: (error: unknown) => void;
    cancelCallback?: () => void;
    completeCallback?: (result?: unknown) => void;
    timeoutCallback?: () => void;
  }
}

确保外部 library 已经加载

如果通过 index.html 引入支付脚本,需要确认脚本地址正确,并根据实际情况使用 defer:

<script src="https://example.com/payment-library.js" defer></script>

调用 API 前也可以先检查 library 是否存在,以免脚本尚未加载就执行调用:

function startPayment() {
  if (!window.PaymentGateway) {
    console.error('Payment library has not been loaded');
    return;
  }

  window.PaymentGateway.open();
}

能否使用 defer 取决于该 library 的加载要求。如果它必须在页面解析期间立即执行,或者依赖其他脚本的加载顺序,应按照支付服务的接入文档处理。

注意事项

  • 优先使用 library 提供的 callback 参数或事件订阅 API。只有在 library 明确要求固定函数名时,才使用全局 window callback。
  • callback 的实际名称、参数和返回值应以支付服务文档为准。
  • 不要只根据前端的 completeCallback 判断订单已经可靠支付。正式业务中,应由服务端接收并验证支付网关的通知,再更新订单状态。
  • 如果支付 component 可能同时挂载多个实例,不应共用同一组 window callback,否则后注册的函数会覆盖先注册的函数。
  • dialog 的显示状态应由 React state 管理,不要在 callback 中直接操作 DOM。

备注:内容仅供参考。