PAYATHON 2026

JusPay EC Headless:Android Kotlin 如何获取支付方式

支付老李

结论

HyperServiceHolder 初始化成功后,不会自动返回 Payment Methods、Saved Cards 或 Available UPI。应用还需要完成以下步骤:

  1. 由服务端创建有效的 Juspay 订单或支付会话;
  2. 获取接口返回的 Headless SDK process/fetch payload;
  3. 使用已经完成初始化的同一个 HyperServiceHolder 实例调用 process(activity, payload);
  4. 在初始化时注册的 hyperService callback 中处理异步结果。

不要将文档里的示例 payload 直接用于真实请求。Saved Cards、UPI 等数据依赖实际的商户、客户、订单、认证令牌和环境信息,示例值通常无法产生有效结果。

正确的调用流程

调用顺序如下:

服务端创建订单或支付会话
        ↓
Android 初始化 HyperServiceHolder
        ↓
收到 initiate_result 成功事件
        ↓
Android 获取服务端返回的 fetch/process payload
        ↓
调用同一个 HyperServiceHolder.process(...)
        ↓
在同一个 callback 中处理 process_result

需要特别注意,process() 的结果不会通过方法返回值提供,而是异步发送到初始化阶段注册的 callback。

Kotlin 调用示例

下面的代码展示了完整的调用结构。不同 EC Headless SDK 版本的 callback 类型、字段名称和 process 方法签名可能略有不同,请以项目当前依赖版本提供的接口为准。

private lateinit var hyperServiceHolder: HyperServiceHolder

private val hyperPaymentsCallback =
    object : HyperPaymentsCallbackAdapter() {

        override fun onEvent(
            event: JSONObject,
            responseHandler: HyperPaymentsResponseHandler
        ) {
            Log.d("JuspayEC", "SDK event: $event")

            val eventName = event.optString("event")
            val payload = event.optJSONObject("payload")

            when (eventName) {
                "initiate_result" -> {
                    val status = payload?.optString("status")

                    if (status.equals("SUCCESS", ignoreCase = true)) {
                        // 初始化已完成。
                        // 此时可以获取服务端生成的 fetch/process payload,
                        // 然后调用 fetchPaymentMethods()。
                    } else {
                        Log.e("JuspayEC", "Initialization failed: $event")
                    }
                }

                "process_result" -> {
                    handleFetchResult(payload, event)
                }

                else -> {
                    // 不要只过滤某一个事件。调试阶段应记录 SDK 返回的全部事件。
                    Log.d("JuspayEC", "Unhandled SDK event: $eventName")
                }
            }
        }
    }

执行 fetch/process:

private fun fetchPaymentMethods(
    activity: FragmentActivity,
    processPayload: JSONObject
) {
    if (!::hyperServiceHolder.isInitialized) {
        Log.e("JuspayEC", "HyperServiceHolder is not initialized")
        return
    }

    Log.d("JuspayEC", "Calling process: $processPayload")

    try {
        hyperServiceHolder.process(activity, processPayload)
    } catch (error: Exception) {
        Log.e("JuspayEC", "Unable to call Juspay process", error)
    }
}

部分 SDK 封装使用的是以下签名:

hyperServiceHolder.process(processPayload)

是否需要传入 Activity,取决于当前使用的 HyperServiceHolder 实现。不要参照其他版本的示例自行修改方法签名。

process payload 应从哪里获取

完整 payload 应由服务端根据当前商户配置、客户和订单动态生成,再交给 Android。客户端应尽量原样转交,不要自行拼接认证字段。

典型的外层结构类似:

{
  "requestId": "unique-request-id",
  "service": "in.juspay.ec",
  "payload": {
    "action": "<当前版本规定的 fetch action>",
    "orderId": "<real-order-id>",
    "merchantId": "<merchant-id>",
    "clientId": "<client-id>",
    "customerId": "<customer-id>",
    "clientAuthToken": "<token-returned-by-server>"
  }
}

这只是结构示意,并不表示所有字段都适用于当前 SDK 版本。以下内容尤其不能根据示例猜测:

  • action 的具体取值;
  • service 的具体取值;
  • clientAuthToken、orderId 等认证或会话字段;
  • payload 是否还需要 environment、customerMobile 等参数。

