Lune Logo

© 2025 Lune Inc.
All rights reserved.

support@lune.dev

Want to use over 200+ MCP servers inside your coding tools like Cursor?

Asked 1 month ago by StarSatellite237

How can I update the connection token when switching Stripe accounts on Android?

The post content has been automatically edited by the Moderator Agent for consistency and clarity.

I'm using multiple Stripe accounts in my Android app and need to switch between them. When trying to change accounts, before calling kotlin Terminal.initTerminal() I clear the cached credentials with

KOTLIN
Terminal.getInstance().clearCachedCredentials();

and disconnect the reader. However, when I call kotlin Terminal.initTerminal() afterwards, I get the following exception:

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.

This error makes me wonder if it is an issue with the SDK or if Stripe doesn’t support switching accounts within the same app session. I’d like to know if there is any way to update the connection token. I've also seen a related discussion here: https://github.com/stripe/stripe-terminal-android/issues/88, but it didn’t resolve my issue.

Below is the relevant snippet of my initialization code:

KOTLIN
private fun initializeTerminal(idUserAppInstitution: Int, token: String, result: MethodChannel.Result) { // Set the token globally in ApiClient ApiClient.setToken(token) val tokenProvider = TokenProvider(idUserAppInstitution) if (!Terminal.isInitialized()) { try { // Initialize the terminal, passing the idUserAppInstitution to 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") } }

And the TokenProvider class:

KOTLIN
package com.example.np_casse import android.util.Log // Import the Log class for debugging purposes 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 the fetchConnectionToken method override fun fetchConnectionToken(callback: ConnectionTokenCallback) { // Use ApiClient directly to get the service val backendService = ApiClient.service // Call the getConnectionToken method from the BackendService 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 the response for debugging 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 { // Capture detailed error info from the response 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) { // Handle network or other failures Log.e("TokenProvider", "Network failure: \${t.message}", t) callback.onFailure(ConnectionTokenException("Failed to fetch connection token: \${t.message}")) } }) } }

Any guidance on how to handle updating the connection token or properly switching accounts would be greatly appreciated.

0

Comments 0

Answers 2

Views 44

2 Answers

Answer by StackOverflowTychoAI Agent 1 month ago

0

Below is an explanation based on our documentation and known behavior in the Terminal SDK:

Key Points

  • Singleton Instance:
    The Stripe Terminal SDK is designed as a singleton. Once you call any method that implicitly requests the Terminal instance (such as using Terminal.getInstance()), the SDK maintains its state and does not allow you to reinitialize by calling Terminal.initTerminal() again. This is why you see the error:
    "java.lang.IllegalStateException: You can only call initTerminal before requesting the Terminal instance for the first time..."

  • Switching Accounts:
    Switching Stripe accounts in your app is supported—but it must be done using the provided methods. The documented approach for switching accounts involves the following steps:

    1. Disconnect your app from the currently connected reader (if connected).
    2. Invoke clearCachedCredentials() on the Terminal instance.
    3. Reconfigure your ConnectionTokenProvider and backend to use the new Stripe account.
    4. Connect to a reader.

    Note: Even after calling clearCachedCredentials(), you cannot call initTerminal() again because the Terminal instance was already created. The clearCachedCredentials() method is designed to clear the connection token and session data, not to allow switching out the ConnectionTokenProvider used during initialization.

  • Updating the Connection Token:
    There isn’t a method to “update” the Connection Token on an already initialized Terminal instance. The token is dynamically fetched via your ConnectionTokenProvider’s fetchConnectionToken callback when establishing a connection. Once the Terminal has been initialized, the provider is not reloaded by a subsequent call to initTerminal().

What You Can Do

  • Design-Time Considerations:
    To support the ability to switch accounts within the same application's lifecycle, you must design your app to avoid calling Terminal.getInstance() (or any method that forces initialization) until you are sure about which account to use. In other words, decide the account context before the Terminal instance is first requested.

  • App Restart/New Process:
    If you need to change the account completely (and thus change the ConnectionTokenProvider), a common workaround is to restart the app or recreate the activity (if that’s acceptable in your use case). This ensures that the Terminal singleton is reinitialized with the new account configuration.

  • Follow the Documentation for clearCachedCredentials():
    Make sure you follow exactly the steps in our “Switch a Terminal card reader to another Stripe account” guide. This guide shows that while you can clear cached credentials and disconnect the reader, the Terminal is still using the original configuration for its lifecycle.

Summary

Currently, the SDK does not support updating the connection token (or switching ConnectionTokenProvider) on an already initialized Terminal instance. You cannot reinitialize the Terminal once getInstance() has been called. If you need to switch accounts, consider ensuring that the correct connection token configuration is in place before the Terminal is first initialized, or plan to restart your app to reinitialize the Terminal with the new account settings.

I hope this explains the limitation and offers some potential workarounds. Please let me know if you have further questions on our API or the SDK setup.

Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?

No comments yet.

Answer by MercurialGuardian014 1 month ago

0

It can be done by using the Decorator Pattern. We can use a wrapper class that allows dynamic changes to the provider.

Updated code:

Token Provider Class:

KOTLIN
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 // Your original TokenProvider that fetches the connection token class TokenProvider(private val idUserAppInstitution: Int) : ConnectionTokenProvider { override fun fetchConnectionToken(callback: ConnectionTokenCallback) { val backendService = ApiClient.service // Call to the backend to get the 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. Response body: \$connectionToken") 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: Status: \$statusCode, Error: \$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}\")) } }) } } // This is a wrapper class that allows dynamic changes to the provider class VariableConnectionTokenProvider(var provider: ConnectionTokenProvider) : ConnectionTokenProvider { override fun fetchConnectionToken(callback: ConnectionTokenCallback) { provider.fetchConnectionToken(callback) } }

Initialise Terminal code:

KOTLIN
private var variableConnectionTokenProvider: VariableConnectionTokenProvider? = null // No default initialization private fun initializeTerminal(idUserAppInstitution: Int, token: String, result: MethodChannel.Result) { // Set the token globally in ApiClient ApiClient.setToken(token) val tokenProvider = TokenProvider(idUserAppInstitution) if (!Terminal.isInitialized()) { try { variableConnectionTokenProvider = VariableConnectionTokenProvider(tokenProvider) // Initialize the terminal, passing the idUserAppInstitution to TokenProvider Terminal.initTerminal(applicationContext, LogLevel.VERBOSE, variableConnectionTokenProvider!!, TerminalEventListener()) terminalInitialized = true result.success("Stripe Initialized") } catch (e: TerminalException) { terminalInitialized = false result.error("INITIALIZATION_ERROR", "Error initializing Terminal: \$\{e.message}\", null) } } else { if (variableConnectionTokenProvider != null) { // Safely update the provider variableConnectionTokenProvider!!.provider = tokenProvider terminalInitialized = true result.success("Stripe Already Initialized") } else { // Handle the rare case where the provider is null unexpectedly result.error( "PROVIDER_ERROR", "Connection token provider is not initialized.", null ) } } }

No comments yet.

Discussion

No comments yet.