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

