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

Stripe Terminal切换账户初始化失败,如何更新Connection Token?

问题:Stripe Terminal多账户切换异常与Connection Token更新方法疑问

我们的应用需使用多个Stripe账户,切换账户时先调用Terminal.getInstance().clearCachedCredentials()并断开读卡器,之后调用Terminal.initTerminal()触发如下异常:

java.lang.IllegalStateException: You can only call initTerminal before requesting the Terminal instance for the first time. If you are trying to switch accounts in your app, refer to the documentation for the clearCachedCredentials method.

不确定这是Bug还是Stripe本身不允许应用内切换账户,想了解是否有更新Connection Token的方法,已查看相关Issue但未解决。

相关代码如下:

初始化Terminal的方法

private fun initializeTerminal(idUserAppInstitution: Int, token: String, result: MethodChannel.Result) {
    // 全局设置Token到ApiClient
    ApiClient.setToken(token)
    val tokenProvider = TokenProvider(idUserAppInstitution)
    if (!Terminal.isInitialized()) {
        try {
            // 初始化Terminal,将idUserAppInstitution传入TokenProvider
            Terminal.initTerminal(applicationContext, LogLevel.VERBOSE, tokenProvider, TerminalEventListener())
            terminalInitialized = true
            result.success("Stripe Initialized")
        } catch (e: TerminalException) {
            terminalInitialized = false
            result.error("INITIALIZATION_ERROR", "Error initializing Terminal: ${e.message}", null)
        }
    } else { 
        terminalInitialized = true
        result.success("Stripe Already Initialized")
    }
}

TokenProvider类实现

package com.example.np_casse

import android.util.Log
import com.stripe.stripeterminal.external.callable.ConnectionTokenCallback
import com.stripe.stripeterminal.external.callable.ConnectionTokenProvider
import com.stripe.stripeterminal.external.models.ConnectionTokenException
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response

class TokenProvider(private val idUserAppInstitution: Int) : ConnectionTokenProvider {
    override fun fetchConnectionToken(callback: ConnectionTokenCallback) {
        val backendService = ApiClient.service

        backendService.getConnectionToken(idUserAppInstitution).enqueue(object : Callback<ConnectionToken> {
            override fun onResponse(call: Call<ConnectionToken>, response: Response<ConnectionToken>) {
                if (response.isSuccessful) {
                    val connectionToken = response.body()
                    if (connectionToken != null && connectionToken.secret != null) {
                        Log.d("TokenProvider", "Successfully fetched connection token: ${connectionToken.secret}")
                        callback.onSuccess(connectionToken.secret)
                    } else {
                        val errorMessage = "Response body or secret is null"
                        Log.e("TokenProvider", errorMessage)
                        callback.onFailure(ConnectionTokenException(errorMessage))
                    }
                } else {
                    val errorMessage = response.errorBody()?.string() ?: "Unknown error"
                    val statusCode = response.code()
                    Log.e("TokenProvider", "Error fetching connection token. Status: $statusCode, Error: $errorMessage")
                    callback.onFailure(ConnectionTokenException("Failed to fetch connection token: $errorMessage"))
                }
            }

            override fun onFailure(call: Call<ConnectionToken>, t: Throwable) {
                Log.e("TokenProvider", "Network failure: ${t.message}", t)
                callback.onFailure(ConnectionTokenException("Failed to fetch connection token: ${t.message}"))
            }
        })
    }
}

解决方案

Stripe Terminal的initTerminal方法仅允许在首次获取Terminal实例前调用一次,多次调用必然触发异常。应用内切换多账户无需重新初始化,正确流程如下:

1. 调整TokenProvider支持动态切换账户

修改TokenProvider,添加机构ID的setter方法,使其能动态更新目标账户标识:

class TokenProvider : ConnectionTokenProvider {
    private var idUserAppInstitution: Int = 0

    // 构造函数保留,用于首次初始化时设置初始ID
    constructor(initialId: Int) : this() {
        this.idUserAppInstitution = initialId
    }

    // 新增setter方法,切换账户时更新ID
    fun setInstitutionId(newId: Int) {
        this.idUserAppInstitution = newId
    }

