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);
然后逐项确认:
script src是否来自 Seamless Basic 对应的文档。- 页面是否同时加载了多个 Cashfree SDK。
- 是否把服务端 npm SDK 误当成了浏览器 SDK。
- 当前文档是否属于新版 Checkout,而代码使用的却是旧版 Seamless Basic API。
- 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 版本和支付流程。
备注:内容仅供参考。
