You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

安卓PayUCheckout Pro集成报错:无法将字符串转为JSONObject

PayU Checkout Pro 调用支付触发onError:String转JSONObject失败

问题详情

已从官网获取MerchantID和SaltKey,在build.gradle中引入依赖:

implementation 'in.payu:payu-checkout-pro:2.0.23'

调用支付时触发onError方法,错误日志:

E/ErrorMessage: Value <!DOCTYPE of type java.lang.String cannot be converted to JSONObject

尝试过本地生成Hash,也试过服务端生成,相关Kotlin代码如下:

btnPay.setOnClickListener {

    val additionalParamsMap: HashMap<String, Any?> = HashMap()
    additionalParamsMap[PayUCheckoutProConstants.CP_UDF1] = "udf1"
    additionalParamsMap[PayUCheckoutProConstants.CP_UDF2] = "udf2"
    additionalParamsMap[PayUCheckoutProConstants.CP_UDF3] = "udf3"
    additionalParamsMap[PayUCheckoutProConstants.CP_UDF4] = "udf4"
    additionalParamsMap[PayUCheckoutProConstants.CP_UDF5] = "udf5"
    additionalParamsMap[PayUCheckoutProConstants.SODEXO_SOURCE_ID] = "srcid123"

    val payUPaymentParams = PayUPaymentParams.Builder()
        .setAmount("10")
        .setIsProduction(true)
        .setKey("Key")
        .setProductInfo("Macbook Pro")
        .setPhone("9874563210")
        .setTransactionId(System.currentTimeMillis().toString())
        .setFirstName("firstname")
        .setEmail("email")
        .setSurl("https://payuresponse.firebaseapp.com/success")
        .setFurl("https://payuresponse.firebaseapp.com/failure")
        .setUserCredential("key:email")
        .setAdditionalParams(additionalParamsMap)
        .build()

    PayUCheckoutPro.open(
        this, payUPaymentParams,
        object : PayUCheckoutProListener {

            override fun onPaymentSuccess(response: Any) {
                response as HashMap<*, *>
                val payUResponse = response[PayUCheckoutProConstants.CP_PAYU_RESPONSE]
                val merchantResponse =
                    response[PayUCheckoutProConstants.CP_MERCHANT_RESPONSE]
            }

            override fun onPaymentFailure(response: Any) {
                response as HashMap<*, *>
                val payUResponse = response[PayUCheckoutProConstants.CP_PAYU_RESPONSE]
                val merchantResponse =
                    response[PayUCheckoutProConstants.CP_MERCHANT_RESPONSE]
            }

            override fun onPaymentCancel(isTxnInitiated: Boolean) {
            }

            override fun onError(errorResponse: ErrorResponse) {
                val errorMessage: String
                if (errorResponse != null && errorResponse.errorMessage != null
                    && errorResponse.errorMessage!!.isNotEmpty()
                )
                    errorMessage = errorResponse.errorMessage!!
                else
                    errorMessage = "Error Message"

                Log.e("ErrorMessage",""+errorResponse.errorMessage);
            }

            override fun setWebViewProperties(webView: WebView?, bank: Any?) {
                //For setting webview properties, if any.
            // Check Customized Integration section for more details on this
            }

            override fun generateHash(
                valueMap: HashMap<String, String?>,
                hashGenerationListener: PayUHashGenerationListener
            ) {
                if (valueMap.containsKey(CP_HASH_STRING) &&
                    valueMap.containsKey(CP_HASH_NAME)
                ) {

                    val hashData = valueMap[CP_HASH_STRING]
                    val hashName = valueMap[CP_HASH_NAME]

                    val hashString: String? = HashGenerationUtils.generateHashFromSDK(
                        hashData.toString(),
                        "Saltkey"
                    )

                    //Do not generate hash from local,
                    // it needs to be calculated from server side only. Here,
                    // hashString contains hash created from your server side.
                    val hash: String? = hashString

                    if (!TextUtils.isEmpty(hash)) {
                        val dataMap: HashMap<String, String?> = HashMap()
                        dataMap[hashName!!] = hash!!
                        hashGenerationListener.onHashGenerated(dataMap)
                    }
                }
            }
        })
}

