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