如果 Juspay 服务端接口已经返回完整的 sdk_payload 或 processPayload,Android 应直接使用该对象:

val processPayload = JSONObject(serverResponse.getString("sdk_payload"))
hyperServiceHolder.process(activity, processPayload)

如果 sdk_payload 只是外层对象中的 payload 部分,则需要按照当前文档补充 requestId 和 service。使用前应先确认服务端响应结构,避免出现重复包装:

{
  "requestId": "...",
  "service": "...",
  "payload": {
    "requestId": "...",
    "service": "...",
    "payload": {}
  }
}

这种重复嵌套通常无法得到预期结果。

如何读取支付方式数据

收到 process_result 后,先检查状态,再读取返回数据。具体字段路径取决于 SDK 版本和 fetch action,不能假定所有版本都会直接返回 paymentMethods。

private fun handleFetchResult(
    payload: JSONObject?,
    rawEvent: JSONObject
) {
    if (payload == null) {
        Log.e("JuspayEC", "process_result does not contain payload: $rawEvent")
        return
    }

    val status = payload.optString("status")

    if (!status.equals("SUCCESS", ignoreCase = true)) {
        val errorCode = payload.optString("errorCode")
        val errorMessage = payload.optString("errorMessage")

        Log.e(
            "JuspayEC",
            "Fetch failed: code=$errorCode, message=$errorMessage, event=$rawEvent"
        )
        return
    }

    // 字段名称必须根据实际 process_result 解析。
    val paymentMethods = payload.optJSONArray("paymentMethods")
    val savedCards = payload.optJSONArray("savedCards")
    val availableUpi = payload.optJSONArray("availableUpi")

    Log.d("JuspayEC", "paymentMethods=$paymentMethods")
    Log.d("JuspayEC", "savedCards=$savedCards")
    Log.d("JuspayEC", "availableUpi=$availableUpi")
}

如果这些字段是 null,不一定说明 SDK 没有返回数据。它们可能位于更深层的 data、result 或其他对象中。调试时应先记录完整的 process_result,再根据实际 JSON 结构建立数据模型。

为什么 Saved Cards 或 UPI 可能为空

Saved Cards 通常与已识别的客户绑定。即使 fetch 调用成功,以下情况仍可能返回空列表:

  • 没有传入稳定且有效的 customerId;
  • 创建会话时没有完成客户认证;
  • 当前客户没有保存过银行卡;
  • 创建订单和初始化 SDK 时使用了不同环境;
  • 商户后台没有启用相关支付方式;
  • 当前订单的币种、金额或商户配置不支持该支付方式。

Available UPI 也不是固定列表。返回内容可能受到设备环境、已安装的 UPI 应用、商户配置、订单参数和 SDK 能力检测结果的影响。

process 没有 callback 时重点检查

如果调用 process() 后完全没有收到事件,请依次检查:

  1. 是否在收到初始化成功事件后才调用;
  2. 是否始终复用同一个 HyperServiceHolder 实例;
  3. callback 是否在初始化时正确注册,并且仍然存活;
  4. 传入的是否为当前版本要求的完整外层 payload;
  5. service 是否与初始化时一致;
  6. requestId 是否非空,并且每次请求都唯一;
  7. 订单、客户和认证令牌是否来自同一环境;
  8. 调用时传入的 Activity 是否处于有效生命周期;
  9. 是否只监听了初始化事件,忽略了 process_result 或错误事件;
  10. 是否在解析 JSON 前就因字段缺失抛出了异常。

调试阶段不要只记录成功分支,应打印 callback 收到的完整 JSON:

override fun onEvent(
    event: JSONObject,
    responseHandler: HyperPaymentsResponseHandler
) {
    Log.d("JuspayEC", event.toString(2))
}

日志中可能包含订单或客户的敏感信息。正式环境必须对这些信息脱敏,并且不要记录认证令牌和完整卡片信息。

如果确认上述流程无误,但仍然没有收到任何 callback,需要将当前 SDK 版本、初始化 payload、脱敏后的 process payload 和完整 SDK 日志提供给 Juspay 支持团队确认。EC Headless 的 fetch action 和响应字段可能因集成方案或版本而异,仅凭通用 sample payload 无法可靠判断。

备注:内容仅供参考。