Unlock In-Person Payments: How to Integrate Stripe Terminal with Your Android App
In today’s fast-paced world, businesses need to offer seamless, secure payment solutions both online and in person. Stripe Terminal provides an easy way for developers to accept in-person payments using physical card readers and integrates smoothly with Android applications. Whether you’re building a mobile point-of-sale (mPOS) app or simply want to add in-person payment capabilities, Stripe Terminal offers a powerful, flexible solution.
In this guide, we’ll walk you through the steps to integrate Stripe Terminal with your Android app, from setting up your Stripe account and configuring your card reader to processing payments securely. By the end of this article, you’ll be equipped to start accepting card payments in your Android app with just a few lines of code!
Setting Up Your Stripe Account for Terminal Integration
Before you can integrate Stripe Terminal with your Android app, you need to set up a few things on your Stripe account. This involves configuring your account to accept in-person payments and ensuring you have the necessary tools and resources to work with the Stripe Terminal API. Here’s how to get started:
Create a Stripe Account
If you don’t already have a Stripe account, head to Stripe’s website and sign up for one. Stripe requires basic information such as your business details, bank account information for payouts, and tax information. Make sure your account is verified and fully activated before proceeding.
Enable Stripe Terminal in Your Account
Once your account is set up, you’ll need to enable Stripe Terminal for in-person payments. Here’s how:
- Log in to your Stripe Dashboard.
- Navigate to the “Terminal” section: In the Stripe Dashboard, click on the “Terminal” link in the left sidebar under the “Payments” category.
- Activate your Terminal Account: If it’s your first time using Terminal, you’ll be prompted to activate your Terminal account. This step is essential to start accepting in-person payments.
You may also be asked to provide additional details related to your business’s physical location, so ensure your account is set up with the appropriate business information.
Order a Stripe Card Reader
Stripe offers several card reader models compatible with Stripe Terminal, such as the BBPOS Chipper 2X BT and the Verifone P400. These card readers are essential for accepting payments physically.
Order a Card Reader: On the “Terminal” page in your Stripe Dashboard, you’ll find an option to order your desired reader. Stripe ships the device to your business address. Make sure to choose a card reader that best fits your use case.
Activate Your Card Reader: Once your reader arrives, you’ll need to connect it to your app. This typically involves pairing the reader via Bluetooth with your Android device, which we’ll cover in the integration steps later.
Obtain Your API Keys
Stripe uses API keys to authenticate requests between your app and Stripe’s servers. You’ll need these keys to interact with Stripe’s services, including creating payment intents, processing payments, and managing customer data.
Here’s how to obtain your API keys:
- Go to the API Keys Section: In your Stripe Dashboard, navigate to Developers > API Keys.
- Use Your Test and Live Keys: Stripe provides both Test and Live API keys. For development, you’ll start with the Test API keys to ensure everything works in a sandbox environment.
- Publishable Key: This key is used in your frontend (client-side) code.
- Secret Key: This key is used in your backend (server-side) code. Keep it secure, as it allows access to sensitive operations.
You’ll use these keys later to authenticate requests from your Android app to Stripe’s servers.
5. Set Up Your Webhooks (Optional but Recommended)
Webhooks allow Stripe to send notifications to your app about the status of payments and other events (such as payment failures or chargebacks). While this step is optional, setting up webhooks is crucial for handling real-time updates on payments made via Stripe Terminal.
- Go to the Webhooks Section: In the Stripe Dashboard, navigate to Developers > Webhooks.
- Add a Webhook Endpoint: Create an endpoint in your backend server to listen for webhook events from Stripe. You can choose which events to subscribe to, such as
payment_intent.succeeded
orpayment_intent.failed
.
Web-hooks are essential for tracking payment statuses and notifying customers about transaction updates.
Preparing Your Android App for Stripe Terminal
Adding stripe terminal dependency
implementation("com.stripe:stripeterminal:4.1.0")
Let’s start working on terminal setup and payments.
Initialisation
override fun init(
connectionType: ConnectionType,
) {
this.connectionType = connectionType
if (Terminal.isInitialized().not()) {
/**
Initializing Terminal
* **/
initializeTerminal()
} else {
/**
Terminal was already initialized
* **/
onTerminalAlreadyInitialized()
}
}
/**
Initializing Terminal and connecting Card Reader
* **/
private fun initializeTerminal() {
try {
Terminal.initTerminal(
context = context,
logLevel = if (BuildConfig.DEBUG) LogLevel.VERBOSE else LogLevel.NONE,
tokenProvider = connectionTokenProvider,
listener = terminalListener
)
/**
Connecting Card Reader
* **/
connectCardReader()
} catch (e: Exception) {
/**
Triggers if we got an exception while initializing terminal
* **/
updateTerminalState(TerminalState.Exception(e.message.toString()))
}
}
/**
Terminal was already initialized, we are connecting reader for payments
* **/
private fun onTerminalAlreadyInitialized() {
/**
Connecting Card Reader
* **/
connectCardReader()
}
Connection Token Provider
/**
Reesponsible for fetching token
* **/
private val connectionTokenProvider by lazy {
object : ConnectionTokenProvider {
override fun fetchConnectionToken(
callback: ConnectionTokenCallback
) {
// Execute your method here for getting token from backend API
}
}
}
Terminal Event Listener
/**
This @param terminalListener is responsible for observing connectivity changes and payment status changes
* **/
private val terminalListener by lazy {
object : TerminalListener {
override fun onConnectionStatusChange(status: ConnectionStatus) {
super.onConnectionStatusChange(status)
/**
On Terminal Connectivity Status Changed
**/
onTerminalConnectionChange(status)
}
override fun onPaymentStatusChange(status: PaymentStatus) {
super.onPaymentStatusChange(status)
/**
On Payment Status Changed
**/
onTerminalPaymentStatusChange(status)
}
}
}
/**
We have four connectivity states i.e. Connected, Connecting, Not Connected and Discovering
* **/
private fun onTerminalConnectionChange(status: ConnectionStatus) {
when (status) {
ConnectionStatus.CONNECTED -> updateTerminalState(TerminalState.Initialized)
ConnectionStatus.CONNECTING -> updateTerminalState(TerminalState.Initializing)
ConnectionStatus.NOT_CONNECTED -> updateTerminalState(TerminalState.NotInitialized)
ConnectionStatus.DISCOVERING -> updateTerminalState(TerminalState.Discovering)
}
}
/**
We have four payment states i.e. Processing, Ready, Waiting for input and Not Ready
* **/
private fun onTerminalPaymentStatusChange(status: PaymentStatus) {
when (status) {
PaymentStatus.PROCESSING -> updateTerminalState(TerminalState.PaymentProcessing)
PaymentStatus.READY -> updateTerminalState(TerminalState.PaymentReady)
PaymentStatus.WAITING_FOR_INPUT -> updateTerminalState(TerminalState.PaymentWaitingForInput)
PaymentStatus.NOT_READY -> updateTerminalState(TerminalState.PaymentNotReady)
}
}
Discovering Card Reader
We have to discover out reader so that we can connect our card reader for payments. It can be discovered via USB(if connected with device via USB) or Bluetooth ( By turning on bluetooth) or by other available options.
Let’s start discovering
/**
This is responsible for start discovering readers.Also, we are validating that location permission is granted or not
* **/
override fun discoverReaders() {
val isLocationEnabled = permissionManager.arePermissionsGranted(
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION
)
if (isLocationEnabled.not()) {
updateTerminalState(TerminalState.Exception("Please grant location permission!"))
} else {
startDiscovering()
}
}
/**
Discovering w.r.t to connection type
* **/
private fun startDiscovering() {
when (connectionType) {
is ConnectionType.USB -> {
discoverUSBReaders()
}
is ConnectionType.Bluetooth -> {
startDiscoveringBluetoothReaders()
}
}
}
Let’s setup our configuration for USB and Bluetooth
/**
This configuration is required for the configuration of card reader via USB
* **/
private val usbDiscoveryConfig by lazy {
DiscoveryConfiguration.UsbDiscoveryConfiguration(
timeout = DISCOVERY_TIMEOUT,
isSimulated = false
)
}
/**
This configuration is required for the configuration of card reader via Bluetooth
* **/
private val bluetoothDiscoveryConfig by lazy {
DiscoveryConfiguration.BluetoothDiscoveryConfiguration(
timeout = DISCOVERY_TIMEOUT,
isSimulated = false
)
}
/**
Discovering readers via USB
* **/
private fun discoverUSBReaders() {
Terminal
.getInstance()
.discoverReaders(
config = usbDiscoveryConfig,
discoveryListener = discoveryListener,
callback = discoveryCallback
)
}
/**
Discovering reader via Bluetooth
* **/
private fun startDiscoveringBluetoothReaders() {
val areBluetoothPermissionsAllowed = permissionManager.arePermissionsGranted(
Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.BLUETOOTH_SCAN
)
if (areBluetoothPermissionsAllowed.not()) {
updateTerminalState(TerminalState.Exception("Please grant bluetooth permissions"))
} else {
discoverBluetoothReaders()
}
}
/**
Discovering readers via Bluetooth
* **/
private fun discoverBluetoothReaders() {
Terminal
.getInstance()
.discoverReaders(
config = bluetoothDiscoveryConfig,
discoveryListener = discoveryListener,
callback = discoveryCallback
)
}
Now let’s setup our discovery listener and callbacks
/**
This callback will be triggered when readers are discovered
* **/
private val discoveryListener by lazy {
object : DiscoveryListener {
override fun onUpdateDiscoveredReaders(readers: List<Reader>) {
/**
No Available Readers
**/
if (readers.isEmpty()) {
updateTerminalState(TerminalState.Exception("No readers available!"))
} else {
/**
Discovered Readers
**/
updateTerminalState(
TerminalState.Discovered(
message = "Discovered: ${
readers.map {
"DeviceName=>${it.deviceType.deviceName},Simulated=>${it.isSimulated},SN=>${it.serialNumber},Reader Name=>${it.deviceType.name}IsUSBConnected=>${it.isUsbConnected}"
}
}"
)
)
/**
Connecting First Available Reader
**/
connectReader(readers.first())
}
}
}
}
/**
This callback is responsible for discovery states
* **/
private val discoveryCallback by lazy {
object : Callback {
override fun onFailure(e: TerminalException) {
/**
Error while discovering readers
* **/
updateTerminalState(TerminalState.Exception(e.message.toString()))
}
override fun onSuccess() {
/**
Reader Discovered!
* **/
updateTerminalState(
TerminalState.Discovered(
message = "Discovered Reader, Now updating discovery..."
)
)
}
}
}
Connecting Card Reader
Connecting card reader if reader is already discovered and ready for connection
/**
If reader was already initialized we are just connecting the same reader for payments otherwise we are discovering readers connected via USB
* **/
private fun connectCardReader() {
lastReaderConnected?.let {
if (connectionType == ConnectionType.USB) {
connectUSBReader(it)
} else {
connectBluetoothReader(it)
}
} ?: run {
discoverReaders()
}
}
/**
Connecting USB reader
* **/
private fun connectUSBReader(reader: Reader) {
val connectionConfig = ConnectionConfiguration.UsbConnectionConfiguration(
locationId = {{Location ID}},
autoReconnectOnUnexpectedDisconnect = true,
usbReaderListener = mobileReaderListener
)
Terminal.getInstance().connectReader(
reader = reader,
config = connectionConfig,
connectionCallback = readerCallback
)
}
/**
Connecting Bluetooth reader
* **/
private fun connectBluetoothReader(reader: Reader) {
val connectionConfig = ConnectionConfiguration.BluetoothConnectionConfiguration(
locationId = {{Location ID}},
autoReconnectOnUnexpectedDisconnect = true,
bluetoothReaderListener = mobileReaderListener
)
Terminal.getInstance().connectReader(
reader = reader,
config = connectionConfig,
connectionCallback = readerCallback
)
}
Our reader callback for handling events
/**
When card reader connected or not after requesting card reader connectivity
* **/
private val readerCallback by lazy {
object : ReaderCallback {
override fun onFailure(e: TerminalException) {
updateTerminalState(TerminalState.Exception(e.message.toString()))
}
override fun onSuccess(reader: Reader) {
updateTerminalState(TerminalState.Connected)
}
}
}
Now let’s setup our reader connectivity event listener
/**
This callback is responsible for handling reader updates and states
* **/
private val mobileReaderListener by lazy {
object : MobileReaderListener {
/**
When reader battery is low
* **/
override fun onReportLowBatteryWarning() {
super.onReportLowBatteryWarning()
updateTerminalState(TerminalState.LowBatteryWarning)
}
/**
When reader was reconnecting but failed
* **/
override fun onReaderReconnectFailed(reader: Reader) {
super.onReaderReconnectFailed(reader)
updateTerminalState(TerminalState.ReaderReconnectFailed)
}
/**
When reader reconnected successfully!
* **/
override fun onReaderReconnectSucceeded(reader: Reader) {
super.onReaderReconnectSucceeded(reader)
updateTerminalState(TerminalState.ReaderReconnectSucceeded)
}
/**
When reader got an event of card insertion or removal
* **/
override fun onReportReaderEvent(event: ReaderEvent) {
super.onReportReaderEvent(event)
updateTerminalState(TerminalState.ReaderEvent(event.name))
}
/**
When reader disconnected due to some reason
* **/
override fun onDisconnect(reason: DisconnectReason) {
super.onDisconnect(reason)
updateTerminalState(TerminalState.Disconnect(reason.name))
}
/**
When reader has an update available
* **/
override fun onReportAvailableUpdate(update: ReaderSoftwareUpdate) {
super.onReportAvailableUpdate(update)
updateTerminalState(
TerminalState.UpdateAvailable(
eta = update.durationEstimate.name,
version = update.version
)
)
}
/**
When reader update is in progress
* **/
override fun onReportReaderSoftwareUpdateProgress(progress: Float) {
super.onReportReaderSoftwareUpdateProgress(progress)
updateTerminalState(
TerminalState.Updating(progress)
)
}
/**
When reader trigger a message like Retry Card, Swipe Card, Insert Card, Remove Card, Card Removed too early etc.
For more info see ReaderDisplayMessage
* **/
override fun onRequestReaderDisplayMessage(message: ReaderDisplayMessage) {
super.onRequestReaderDisplayMessage(message)
updateTerminalState(
TerminalState.RequestReaderDisplayMessage(
message.name
)
)
}
/**
When reader triggers message of available options of reader like Insert/Swipe/Manual Entry etc.
* **/
override fun onRequestReaderInput(options: ReaderInputOptions) {
super.onRequestReaderInput(options)
updateTerminalState(
TerminalState.RequestReaderInput(
options = "${options.options.map { it.name }}"
)
)
}
/**
When reader starting reconnection
* **/
override fun onReaderReconnectStarted(
reader: Reader,
cancelReconnect: Cancelable,
reason: DisconnectReason
) {
super.onReaderReconnectStarted(reader, cancelReconnect, reason)
updateTerminalState(TerminalState.ReaderReconnectionStarted(reason.name))
}
/**
When reader update finished!
* **/
override fun onFinishInstallingUpdate(
update: ReaderSoftwareUpdate?,
e: TerminalException?
) {
super.onFinishInstallingUpdate(update, e)
e?.let { exception ->
updateTerminalState(TerminalState.Exception(exception.message.toString()))
} ?: run {
updateTerminalState(TerminalState.UpdateFinished)
}
}
/**
When reader have an update which is cancellable or mandatory
* **/
override fun onStartInstallingUpdate(
update: ReaderSoftwareUpdate,
cancelable: Cancelable?
) {
cancelable?.let {
// We can cancel our update if we want to cancel update for now and its cancellable
updateTerminalState(TerminalState.CancellingUpdate)
cancelable.cancel(updateCancellationCallback)
} ?: run {
updateTerminalState(
TerminalState.StartInstallingUpdates
)
}
}
/**
Battery level updates like is charging, battery level etc.
* **/
override fun onBatteryLevelUpdate(
batteryLevel: Float,
batteryStatus: BatteryStatus,
isCharging: Boolean
) {
super.onBatteryLevelUpdate(batteryLevel, batteryStatus, isCharging)
updateTerminalState(
TerminalState.BatteryLevel(
isCharging = isCharging,
level = batteryLevel,
status = batteryStatus.name
)
)
}
}
}
Now our reader is ready for accepting payments
Payment Processing
Before accepting payment we require a paymentIntentId which can be requested to backend for our payment processing
Let’s assume we have a paymentIntentId for payment processing.
Let’s start building payment processing.
fun processPayment(paymentIntentId: String)
/**
When reader is waiting for input and we tap or insert a card
* **/
override fun processPayment(secret: String) {
Terminal
.getInstance()
.retrievePaymentIntent(
clientSecret = secret,
callback = paymentIntentCallback
)
}
/**
When payment intent retrieved with success or failure
* **/
private val paymentIntentCallback by lazy {
object : PaymentIntentCallback {
override fun onSuccess(paymentIntent: PaymentIntent) {
updateTerminalState(TerminalState.PaymentIntentSuccess)
/**
Payment intent received, now collecting payment
* **/
collectPaymentMethod(paymentIntent)
}
override fun onFailure(e: TerminalException) {
updateTerminalState(
TerminalState.PaymentIntentFailure(e.message.toString())
)
}
}
}
Now start collecting payments
private fun collectPaymentMethod(intent: PaymentIntent) {
paymentCancellable = Terminal.getInstance().collectPaymentMethod(
callback = collectPaymentIntentCallback,
intent = intent,
config = paymentCollectionConfig
)
}
/**
Payment collection configuration with re-display config
* **/
private val paymentCollectionConfig by lazy {
CollectConfiguration
.Builder()
.setAllowRedisplay(AllowRedisplay.ALWAYS)
.build()
}
/**
Result of payment collection
* **/
private val collectPaymentIntentCallback by lazy {
object : PaymentIntentCallback {
override fun onFailure(e: TerminalException) {
updateTerminalState(
TerminalState.PaymentMethodCollectionFailure(e.message.toString())
)
}
override fun onSuccess(paymentIntent: PaymentIntent) {
updateTerminalState(TerminalState.PaymentMethodCollected)
/**
Confirming Payment
* **/
confirmPayment(paymentIntent)
}
}
}
Now confirming Payments
/**
Payment Confirmation
* **/
private fun confirmPayment(paymentIntent: PaymentIntent) {
Terminal.getInstance().confirmPaymentIntent(
intent = paymentIntent,
callback = confirmPaymentIntentCallback
)
}
/**
Triggers when payment confirmation result
* **/
private val confirmPaymentIntentCallback by lazy {
object : PaymentIntentCallback {
/**
Payment confirmed (Amount Deducted )
* **/
override fun onSuccess(paymentIntent: PaymentIntent) {
// Here we have deducted payment and now we can navigate user
// to success screen
updateTerminalState(TerminalState.PaymentConfirmed)
}
override fun onFailure(e: TerminalException) {
updateTerminalState(
TerminalState.PaymentConfirmationFailed(e.message.toString())
)
}
}
}
Now let’s reset our payment config for other payment
override fun clearPaymentConfig() {
cancelCurrentTransaction()
}
/**
Cancelling current transaction so we can start a new one
* **/
private fun cancelCurrentTransaction() {
paymentCancellable?.let {
paymentCancellable?.cancel(paymentCancellationCallback)
} ?: run {
_cancellationState.value = true
}
}
/**
Triggers when payment cancellation occurs
* **/
private val paymentCancellationCallback by lazy {
object : Callback {
override fun onFailure(e: TerminalException) {
updateTerminalState(TerminalState.Exception(e.message.toString()))
}
override fun onSuccess() {
updateTerminalState(TerminalState.PaymentCancelled)
resetTerminal()
}
}
}
/**
Resetting terminal and starting disconnection
* **/
private fun resetTerminal() {
if (Terminal.isInitialized()) {
Terminal.getInstance().disconnectReader(terminalDisconnectionCallback)
} else {
_cancellationState.value = true
}
}
Now move all these methods to an interface to make it testable and maintainable
interface PaymentManager {
fun init(
connectionType: ConnectionType, )
fun discoverReaders()
fun processPayment(secret: String)
val terminalState: StateFlow<TerminalState>
fun resetCancelable()
val cancellationState: StateFlow<Boolean>
fun clearPaymentConfig()
fun resetCancellationState()
}
Here is our Permission Manager
fun interface PermissionManager {
fun arePermissionsGranted(vararg permissions: String): Boolean
}
class DefaultPermissionManager @Inject constructor(
@ApplicationContext private val context: Context
) : PermissionManager {
override fun arePermissionsGranted(vararg permissions: String): Boolean {
return permissions.all { permission ->
ContextCompat.checkSelfPermission(
context,
permission
) == android.content.pm.PackageManager.PERMISSION_GRANTED
}
}
}
Here is our Connection Type Management
sealed interface ConnectionType {
data object USB : ConnectionType
data object Bluetooth : ConnectionType
}
Here is our Terminal State
sealed interface TerminalState {
data object Idle : TerminalState
data object Discovering : TerminalState
data class Discovered(val message: String) : TerminalState
data object Initialized : TerminalState
data object Initializing : TerminalState
data object NotInitialized : TerminalState
data object Connected : TerminalState
data object NotConnected : TerminalState
data object Connecting : TerminalState
data object PaymentProcessing : TerminalState
data object PaymentReady : TerminalState
data object PaymentWaitingForInput : TerminalState
data object PaymentNotReady : TerminalState
data class Exception(val message: String) : TerminalState
data object LowBatteryWarning : TerminalState
data object ReaderReconnectFailed : TerminalState
data object ReaderReconnectSucceeded : TerminalState
data class ReaderEvent(val eventName: String) : TerminalState
data class Disconnect(val reason: String) : TerminalState
data class UpdateAvailable(
val eta: String,
val version: String
) : TerminalState
data class Updating(val progress: Float) : TerminalState
data class RequestReaderDisplayMessage(val message: String) : TerminalState
data class RequestReaderInput(val options: String) : TerminalState
data class ReaderReconnectionStarted(val disconnectionReason: String) : TerminalState
data object UpdateFinished : TerminalState
data object StartInstallingUpdates : TerminalState
data class BatteryLevel(
val isCharging: Boolean,
val level: Float,
val status: String
) : TerminalState
data class PaymentIntentFailure(val message: String) : TerminalState
data object PaymentIntentSuccess : TerminalState
data object PaymentMethodCollected : TerminalState
data class PaymentMethodCollectionFailure(val message: String) : TerminalState
data class PaymentConfirmationFailed(val message: String) : TerminalState
data object PaymentConfirmed : TerminalState
data object TokenRetrieved : TerminalState
data object ResetTerminalSuccess : TerminalState
data object PaymentCancelled : TerminalState
data object CancellingUpdate : TerminalState
data object CancelledUpdate : TerminalState
data class UpdateCancellationFailed(val message: String) : TerminalState
}
Now let’s start observing our terminal event state and cancellation state
Instance
@Inject
lateinit var paymentManager: PaymentManager
Terminal State Observation
private fun listenToPaymentUpdates() {
collectFlowOnLifecycle(
flow = paymentManager.terminalState,
block = { state ->
onTerminalStateUpdate(state)
}
)
}
private fun onTerminalStateUpdate(state: TerminalState) {
when (state) {
is TerminalState.Idle -> {
Timber.i(TERMINAL_LOG_KEY + "Idle State")
}
is TerminalState.Discovering -> {
Timber.i(TERMINAL_LOG_KEY + "TERMINAL_LOG_KEY + Discovering")
}
is TerminalState.Discovered -> {
Timber.i(TERMINAL_LOG_KEY + "Discovered:${state.message}")
}
is TerminalState.NotConnected -> {
Timber.i(TERMINAL_LOG_KEY + "NotConnected")
}
is TerminalState.Connected -> {
Timber.i(TERMINAL_LOG_KEY + "Connected")
}
is TerminalState.Connecting -> {
Timber.i(TERMINAL_LOG_KEY + "Connecting")
}
is TerminalState.PaymentProcessing -> {
Timber.i(TERMINAL_LOG_KEY + "Payment Processing")
}
is TerminalState.PaymentReady -> {
Timber.i(TERMINAL_LOG_KEY + "Payment Ready")
}
is TerminalState.PaymentWaitingForInput -> {
Timber.i(TERMINAL_LOG_KEY + "Payment Waiting for Input")
}
is TerminalState.PaymentNotReady -> {
Timber.i(TERMINAL_LOG_KEY + "Payment Not Ready")
}
is TerminalState.Exception -> {
Timber.e(TERMINAL_LOG_KEY + "Exception:${message}")
}
is TerminalState.LowBatteryWarning -> {
Timber.i(TERMINAL_LOG_KEY + "Low Battery Warning")
}
is TerminalState.ReaderReconnectFailed -> {
Timber.e(TERMINAL_LOG_KEY + "Reader Reconnection Failed!")
}
is TerminalState.ReaderReconnectSucceeded -> {
Timber.i(TERMINAL_LOG_KEY + "Reader Reconnected Successfully!")
}
is TerminalState.ReaderEvent -> {
Timber.i(TERMINAL_LOG_KEY + "Reader Event:${state.eventName}")
}
is TerminalState.Disconnect -> {
Timber.e(TERMINAL_LOG_KEY + "Disconnected because ${state.reason}")
}
is TerminalState.UpdateAvailable -> {
Timber.i(TERMINAL_LOG_KEY + "Update Available!")
}
is TerminalState.Updating -> {
Timber.i(TERMINAL_LOG_KEY + "Updating...: ${state.progress}")
}
is TerminalState.RequestReaderDisplayMessage -> {
Timber.i(TERMINAL_LOG_KEY + "Reader Display Message: ${state.message}")
}
is TerminalState.RequestReaderInput -> {
Timber.i(TERMINAL_LOG_KEY + "Request Reader Input:${state.options}")
}
is TerminalState.ReaderReconnectionStarted -> {
Timber.i(TERMINAL_LOG_KEY + "Reader Reconnection Started, disconnected before because ${state.disconnectionReason}")
}
is TerminalState.UpdateFinished -> {
Timber.i(TERMINAL_LOG_KEY + "Reader Update Finished!")
}
is TerminalState.StartInstallingUpdates -> {
Timber.i(TERMINAL_LOG_KEY + "Start Installing Updates because it was mandatory")
}
is TerminalState.CancellingUpdate -> {
Timber.i(TERMINAL_LOG_KEY + "Cancelling Update!")
}
is TerminalState.CancelledUpdate -> {
Timber.i(TERMINAL_LOG_KEY + "Cancelled Update!")
}
is TerminalState.UpdateCancellationFailed -> {
Timber.i(TERMINAL_LOG_KEY + "Failed to cancel update because ${state.message}")
}
is TerminalState.PaymentIntentFailure -> {
Timber.e(TERMINAL_LOG_KEY + "Payment Intent Failure ${state.message}")
}
is TerminalState.PaymentIntentSuccess -> {
Timber.i(TERMINAL_LOG_KEY + "Payment Intent Success!")
}
is TerminalState.PaymentMethodCollected -> {
Timber.i(TERMINAL_LOG_KEY + "Payment Method Collected!")
}
is TerminalState.PaymentMethodCollectionFailure -> {
Timber.e(TERMINAL_LOG_KEY + "Payment Method Collection Failure ${state.message}")
}
is TerminalState.PaymentConfirmationFailed -> {
Timber.e(TERMINAL_LOG_KEY + "Payment Confirmation Failure ${state.message}")
}
is TerminalState.PaymentConfirmed -> {
Timber.i(TERMINAL_LOG_KEY + "Payment Confirmed!")
}
is TerminalState.BatteryLevel -> {
Timber.i(TERMINAL_LOG_KEY + "Battery Level Update: ${state.level}")
Timber.i(TERMINAL_LOG_KEY + "Battery Level State: ${state.status},")
Timber.i(TERMINAL_LOG_KEY + "Battery Charging: ${state.isCharging}")
}
is TerminalState.Initialized -> {
Timber.i(TERMINAL_LOG_KEY + "Initialized")
}
is TerminalState.Initializing -> {
Timber.i(TERMINAL_LOG_KEY + "Initializing....")
}
is TerminalState.NotInitialized -> {
Timber.i(TERMINAL_LOG_KEY + "Not Initialized!")
}
is TerminalState.TokenRetrieved -> {
Timber.i(TERMINAL_LOG_KEY + "Token Retrieved!")
}
is TerminalState.ResetTerminalSuccess -> {
Timber.i(TERMINAL_LOG_KEY + "Terminal Reset Success!")
}
is TerminalState.PaymentCancelled -> {
Timber.i(TERMINAL_LOG_KEY + "Payment Cancelled!")
}
}
}
Cancellation State Management
private fun listenToPaymentCancellationUpdates() {
collectFlowOnLifecycle(
flow = paymentManager.cancellationState,
block = { isCancelled ->
if (isCancelled) {
// From here we can navigate user to success screen or home
}
}
)
}
Conclusion
Integrating Stripe Terminal with your Android app opens up new opportunities for businesses to accept in-person payments securely and efficiently. By following the steps outlined in this guide, you’ve learned how to set up your Stripe account, order and configure your card reader, and authenticate your app with the necessary API keys.
Additionally, with the code examples provided in Kotlin, you now have a solid foundation to implement Stripe Terminal’s payment functionality within your Android app. Whether you’re building a mobile point-of-sale system or simply adding in-person payment capabilities, Stripe Terminal’s SDKs and APIs give you the flexibility to create a smooth, seamless payment experience for your users.
Remember to thoroughly test your integration using Stripe’s test keys and environment before going live. Once you’re ready, you can switch to live keys and start processing real transactions, with Stripe handling the complexities of secure payment processing.
Now that you’ve integrated Stripe Terminal, you’re equipped to build sophisticated in-person payment solutions and enhance your Android app’s capabilities. Happy coding, and good luck with your payment integration!
Feel free to contact me : Let’s Connect