PAYATHON 2026

Reactjs 集成 Cashfree 时提示 CashFree 未定义

支付阿杰

结论

CashFree 不是 React 模块,而是旧版 Seamless Basic 浏览器脚本挂载到 window 上的全局对象。在 React 组件中,需要通过 window.CashFree 调用它,并等脚本加载完成后再执行 init()。

安装 cashfree-sdk 通常解决不了这个问题。这个包可能是服务端 SDK,也可能属于另一代 Cashfree API,不包含旧版浏览器接口中的 CashFree.init()、paySeamless() 和 initPopup()。

示例代码中还有几个与 SDK 引入无关的问题:

  • onClick={Pay()} 会在渲染阶段立即执行,应改为传入函数。
  • postPaymentCallback() 会立即调用回调,应传入 postPaymentCallback。
  • 调用 pc() 时没有传入 data。
  • useEffect 必须写在 React 组件顶层,不能放进普通的点击处理函数。

正确加载浏览器 SDK

在 React 项目的 public/index.html 中,加入 Seamless Basic 文档指定的浏览器脚本:

<body>
  <div id="root"></div>

  <!-- 使用与你当前 Seamless Basic 文档版本完全对应的 SDK 地址 -->
  <script src="CASHFREE_SEAMLESS_BASIC_SDK_URL"></script>
</body>

不要把它替换成名称相似的 npm 服务端包。SDK 地址必须来自同一套 Seamless Basic 文档,因为不同代际的 Cashfree SDK 使用不同的初始化方法。

脚本不要使用会导致执行顺序不确定的 async,否则 React 组件挂载时,window.CashFree 可能还没有创建。如果必须异步加载,应等脚本触发 load 事件后再初始化。

React 组件示例

下面按旧版 Seamless Basic API 的调用方式整理。这里假设 paymentData 已包含订单信息,以及 Django 后端返回的签名字段:

import { useCallback, useEffect, useRef, useState } from "react";

function CashfreePayment({ paymentData }) {
  const cashfreeRef = useRef(null);
  const [ready, setReady] = useState(false);

  useEffect(() => {
    const cashfree = window.CashFree;

    if (!cashfree) {
      console.error(
        "Cashfree browser SDK is not loaded. Check the script tag in public/index.html."
      );
      return;
    }

    if (typeof cashfree.init !== "function") {
      console.error(
        "The loaded Cashfree SDK does not support the Seamless Basic init() API."
      );
      return;
    }

    const config = {
      layout: {},
      checkout: "transparent",
      mode: "TEST",
    };

    const response = cashfree.init(config);

    if (response?.status !== "OK") {
      console.error("Cashfree initialization failed:", response);
      return;
    }

    cashfreeRef.current = cashfree;
    setReady(true);
  }, []);

  const postPaymentCallback = useCallback((event) => {
    console.log("Cashfree payment event:", event);

    if (event.name === "PAYMENT_RESPONSE") {
      if (event.status === "SUCCESS") {
        // 只把它作为客户端提示。
        // 最终支付状态必须由 Django 后端向 Cashfree 查询或验签确认。
      } else if (event.status === "CANCELLED") {
        // 用户取消支付
      } else if (event.status === "FAILED") {
        // 支付失败
      }
    } else if (event.name === "VALIDATION_ERROR") {
      // 支付参数校验失败
    }
  }, []);

  const payCard = () => {
    const cashfree = cashfreeRef.current;

    if (!cashfree) {
      console.error("Cashfree SDK is not ready.");
      return;
    }

    if (!paymentData) {
      console.error("Payment data is missing.");
      return;
    }

    const data = {
      ...paymentData,
      paymentOption: "card",
      card: {
        number: "4444333322221111",
        expiryMonth: "07",
        expiryYear: "23",
        cvv: "123",
      },
    };

    cashfree.initPopup();
    cashfree.paySeamless(data, postPaymentCallback);
  };

  return (
    <button type="button" onClick={payCard} disabled={!ready}>
      {ready ? "PAY" : "Loading payment SDK..."}
    </button>
  );
}

export default CashfreePayment;

其中最重要的是这行代码:

const cashfree = window.CashFree;

这样既能避免 ESLint 报错:

'CashFree' is not defined no-undef

也明确说明了该对象来自外部浏览器脚本,而不是当前 JavaScript 模块。

如果脚本需要动态加载

如果不能直接修改 public/index.html,可以在组件挂载后加载脚本,并在 onload 中初始化:

import { useEffect, useState } from "react";

function CashfreeLoader() {
  const [cashfree, setCashfree] = useState(null);

  useEffect(() => {
    if (window.CashFree) {
      setCashfree(window.CashFree);
      return;
    }

    const script = document.createElement("script");
    script.src = "CASHFREE_SEAMLESS_BASIC_SDK_URL";
    script.onload = () => {
      if (
        window.CashFree &&
        typeof window.CashFree.init === "function"
      ) {
        setCashfree(window.CashFree);
      } else {
        console.error("Loaded script does not expose CashFree.init().");
      }
    };
    script.onerror = () => {
      console.error("Failed to load the Cashfree browser SDK.");
    };

    document.body.appendChild(script);
  }, []);

  // cashfree 可用后再调用 cashfree.init(...)
  return null;
}

在生产项目中,不要让多个组件重复插入同一个脚本。

为什么会出现 CashFree.init() is not a function

如果 window.CashFree 已经存在,但 init 不是函数,通常说明页面加载了版本不对或用途不同的 SDK。可以先在浏览器控制台检查:

console.log(window.CashFree);
console.log(typeof window.CashFree?.init);

然后逐项确认:

  1. script src 是否来自 Seamless Basic 对应的文档。
  2. 页面是否同时加载了多个 Cashfree SDK。
  3. 是否把服务端 npm SDK 误当成了浏览器 SDK。
  4. 当前文档是否属于新版 Checkout,而代码使用的却是旧版 Seamless Basic API。
  5. SDK 请求是否被 CSP、广告拦截器或网络错误阻止。

新版 Cashfree Web Checkout 和旧版 Seamless Basic 的初始化接口可能完全不同,不能把新版脚本与 CashFree.init()、paySeamless() 混在一起使用。如果 Cashfree 已不再向新商户提供这套旧接口,应改用当前控制台和官方文档对应的 Web Checkout 流程。

原代码还需要修正的地方

事件处理器不能这样写:

<button onClick={Pay()}>PAY</button>

这种写法会在组件渲染时执行 Pay(),应改为:

<button onClick={payCard}>PAY</button>

回调函数也不能提前调用:

CashFree.paySeamless(data, postPaymentCallback());

应传递函数本身:

CashFree.paySeamless(data, postPaymentCallback);

调用 pc 时同样要传入数据:

pc(paymentData);

也可以像前面的完整示例一样,直接在 payCard 中构造支付参数。

安全注意事项

签名必须由 Django 后端生成。Cashfree 的 Secret Key 不能出现在 React 代码、构建变量或浏览器网络响应中。订单金额、订单状态和支付成功结果也不能只依赖前端回调,后端需要按照当前 Cashfree API 的要求验签,或主动查询支付状态。

示例银行卡号只能用于 Cashfree 测试环境。直接采集卡号、CVV 等数据还涉及 PCI DSS 和 Cashfree 商户能力限制。正式接入前,需要确认当前账户是否仍支持 Seamless Basic,并核对应该使用的 SDK 版本和支付流程。

备注:内容仅供参考。