# Terminal SDK migration guide Learn how to migrate to version 5.0.0 of the Stripe Terminal SDK. The Stripe Terminal iOS and Android SDKs have been updated with a number of breaking changes in APIs and behavior, some of which require you to update your integration with the Stripe Terminal SDK. To improve consistency between our SDKs and to simplify your application logic and integration, we regularly make changes in major version updates that might affect the way your integration works or behaves. This guide explains the latest changes to help you upgrade your integration. > Building a new Stripe Terminal integration? Visit our [Design an integration](https://docs.stripe.com/terminal/designing-integration.md) page to learn how to get started. ## Migrate to version 5.0.0 Here’s what you need to know about the version 5.0.0 Stripe Terminal iOS and Android SDKs: - Simplified payment integration - Process payments, setup intents, and refunds with a single method call combining collect and confirm steps. - Supports modern Swift async variants and Kotlin Coroutines for simplifying complex asynchronous flows - Swift concurrency (async/await) for iOS and Kotlin Coroutines for Android. - Customer cancellation enabled by default - On supported readers, customers can now cancel transactions by default during payment, setup, refund, and data collection flows. - Improved mobile reader and Tap to Pay reader auto reconnection observability - Enhanced reader auto-reconnection handling with more connection status states for mobile readers (Bluetooth and USB) and Tap to Pay readers. - Discover card acceptance support for Tap to Pay on Android (Public preview) - Accept Discover card payments with Tap to Pay on Android. - Updates to minimum supported platform versions from iOS 14.0 to iOS 15.0 # iOS > This is a iOS for when terminal-sdk-platform is ios. View the full page at https://docs.stripe.com/terminal/references/sdk-migration-guide?terminal-sdk-platform=ios. If your application currently uses a Terminal iOS SDK version earlier than 5.0.0, there are several changes you need to make to upgrade. For a detailed list of the changes from version 4.x to 5.0.0, see the [SDK changelog](https://github.com/stripe/stripe-terminal-ios/blob/master/CHANGELOG.md). ## Update your minimum supported version to iOS 15 or higher We regularly update the minimum supported version of our SDKs to streamline our developer support efforts. Existing 4.X versions of the Terminal iOS SDK will continue to support devices running *iOS 14* and higher. ## Simplified payment integration ### Update to unified payment processing The v5 SDK includes methods that combine the collect and confirm steps into a single operation. While the existing `collectPaymentMethod` and `confirmPaymentIntent` methods continue to work, we recommend using the unified methods for simpler integrations. #### Processing payments with processPaymentIntent Replace two-step collect and confirm with a single `processPaymentIntent` method call. *Before* #### Swift ```swift // Step 1: Collect payment method Terminal.shared.collectPaymentMethod(paymentIntent, collectConfig: collectConfig) { collectedPaymentIntent, collectError in guard let collectedPaymentIntent = collectedPaymentIntent else { // Payment method collection failed return } // Step 2: Confirm the payment Terminal.shared.confirmPaymentIntent(collectedPaymentIntent) { confirmedPaymentIntent, confirmError in if let confirmedPaymentIntent = confirmedPaymentIntent { // Payment successful } else { // Payment confirmation failed } } } ``` #### Objective-C ```objc // Step 1: Collect payment method [[SCPTerminal shared] collectPaymentMethodWithPaymentIntent:paymentIntent collectConfig:collectConfig completion:^(SCPPaymentIntent * _Nullable collectedPaymentIntent, NSError * _Nullable collectError) { if (collectError) { // Payment method collection failed return; } // Step 2: Confirm the payment [[SCPTerminal shared] confirmPaymentIntent:collectedPaymentIntent completion:^(SCPPaymentIntent * _Nullable confirmedPaymentIntent, NSError * _Nullable confirmError) { if (confirmedPaymentIntent) { // Payment successful } else { // Payment confirmation failed } }]; }]; ``` *After* #### Swift ```swift // Process and confirm the payment in one step Terminal.shared.processPaymentIntent(paymentIntent, collectConfig: collectConfig) { processedPaymentIntent, processError in if let processedPaymentIntent = processedPaymentIntent { // Payment successful } else { // Payment failed } } ``` #### Objective-C ```objc // Process and confirm the payment in one step [[SCPTerminal shared] processPaymentIntent:paymentIntent collectConfig:collectConfig completion:^(SCPPaymentIntent * _Nullable processedPaymentIntent, NSError * _Nullable processError) { if (processedPaymentIntent) { // Payment successful } else { // Payment failed } }]; ``` #### Processing refunds with processRefund The `collectRefundPaymentMethod` and `confirmRefund` methods are now deprecated. Use `processRefund` instead. *Before* #### Swift ```swift // Step 1: Collect refund payment method Terminal.shared.collectRefundPaymentMethod(refundParams) { collectError in guard collectError == nil else { // Refund collection failed return } // Step 2: Confirm the refund Terminal.shared.confirmRefund { refund, confirmError in if let refund = refund { // Refund successful } else { // Refund confirmation failed } } } ``` #### Objective-C ```objc // Step 1: Collect refund payment method [[SCPTerminal shared] collectRefundPaymentMethod:refundParams completion:^(NSError * _Nullable collectError) { if (collectError) { // Refund collection failed return; } // Step 2: Confirm the refund [[SCPTerminal shared] confirmRefund:^(SCPRefund * _Nullable refund, NSError * _Nullable confirmError) { if (refund) { // Refund successful } else { // Refund confirmation failed } }]; }]; ``` *After* #### Swift ```swift // Process the refund in one step Terminal.shared.processRefund(refundParams) { refund, refundError in if let refund = refund { // Refund successful } else { // Refund failed } } ``` #### Objective-C ```objc // Process the refund in one step [[SCPTerminal shared] processRefund:refundParams completion:^(SCPRefund * _Nullable refund, NSError * _Nullable refundError) { if (refund) { // Refund successful } else { // Refund failed } }]; ``` #### Processing setup intents with processSetupIntent Replace two-step collect and confirm with a single `processSetupIntent` method call. *Before* #### Swift ```swift // Step 1: Collect setup intent payment method Terminal.shared.collectSetupIntentPaymentMethod(setupIntent, customerConsentCollected: true) { collectedSetupIntent, collectError in guard let collectedSetupIntent = collectedSetupIntent else { // Setup intent collection failed return } // Step 2: Confirm the setup intent Terminal.shared.confirmSetupIntent(collectedSetupIntent) { confirmedSetupIntent, confirmError in if let confirmedSetupIntent = confirmedSetupIntent { // Setup intent successful } else { // Setup intent confirmation failed } } } ``` #### Objective-C ```objc // Step 1: Collect setup intent payment method [[SCPTerminal shared] collectSetupIntentPaymentMethod:setupIntent customerConsentCollected:YES completion:^(SCPSetupIntent * _Nullable collectedSetupIntent, NSError * _Nullable collectError) { if (collectError) { // Setup intent collection failed return; } // Step 2: Confirm the setup intent [[SCPTerminal shared] confirmSetupIntent:collectedSetupIntent completion:^(SCPSetupIntent * _Nullable confirmedSetupIntent, NSError * _Nullable confirmError) { if (confirmedSetupIntent) { // Setup intent successful } else { // Setup intent confirmation failed } }]; }]; ``` *After* #### Swift ```swift // Configure with allowRedisplay let config = try CollectSetupIntentConfigurationBuilder() .setAllowRedisplay(.always) .build() // Process the setup intent in one step Terminal.shared.processSetupIntent(setupIntent, collectConfig: config) { processedSetupIntent, setupError in if let processedSetupIntent = processedSetupIntent { // Setup intent successful } else { // Setup intent failed } } ``` #### Objective-C ```objc // Configure with allowRedisplay NSError *error = nil; SCPCollectSetupIntentConfiguration *config = [[[SCPCollectSetupIntentConfigurationBuilder new] setAllowRedisplay:SCPAllowRedisplayAlways] build:&error]; // Process the setup intent in one step [[SCPTerminal shared] processSetupIntent:setupIntent collectConfig:config completion:^(SCPSetupIntent * _Nullable processedSetupIntent, NSError * _Nullable setupError) { if (processedSetupIntent) { // Setup intent successful } else { // Setup intent failed } }]; ``` ### Swift async variant support The SDK now provides async variants for Terminal methods. You can write cleaner, sequential code instead of nesting completion handlers. *Before* ```swift let cancelable = Terminal.shared.collectPaymentMethod(paymentIntent, collectConfig: collectConfig) { collectedPaymentIntent, collectError in guard let collectedPaymentIntent = collectedPaymentIntent else { // Payment method collection failed return } Terminal.shared.confirmPaymentIntent(collectedPaymentIntent) { confirmedPaymentIntent, confirmError in // Handle confirmation } } ``` *After* ```swift let collectTask = Task { do { let collectedIntent = try await Terminal.shared.collectPaymentMethod(paymentIntent, collectConfig: collectConfig) let confirmedIntent = try await Terminal.shared.confirmPaymentIntent(collectedIntent) // Payment successful } catch { // Handle error } } // Use collectTask.cancel() to cancel the operation when needed ``` ## Platform and initialization ### Update Terminal initialization The `setTokenProvider` method has been removed. You must now initialize the SDK with the static `Terminal.initWithTokenProvider(_tokenProvider:)` method before accessing the `Terminal.shared` singleton. *Before* #### Swift ```swift // In your AppDelegate or scene delegate Terminal.setTokenProvider(yourTokenProvider) ``` #### Objective-C ```objc // In your AppDelegate or scene delegate [SCPTerminal setTokenProvider:yourTokenProvider]; ``` *After* #### Swift ```swift // In your AppDelegate or scene delegate, at app launch Terminal.initWithTokenProvider(yourTokenProvider) ``` #### Objective-C ```objc // In your AppDelegate or scene delegate, at app launch [SCPTerminal initializeWithTokenProvider:yourTokenProvider]; ``` ## Reader discovery and connection ### Update DiscoveryConfiguration initialization You can no longer initialize `DiscoveryConfiguration` objects directly with `init` or `new`. You must now use their associated builder classes. *Before* #### Swift ```swift let config = SCPInternetDiscoveryConfiguration(isSimulated: true) ``` #### Objective-C ```objc SCPInternetDiscoveryConfiguration *config = [[SCPInternetDiscoveryConfiguration alloc] initWithSimulated:YES]; ``` *After* #### Swift ```swift let config = InternetDiscoveryConfiguration.Builder() .setSimulated(true) .build() ``` #### Objective-C ```objc SCPInternetDiscoveryConfiguration *config = [[[[SCPInternetDiscoveryConfigurationBuilder alloc] init] setSimulated:YES] build]; ``` ### Handle reconnection status changes A new `.reconnecting` value has been added to the `ConnectionStatus` enum. During a reconnect, `Terminal.shared.connectedReader` will now be `nil` until the reconnection is successful. *Before* #### Swift ```swift func terminal(_ terminal: Terminal, didChange connectionStatus: ConnectionStatus) { switch connectionStatus { case .notConnected: // Handle not connected case .connected: // Handle connected @unknown default: break } } ``` #### Objective-C ```objc - (void)terminal:(SCPTerminal *)terminal didChangeConnectionStatus:(SCPConnectionStatus)status { switch (status) { case SCPConnectionStatusNotConnected: // Handle not connected break; case SCPConnectionStatusConnected: // Handle connected break; } } ``` *After* #### Swift ```swift func terminal(_ terminal: Terminal, didChange connectionStatus: ConnectionStatus) { switch connectionStatus { case .notConnected: // Handle not connected case .connected: // Handle connected case .reconnecting: // Handle reconnection in progress @unknown default: break } } ``` #### Objective-C ```objc - (void)terminal:(SCPTerminal *)terminal didChangeConnectionStatus:(SCPConnectionStatus)status { switch (status) { case SCPConnectionStatusNotConnected: // Handle not connected break; case SCPConnectionStatusConnected: // Handle connected break; case SCPConnectionStatusReconnecting: // Handle reconnection in progress break; } } ``` ## Payment acceptance and data collection ### Customer cancellation is now enabled by default On supported readers, the ability for customers to cancel transactions is now *enabled by default*. The `customerCancellation` property has changed from a `Bool` to the new `SCPCustomerCancellation` enum. *Before* #### Swift ```swift let collectConfig = try CollectConfigurationBuilder() .setEnableCustomerCancellation(false) .build() ``` #### Objective-C ```objc NSError *error = nil; SCPCollectConfiguration *collectConfig = [[[SCPCollectConfigurationBuilder new] setEnableCustomerCancellation:NO] build:&error]; ``` *After* #### Swift ```swift let collectConfig = try CollectPaymentIntentConfigurationBuilder() .setCustomerCancellation(.disableIfAvailable) .build() ``` #### Objective-C ```objc NSError *error = nil; SCPCollectPaymentIntentConfiguration *collectConfig = [[[SCPCollectPaymentIntentConfigurationBuilder new] setCustomerCancellation:SCPCustomerCancellationDisableIfAvailable] build:&error]; ``` ### Interac refund parameter updates If you create `SCPRefundParameters` for an Interac refund using a PaymentIntent ID, you must now also pass the PaymentIntent’s `clientSecret`. You can alternatively continue using the charge ID, which doesn’t require the `clientSecret`. *Before* #### Swift ```swift let refundParams = try RefundParametersBuilder( paymentIntentId: "pi_123", amount: 1000, currency: "cad" ).build() ``` #### Objective-C ```objc NSError *error = nil; SCPRefundParameters *refundParams = [[SCPRefundParametersBuilder alloc] initWithPaymentIntentId:@"pi_123" amount:1000 currency:@"cad"]; ``` *After* #### Swift ```swift let refundParams = try RefundParametersBuilder( paymentIntentId: "pi_123", clientSecret: "pi_123_secret_abc", amount: 1000, currency: "cad" ).build() ``` #### Objective-C ```objc NSError *error = nil; SCPRefundParameters *refundParams = [[SCPRefundParametersBuilder alloc] initWithPaymentIntentId:@"pi_123" clientSecret:@"pi_123_secret_abc" amount:1000 currency:@"cad"]; ``` # Android > This is a Android for when terminal-sdk-platform is android. View the full page at https://docs.stripe.com/terminal/references/sdk-migration-guide?terminal-sdk-platform=android. If your application currently uses a Terminal Android SDK version earlier than 5.0.0, there are several changes you need to make to upgrade. For a detailed list of the changes from version 4.x to 5.0.0, see the [SDK changelog](https://github.com/stripe/stripe-terminal-android/blob/master/CHANGELOG.md). ## Simplified payment integration ### Update to unified payment processing The v5 SDK introduces streamlined methods that combine the collect and confirm steps into a single operation. While the existing `collectPaymentMethod` and `confirmPaymentIntent` methods continue to work, we recommend using the new unified methods for simpler integrations. #### Processing payments with processPaymentIntent Replace two-step collect and confirm with a single `processPaymentIntent` method call. *Before* #### Kotlin ```kotlin // Step 1: Collect payment method Terminal.getInstance().collectPaymentMethod( paymentIntent, collectConfig, object : PaymentIntentCallback { override fun onSuccess(paymentIntent: PaymentIntent) { // Step 2: Confirm the payment Terminal.getInstance().confirmPaymentIntent(paymentIntent, object : PaymentIntentCallback { override fun onSuccess(confirmedPaymentIntent: PaymentIntent) { // Payment successful } override fun onFailure(e: TerminalException) { // Payment confirmation failed } }) } override fun onFailure(e: TerminalException) { // Payment method collection failed } } ) ``` #### Java ```java // Step 1: Collect payment method Terminal.getInstance().collectPaymentMethod( paymentIntent, collectConfig, new PaymentIntentCallback() { @Override public void onSuccess(@NotNull PaymentIntent paymentIntent) { // Step 2: Confirm the payment Terminal.getInstance().confirmPaymentIntent(paymentIntent, new PaymentIntentCallback() { @Override public void onSuccess(@NotNull PaymentIntent confirmedPaymentIntent) { // Payment successful } @Override public void onFailure(@NotNull TerminalException e) { // Payment confirmation failed } }); } @Override public void onFailure(@NotNull TerminalException e) { // Payment method collection failed } } ); ``` *After* #### Kotlin ```kotlin // Process and confirm the payment in one step Terminal.getInstance().processPaymentIntent( paymentIntent, collectConfig, confirmConfig, object : PaymentIntentCallback { override fun onSuccess(paymentIntent: PaymentIntent) { // Payment successful } override fun onFailure(e: TerminalException) { // Payment failed } } ) ``` #### Java ```java // Process and confirm the payment in one step Terminal.getInstance().processPaymentIntent( paymentIntent, collectConfig, confirmConfig, new PaymentIntentCallback() { @Override public void onSuccess(@NotNull PaymentIntent paymentIntent) { // Payment successful } @Override public void onFailure(@NotNull TerminalException e) { // Payment failed } } ); ``` #### Processing refunds with processRefund The `collectRefundPaymentMethod` and `confirmRefund` methods are now deprecated. Use `processRefund` instead. *Before* #### Kotlin ```kotlin // Step 1: Collect refund payment method val refundParams = RefundParameters.ByChargeId( id = "ch_123", amount = 1000L, currency = "cad" ).build() Terminal.getInstance().collectRefundPaymentMethod( refundParams, object : Callback { override fun onSuccess() { // Step 2: Confirm the refund Terminal.getInstance().confirmRefund(object : RefundCallback { override fun onSuccess(refund: Refund) { // Refund successful } override fun onFailure(e: TerminalException) { // Refund confirmation failed } }) } override fun onFailure(e: TerminalException) { // Refund collection failed } } ) ``` #### Java ```java // Step 1: Collect refund payment method RefundParameters refundParams = new RefundParameters.ByChargeId( "ch_123", 1000, "cad" ).build(); Terminal.getInstance().collectRefundPaymentMethod( refundParams, new Callback() { @Override public void onSuccess() { // Step 2: Confirm the refund Terminal.getInstance().confirmRefund(new RefundCallback() { @Override public void onSuccess(@NotNull Refund refund) { // Refund successful } @Override public void onFailure(@NotNull TerminalException e) { // Refund confirmation failed } }); } @Override public void onFailure(@NotNull TerminalException e) { // Refund collection failed } } ); ``` *After* #### Kotlin ```kotlin val refundParams = RefundParameters.ByChargeId( id = "ch_123", amount = 1000, currency = "cad" ).build() // Process the refund in one step Terminal.getInstance().processRefund( refundParams, object : RefundCallback { override fun onSuccess(refund: Refund) { // Refund successful } override fun onFailure(e: TerminalException) { // Refund failed } } ) ``` #### Java ```java RefundParameters refundParams = new RefundParameters.ByChargeId( "ch_123", 1000, "cad" ).build(); // Process the refund in one step Terminal.getInstance().processRefund( refundParams, new RefundCallback() { @Override public void onSuccess(@NotNull Refund refund) { // Refund successful } @Override public void onFailure(@NotNull TerminalException e) { // Refund failed } } ); ``` #### Processing setup intents with processSetupIntent Replace two-step collect and confirm with a single `processSetupIntent` method call. *Before* #### Kotlin ```kotlin // Step 1: Collect setup intent payment method Terminal.getInstance().collectSetupIntentPaymentMethod( intent = setupIntent, allowRedisplay = AllowRedisplay.ALWAYS, callback = object : SetupIntentCallback { override fun onSuccess(setupIntent: SetupIntent) { // Step 2: Confirm the setup intent Terminal.getInstance().confirmSetupIntent(setupIntent, object : SetupIntentCallback { override fun onSuccess(confirmedSetupIntent: SetupIntent) { // Setup intent successful } override fun onFailure(e: TerminalException) { // Setup intent confirmation failed } }) } override fun onFailure(e: TerminalException) { // Setup intent collection failed } } ) ``` #### Java ```java // Step 1: Collect setup intent payment method Terminal.getInstance().collectSetupIntentPaymentMethod( setupIntent, AllowRedisplay.ALWAYS, new SetupIntentCallback() { @Override public void onSuccess(@NotNull SetupIntent setupIntent) { // Step 2: Confirm the setup intent Terminal.getInstance().confirmSetupIntent(setupIntent, new SetupIntentCallback() { @Override public void onSuccess(@NotNull SetupIntent confirmedSetupIntent) { // Setup intent successful } @Override public void onFailure(@NotNull TerminalException e) { // Setup intent confirmation failed } }); } @Override public void onFailure(@NotNull TerminalException e) { // Setup intent collection failed } } ); ``` *After* #### Kotlin ```kotlin // Configure with allowRedisplay val config = CollectSetupIntentConfiguration.Builder() .build() // Process the setup intent in one step Terminal.getInstance().processSetupIntent( intent = setupIntent, allowRedisplay = AllowRedisplay.ALWAYS, collectConfig = config, callback = object : SetupIntentCallback { override fun onSuccess(setupIntent: SetupIntent) { // Setup intent successful } override fun onFailure(e: TerminalException) { // Setup intent failed } } ) ``` #### Java ```java // Configure with allowRedisplay CollectSetupIntentConfiguration config = new CollectSetupIntentConfiguration.Builder() .build(); // Process the setup intent in one step Terminal.getInstance().processSetupIntent( setupIntent, AllowRedisplay.ALWAYS, config, new SetupIntentCallback() { @Override public void onSuccess(@NotNull SetupIntent setupIntent) { // Setup intent successful } @Override public void onFailure(@NotNull TerminalException e) { // Setup intent failed } } ); ``` ### Kotlin Coroutines support For Kotlin developers, a new optional module `stripeterminal-ktx` provides `suspend` function wrappers for asynchronous Terminal APIs. > Add this dependency: `implementation("com.stripe:stripeterminal-ktx:5.0.0")` *Before* ```kotlin Terminal.getInstance().discoverReaders(config, object : DiscoveryListener { override fun onUpdateDiscoveredReaders(readers: List) { val selectedReader = readers[0] Terminal.getInstance().connectReader(selectedReader, connectionConfig, object : ReaderCallback { override fun onSuccess(reader: Reader) { // Handle successful connection } override fun onFailure(e: TerminalException) { // Handle connection failure } }) } }) ``` *After* ```kotlin // Add dependency: implementation("com.stripe:stripeterminal-ktx:5.0.0") coroutineScope { try { val readers = Terminal.getInstance().discoverReaders(discoveryConfig) .filter { it.isNotEmpty() } .first() val selectedReader = readers.first() val reader = Terminal.getInstance().connectReader(selectedReader, connectConfig) // Handle successful connection } catch(e: TerminalException) { // Handle failures on discovery or connect } } ``` ## Platform and initialization ### Update Terminal initialization The `Terminal.initTerminal` method has been renamed to `Terminal.init`. It now requires a nullable `OfflineListener` parameter. *Before* #### Kotlin ```kotlin Terminal.initTerminal(applicationContext, LogLevel.VERBOSE, tokenProvider, terminalListener) ``` #### Java ```java Terminal.initTerminal(getApplicationContext(), LogLevel.VERBOSE, tokenProvider, terminalListener); ``` *After* #### Kotlin ```kotlin Terminal.init(applicationContext, LogLevel.VERBOSE, tokenProvider, terminalListener, offlineListener) ``` #### Java ```java Terminal.init(getApplicationContext(), LogLevel.VERBOSE, tokenProvider, terminalListener, offlineListener); ``` ## Reader discovery and connection ### Handle reconnection status changes A new `RECONNECTING` value has been added to the `ConnectionStatus` enum. During initial connection `Terminal.getInstance().getConnectedReader()` will now be `null` until the connection attempt succeeds. *Before* #### Kotlin ```kotlin override fun onConnectionStatusChange(status: ConnectionStatus) { when (status) { ConnectionStatus.NOT_CONNECTED -> { // Handle not connected } ConnectionStatus.CONNECTED -> { // Handle connected } } } ``` #### Java ```java @Override public void onConnectionStatusChange(@NotNull ConnectionStatus status) { switch (status) { case NOT_CONNECTED: // Handle not connected break; case CONNECTED: // Handle connected break; } } ``` *After* #### Kotlin ```kotlin override fun onConnectionStatusChange(status: ConnectionStatus) { when (status) { ConnectionStatus.NOT_CONNECTED -> { // Handle not connected } ConnectionStatus.CONNECTED -> { // Handle connected } ConnectionStatus.RECONNECTING -> { // Handle reconnection in progress } } } ``` #### Java ```java @Override public void onConnectionStatusChange(@NotNull ConnectionStatus status) { switch (status) { case NOT_CONNECTED: // Handle not connected break; case CONNECTED: // Handle connected break; case RECONNECTING: // Handle reconnection in progress break; } } ``` ### Streamlined connection with easyConnect For smart readers, Tap to Pay, and Apps on Devices integrations, you can now use `Terminal.easyConnect`, which combines discovery and connection into a single method call. *Before* #### Kotlin ```kotlin // Step 1: Discover the reader Terminal.getInstance().discoverReaders(config, object : DiscoveryListener { override fun onUpdateDiscoveredReaders(readers: List) { val selectedReader = readers[0] // Step 2: Connect to the reader Terminal.getInstance().connectReader(selectedReader, connectionConfig, readerCallback) } }) ``` #### Java ```java // Step 1: Discover the reader Terminal.getInstance().discoverReaders(config, new DiscoveryListener() { @Override public void onUpdateDiscoveredReaders(@NotNull List readers) { Reader selectedReader = readers.get(0); // Step 2: Connect to the reader Terminal.getInstance().connectReader(selectedReader, connectionConfig, readerCallback); } }); ``` *After* #### Kotlin ```kotlin // Discover and connect in one step by providing discovery filter val easyConnectConfig = InternetEasyConnectConfiguration( discoveryConfiguration = DiscoveryConfiguration.InternetDiscoveryConfiguration( location = "YOUR-LOCATION-ID", // optional discoveryFilter = DiscoveryFilter.BySerial("YOUR-READER-SERIAL-NUMBER"), ), connectionConfiguration = ConnectionConfiguration.InternetConnectionConfiguration( internetReaderListener = internetReaderListener, ) ) Terminal.getInstance().easyConnect( easyConnectConfig, object : ReaderCallback { override fun onSuccess(reader: Reader) { // Handle successful connection } override fun onFailure(e: TerminalException) { // Handle failure } } ) ``` #### Java ```java // Discover and connect in one step by providing discovery filter InternetEasyConnectConfiguration easyConnectConfig = new InternetEasyConnectConfiguration( new DiscoveryConfiguration.InternetDiscoveryConfiguration( "YOUR-LOCATION-ID", // optional new DiscoveryFilter.BySerial("YOUR-READER-SERIAL-NUMBER") ), new ConnectionConfiguration.InternetConnectionConfiguration( internetReaderListener, false // failIfInUse ) ); Terminal.getInstance().easyConnect( easyConnectConfig, new ReaderCallback() { @Override public void onSuccess(@NotNull Reader reader) { // Handle successful connection } @Override public void onFailure(@NotNull TerminalException e) { // Handle failure } } ); ``` ### Internet reader discovery filtering Internet reader discovery now supports filtering by reader ID or serial number. Set the `discoveryFilter` property on `InternetDiscoveryConfiguration` to discover a specific reader. *Before* #### Kotlin ```kotlin val config = InternetDiscoveryConfiguration(location = "loc_123") ``` #### Java ```java InternetDiscoveryConfiguration config = new InternetDiscoveryConfiguration( 0, // timeout in seconds "loc_123", ); ``` *After* #### Kotlin ```kotlin val config = InternetDiscoveryConfiguration( location = "loc_123", // optional discoveryFilter = DiscoveryFilter.BySerial("READER-SERIAL-NUMBER"), // or DiscoveryFilter.ByReaderId("tmr_YOUR-READER-STRIPE-ID) to filter by reader id ) ``` #### Java ```java InternetDiscoveryConfiguration config = new InternetDiscoveryConfiguration( 0, // timeout in seconds, "loc_123", // optional false, // is simulated new DiscoveryFilter.BySerial("READER-SERIAL-NUMBER") // or new DiscoveryFilter.ByReaderId("tmr_YOUR-READER-STRIPE-ID) to filter by reader id ); ``` ## Payment acceptance and data collection ### Customer cancellation is now enabled by default On Android-based readers, the ability for customers to cancel transactions is now *enabled by default*. You can disable this feature by setting `customerCancellation` to `DISABLE_IF_AVAILABLE`. *Before* #### Kotlin ```kotlin val config = CollectConfiguration.Builder() .setEnableCustomerCancellation(false) .build() ``` #### Java ```java CollectConfiguration config = new CollectConfiguration.Builder() .setEnableCustomerCancellation(false) .build(); ``` *After* #### Kotlin ```kotlin val config = CollectPaymentIntentConfiguration.Builder() .setCustomerCancellation(CustomerCancellation.DISABLE_IF_AVAILABLE) .build() ``` #### Java ```java CollectPaymentIntentConfiguration config = new CollectPaymentIntentConfiguration.Builder() .setCustomerCancellation(CustomerCancellation.DISABLE_IF_AVAILABLE) .build(); ``` ### Interac refund parameter updates If you create `RefundParameters` for an Interac refund using a PaymentIntent ID, you must now also pass the PaymentIntent’s `clientSecret`. You can alternatively continue using the charge ID, which doesn’t require the `clientSecret`. *Before* #### Kotlin ```kotlin val refundParams = RefundParameters.Builder( RefundParameters.Id.PaymentIntent("pi_123"), 1000, "cad" ).build() ``` #### Java ```java RefundParameters refundParams = new RefundParameters.Builder( new RefundParameters.Id.PaymentIntent("pi_123"), 1000, "cad" ).build(); ``` *After* #### Kotlin ```kotlin val refundParams = RefundParameters.ByPaymentIntentId( paymentIntentId = "pi_123", clientSecret = "pi_123_secret_abc", amount = 1000, currency = "cad" ).build() ``` #### Java ```java RefundParameters refundParams = new RefundParameters.ByPaymentIntentId( "pi_123", "pi_123_secret_abc", 1000, "cad" ).build(); ``` ## Update Tap to Pay on Android integration ### TapZone configuration refactoring The `TapToPayUxConfiguration.TapZone` class has been refactored. The `indicator` and `position` fields are replaced by a single `TapZone` object. *Before* #### Kotlin ```kotlin val config = TapToPayUxConfiguration.Builder() .setTapZone( indicator = TapZoneIndicator.ABOVE, position = TapZonePosition.Manual(0.5f, 0.2f) ) .build() ``` #### Java ```java TapToPayUxConfiguration config = new TapToPayUxConfiguration.Builder() .setTapZone( TapZoneIndicator.ABOVE, new TapZonePosition.Manual(0.5f, 0.2f) ) .build(); ``` *After* #### Kotlin ```kotlin // Position the tap zone above the reader UI val config = TapToPayUxConfiguration.Builder() .setTapZone(TapZone.Above(horizontalBias = 0.2f)) .build() // Or position it on the left side of the screen val config2 = TapToPayUxConfiguration.Builder() .setTapZone(TapZone.Left()) // Center vertically by default .build() ``` #### Java ```java // Position the tap zone above the reader UI TapToPayUxConfiguration config = new TapToPayUxConfiguration.Builder() .setTapZone(new TapZone.Above(0.2f)) .build(); // Or position it on the left side of the screen TapToPayUxConfiguration config2 = new TapToPayUxConfiguration.Builder() .setTapZone(new TapZone.Left()) // Center vertically by default .build(); ```