PAYATHON 2026

CFSDK payment gateway 报错 Invalid token sent in request

支付小周

结论

这段 CFViewController 初始化代码本身没有明显的结构问题。“Invalid token sent in request”通常与 WebView 或 UINavigationController 无关,更可能是传入的 tokenData 与当前支付环境、商户账号或订单信息不匹配。

失败地址是 https://test.cashfree.com/...,说明 SDK 运行在测试环境。对应的 token 也必须使用同一个 Cashfree 测试账号和测试环境凭据生成,并且生成 token 时使用的 orderId 要与客户端传入的值完全一致。生产环境的 token、其他 App ID 生成的 token,以及重复订单对应的旧 token,都会被拒绝。

返回页面无法关闭是另一个问题。CFViewController 是新建导航控制器的根节点,如果 SDK 调用 popViewController,导航栈中没有上一层页面,因此页面不会自动 dismiss。

排查 Invalid token

先在后端逐项核对:

  • SDK 使用测试环境时,后端也必须调用 Cashfree 的测试环境接口。
  • 生成 token 使用的 Client ID 和 Client Secret,必须与传入 SDK 的 appId 属于同一个账号。
  • 生成 token 时使用的 orderId 必须与 params["orderId"] 完全一致,包括大小写和空格。
  • 如果 token 接口还接收 orderAmount 和 orderCurrency,这些值也必须与 SDK 参数一致。
  • tokenData 只能包含接口响应中的 token 字符串,不能传入完整 JSON、带引号的 JSON 字符串或经过 Base64 编码的响应。
  • 不要再次对 token 进行 URL 编码、转义或拼接。
  • 检查后端响应和本地保存过程,去掉意外加入的换行符及首尾空格。
  • 不要复用其他订单的 token。只要修改了 orderId,就应重新生成 token。
  • 确认 token 创建请求已经成功,避免把错误响应中的空值或错误信息当作 token 返回给客户端。

客户端也可以先做基本校验:

func getPaymentParams() -> [String: String] {
    let orderId = self.orderId.trimmingCharacters(in: .whitespacesAndNewlines)
    let token = self.paymentToken.trimmingCharacters(in: .whitespacesAndNewlines)

    precondition(!orderId.isEmpty, "orderId is empty")
    precondition(!token.isEmpty, "paymentToken is empty")

    return [
        "orderId": orderId,
        "tokenData": token,
        "orderAmount": self.paymentValue,
        "customerName": "name",
        "orderNote": "health products",
        "orderCurrency": "INR",
        "customerPhone": "9876543210",
        "customerEmail": "abc@gmail.com",
        "notifyUrl": "https://your-domain.example/payment/cashfree/notify"
    ]
}

不要在日志中打印完整 token。调试时记录 token 长度和少量前缀即可:

let token = paymentToken.trimmingCharacters(in: .whitespacesAndNewlines)

print("Environment:", environmentCF)
print("App ID:", appId)
print("Order ID:", orderId)
print("Token length:", token.count)
print("Token prefix:", String(token.prefix(6)))

还要重点检查后端返回值的解析方式。如果 token 接口返回 JSON,应从中提取真正的 token 字段,不能把整个响应直接传给 SDK:

// 错误示例:把完整 JSON 当作 token
paymentToken = responseString

正确做法是先解析 JSON,再读取对应字段。旧版 Cashfree 接口使用的字段名可能是 cftoken,具体应以后端实际收到的响应为准:

struct TokenResponse: Decodable {
    let cftoken: String
}

如果环境、App ID 和订单参数均一致,可以在后端记录一次脱敏后的 token 创建请求及响应状态。客户端收到“Invalid token”时,需要逐项对照以下内容:

token 创建环境
        +
创建 token 的商户凭据
        +
SDK 中的 appId
        +
SDK environment
        +
orderId
        +
orderAmount / currency

notifyUrl 配置错误通常会影响支付结果通知,一般不会导致提交页面立即返回“Invalid token”。

正确处理返回操作

方案一:推入现有导航栈

如果当前页面已经在 UINavigationController 中,优先直接 push:

func initiateCFSDK() {
    let cashfreeVC = CFViewController(
        params: getPaymentParams(),
        appId: self.appId,
        env: self.environmentCF,
        callBack: self
    )

    self.navigationController?.pushViewController(
        cashfreeVC,
        animated: true
    )
}

这样一来,SDK 或用户执行返回操作时,popViewController 才能回到上一层页面。

方案二:模态展示并自行提供关闭按钮

如果必须使用 present,应由外层页面调用 dismiss:

func initiateCFSDK() {
    let cashfreeVC = CFViewController(
        params: getPaymentParams(),
        appId: self.appId,
        env: self.environmentCF,
        callBack: self
    )

    cashfreeVC.navigationItem.leftBarButtonItem = UIBarButtonItem(
        barButtonSystemItem: .close,
        target: self,
        action: #selector(closeCashfreeCheckout)
    )

    let navVC = UINavigationController(rootViewController: cashfreeVC)
    navVC.modalPresentationStyle = .fullScreen

    present(navVC, animated: true)
}

@objc private func closeCashfreeCheckout() {
    presentedViewController?.dismiss(animated: true)
}

支付成功、失败或用户取消后,也可以在 SDK delegate 回调中关闭页面:

private func closeCheckout() {
    if presentedViewController != nil {
        dismiss(animated: true)
    } else {
        navigationController?.popViewController(animated: true)
    }
}

具体的 delegate 方法名取决于项目使用的 Cashfree SDK 版本。应在该版本提供的成功、失败或取消回调中调用 closeCheckout()。

其他注意事项

不能只根据客户端 WebView 回调认定支付成功。客户端回调可能中断或被伪造,最终订单状态应由后端通过 Cashfree 的订单查询接口或签名校验来确认。

示例中的 CFViewController、tokenData 和 cftoken 也可能属于旧版集成方式。如果项目使用较新的 Cashfree SDK,而后端仍通过旧接口生成 token,或者客户端使用旧 SDK、后端却采用新版接口,同样可能出现凭据无法识别的问题。iOS SDK、后端创建订单接口和客户端参数必须属于同一套 API 版本,不能混用旧版 token 与新版 payment session。

备注:内容仅供参考。