排查与解决方案

这个错误的核心是SDK尝试解析HTML内容(以<!DOCTYPE开头)为JSONObject,说明请求返回的不是预期的JSON响应,而是错误页或重定向页。按以下步骤排查:

1. 校验环境与密钥配置

  • 确认setIsProduction(true)是否匹配当前环境:如果使用测试密钥,必须设置为false;生产环境才用true。错误的环境配置会导致请求到错误的PayU服务器,返回HTML错误页。
  • 确保setKey("Key")中的Key和HashGenerationUtils中使用的SaltKey是对应环境(测试/生产)的配对密钥,不能混用。

2. 修正非法参数

  • setEmail("email")是无效邮箱格式,必须改为合法邮箱(如test@example.com),PayU会拒绝非法参数并返回错误页。
  • 检查手机号9874563210是否符合PayU的格式要求(必须是有效10位数字)。

3. 确保Hash生成正确

  • 本地测试:使用HashGenerationUtils.generateHashFromSDK时,确保hashData是PayU要求的正确拼接字符串,且SaltKey无误。
  • 服务端生成:服务端生成Hash时,必须严格使用和客户端完全一致的参数(amount、txnId、productInfo等内容/大小写完全匹配),并使用正确的SaltKey生成SHA-512 Hash。

4. 检查回调地址可用性

  • 确认surl和furl是公开可访问的地址,且能返回PayU要求的响应格式。如果地址不可达、返回HTML或被拦截,会导致SDK解析失败。

修正后的关键代码示例

// 修正环境与参数
val payUPaymentParams = PayUPaymentParams.Builder()
    .setAmount("10")
    .setIsProduction(false) // 测试环境设为false,生产环境改为true
    .setKey("YOUR_TEST_KEY") // 替换为实际测试/生产Key
    .setProductInfo("Macbook Pro")
    .setPhone("9874563210")
    .setTransactionId(System.currentTimeMillis().toString())
    .setFirstName("firstname")
    .setEmail("test@example.com") // 修正为合法邮箱
    .setSurl("https://your-public-success-url.com") // 替换为实际可访问的回调地址
    .setFurl("https://your-public-failure-url.com") // 替换为实际可访问的回调地址
    .setUserCredential("YOUR_TEST_KEY:test@example.com") // 匹配Key和邮箱
    .setAdditionalParams(additionalParamsMap)
    .build()

// 服务端Hash生成示例(替换本地生成逻辑)
override fun generateHash(
    valueMap: HashMap<String, String?>,
    hashGenerationListener: PayUHashGenerationListener
) {
    if (valueMap.containsKey(CP_HASH_STRING) && valueMap.containsKey(CP_HASH_NAME)) {
        // 调用你的服务端接口获取Hash
        val hashData = valueMap[CP_HASH_STRING]
        val hashName = valueMap[CP_HASH_NAME]
        
        // 这里替换为服务端请求逻辑,比如用Retrofit获取Hash
        yourApiService.getPayUHash(hashData!!)
            .enqueue(object : Callback<String> {
                override fun onResponse(call: Call<String>, response: Response<String>) {
                    val hash = response.body()
                    if (!TextUtils.isEmpty(hash)) {
                        val dataMap = HashMap<String, String?>()
                        dataMap[hashName!!] = hash!!
                        hashGenerationListener.onHashGenerated(dataMap)
                    }
                }

                override fun onFailure(call: Call<String>, t: Throwable) {
                    hashGenerationListener.onHashGenerationFailed(t.message)
                }
            })
    }
}

内容的提问来源于stack exchange,提问作者prabhakaran

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 06:44:58