androidstripe-payments

Change Connection Token/ switch account in Stripe Terminal


We are using more than 1 stripe account in the application and when I'm trying to switch accounts in the app and before calling Terminal.initTerminal() I'm doing :

Terminal.getInstance().clearCachedCredentials(); and I an disconnecting the reader It is not possible to initialise the terminal, when i try to Terminal.initTerminal() then it shows exception/error:

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. Don't know if this is an issue or if stripe does not let us change accounts within the app.

I would like to know is there any way to update connection token. If someone knows then please let me know. I have spent a lot of time on it. Thank you

I have see it: https://github.com/stripe/stripe-terminal-android/issues/88. but i am not able to solve issue

My code =

 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")
        }
    }
    
    
    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}"))
            }
        })
    }

}

Solution

  • 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:

    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:

        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
                        )
                    }          
            }
        }