    override fun fetchConnectionToken(callback: ConnectionTokenCallback) {
        val backendService = ApiClient.service
        // 使用当前的idUserAppInstitution请求对应账户的Connection Token
        backendService.getConnectionToken(idUserAppInstitution).enqueue(object : Callback<ConnectionToken> {
            // 原回调逻辑不变
            override fun onResponse(call: Call<ConnectionToken>, response: Response<ConnectionToken>) {
                if (response.isSuccessful) {
                    val connectionToken = response.body()
                    if (connectionToken != null && connectionToken.secret != null) {
                        Log.d("TokenProvider", "Successfully fetched connection token: ${connectionToken.secret}")
                        callback.onSuccess(connectionToken.secret)
                    } else {
                        val errorMessage = "Response body or secret is null"
                        Log.e("TokenProvider", errorMessage)
                        callback.onFailure(ConnectionTokenException(errorMessage))
                    }
                } else {
                    val errorMessage = response.errorBody()?.string() ?: "Unknown error"
                    val statusCode = response.code()
                    Log.e("TokenProvider", "Error fetching connection token. Status: $statusCode, Error: $errorMessage")
                    callback.onFailure(ConnectionTokenException("Failed to fetch connection token: $errorMessage"))
                }
            }

            override fun onFailure(call: Call<ConnectionToken>, t: Throwable) {
                Log.e("TokenProvider", "Network failure: ${t.message}", t)
                callback.onFailure(ConnectionTokenException("Failed to fetch connection token: ${t.message}"))
            }
        })
    }
}

2. 实现账户切换逻辑

新增专门的账户切换方法,跳过重复初始化,核心步骤为:断开读卡器 → 清除缓存凭证 → 更新TokenProvider的账户ID

private fun switchStripeAccount(idUserAppInstitution: Int, token: String, result: MethodChannel.Result) {
    // 更新全局API Token
    ApiClient.setToken(token)
    
    // 更新TokenProvider的目标账户ID
    (tokenProvider as? TokenProvider)?.setInstitutionId(idUserAppInstitution) ?: run {
        result.error("TOKEN_PROVIDER_ERROR", "TokenProvider实例无效", null)
        return
    }

    try {
        // 先断开当前连接的读卡器
        Terminal.getInstance().disconnectReader(object : ReaderCallback {
            override fun onSuccess(reader: Reader?) {
                // 清除旧账户的缓存凭证
                Terminal.getInstance().clearCachedCredentials()
                result.success("账户切换完成")
            }

            override fun onFailure(e: TerminalException) {
                result.error("DISCONNECT_FAILED", "断开读卡器失败: ${e.message}", null)
            }
        })
    } catch (e: Exception) {
        result.error("SWITCH_FAILED", "账户切换异常: ${e.message}", null)
    }
}

3. 保留原初始化逻辑仅用于首次启动

原initializeTerminal方法仅在应用首次启动时调用,后续切换账户不再执行初始化:

// 全局持有TokenProvider实例,避免重复创建
private lateinit var tokenProvider: TokenProvider

private fun initializeTerminal(idUserAppInstitution: Int, token: String, result: MethodChannel.Result) {
    ApiClient.setToken(token)
    tokenProvider = TokenProvider(idUserAppInstitution)
    if (!Terminal.isInitialized()) {
        try {
            Terminal.initTerminal(applicationContext, LogLevel.VERBOSE, tokenProvider, TerminalEventListener())
            terminalInitialized = true
            result.success("Stripe 初始化完成")
        } catch (e: TerminalException) {
            terminalInitialized = false
            result.error("INIT_ERROR", "初始化Terminal失败: ${e.message}", null)
        }
    } else {
        terminalInitialized = true
        result.success("Stripe 已初始化")
    }
}

关键注意事项

  • initTerminal是一次性操作,应用生命周期内只能调用一次
  • 切换账户必须先断开读卡器,再清除缓存,避免设备状态不一致
  • 切换后Terminal会自动调用TokenProvider获取新账户的Connection Token,无需手动触发更新

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 22:54:52