androidflutterkotlindartstripe-payments

Stripe terminal Wisepad 3 reader not get detected in android Flutter


I am working with stripe terminal android SDK https://docs.stripe.com/terminal/quickstart?reader=wp3 in flutter because there is no SDK available for Flutter. I am trying for many days. Stripe terminal got initialized but it is not detecting Wisepad 3 reader.

Flutter code =

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class BluetoothConfigurationScreen extends StatefulWidget {
  const BluetoothConfigurationScreen({super.key});

  @override
  State<BluetoothConfigurationScreen> createState() =>
      _BluetoothConfigurationScreenState();
}

class _BluetoothConfigurationScreenState
    extends State<BluetoothConfigurationScreen> {
  static const platform = MethodChannel('com.example.case/stripe');
  TextEditingController textEditingController = TextEditingController();
  String resultText = '';
  String _batteryLevel = 'Unknown battery level.';
  String _cardInfo = 'No card scanned';
  String _connectedDeviceInfo = 'No device connected';
  bool isTerminalInitialized = false;
  bool isReaderConnected = false;

  // Method to initialize Stripe terminal
  Future<void> _initializeStripe() async {
    try {
      // Initialize the Stripe terminal by invoking the method on the native side
      final result = await platform.invokeMethod('initializeStripe');
      setState(() {
        isTerminalInitialized = true; // Mark terminal as initialized
      });
      print(result);
    } catch (e) {
      setState(() {
        isTerminalInitialized = false; // Handle initialization failure
      });
      print("Error initializing terminal: $e");
    }
  }

  // Method to get battery level
  Future<void> _getBatteryLevel() async {
    String batteryLevel;
    try {
      final result = await platform.invokeMethod<int>('getBatteryLevel');
      batteryLevel = 'Battery level at $result % .';
    } on PlatformException catch (e) {
      batteryLevel = "Failed to get battery level: '${e.message}'.";
    }

    setState(() {
      _batteryLevel = batteryLevel;
    });
  }

  // Method to start card scan
  Future<void> _startCardScan() async {
    if (!isTerminalInitialized || !isReaderConnected) {
      setState(() {
        _cardInfo = 'Terminal or Reader is not connected. Please initialize and connect first.';
      });
      return;
    }

    try {
      final cardData = await platform.invokeMethod('scanCard');
      setState(() {
        _cardInfo = cardData;
      });
    } catch (e) {
      setState(() {
        _cardInfo = 'Error scanning card: $e';
      });
    }
  }

  // Method to discover Bluetooth devices and fetch connected device info
  Future<void> _discoverReaders() async {
    if (!isTerminalInitialized) {
      setState(() {
        _cardInfo = 'Terminal is not initialized yet. Please initialize first.';
      });
      return;
    }

    try {
      final result = await platform.invokeMethod('discoverReaders');
      setState(() {
        _cardInfo = result;
        isReaderConnected = true; // Assuming reader is connected after discovery
      });

      // Fetch connected device info
      final connectedDevice = await platform.invokeMethod('getConnectedDeviceInfo');
      setState(() {
        _connectedDeviceInfo = connectedDevice ?? 'No connected device information available';
      });
    } catch (e) {
      setState(() {
        _cardInfo = 'Error discovering readers: $e';
        isReaderConnected = false;
        _connectedDeviceInfo = 'Error fetching connected device info';
      });
    }
  }

  // Method to make a 0.1 cent payment
  Future<void> _makePayment() async {
    if (!isTerminalInitialized || !isReaderConnected) {
      setState(() {
        _cardInfo = 'Terminal or Reader is not connected. Please initialize and connect first.';
      });
      return;
    }

    try {
      final paymentResult = await platform.invokeMethod('makePayment');
      setState(() {
        _cardInfo = paymentResult;
      });
    } catch (e) {
      setState(() {
        _cardInfo = 'Error processing payment: $e';
      });
    }
  }

  @override
  void dispose() {
    textEditingController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            ElevatedButton(
              onPressed: () async {
                await _initializeStripe(); // Initialize Stripe Terminal
                if (isTerminalInitialized) {
                  setState(() {
                    _cardInfo = "Terminal Initialized. You can now scan cards.";
                  });
                } else {
                  setState(() {
                    _cardInfo = "Initialization failed. Try again.";
                  });
                }
              },
              child: const Text("Initialize Stripe Terminal"),
            ),
            const SizedBox(height: 30),
            ElevatedButton(
              onPressed: _getBatteryLevel,
              child: const Text('Get Battery Level'),
            ),
            Text(_batteryLevel),
            const SizedBox(height: 30),
            ElevatedButton(
              onPressed: _startCardScan, // Trigger card scan
              child: const Text('Scan Card'),
            ),
            Text(_cardInfo), // Display scanned card information
            const SizedBox(height: 30),
            ElevatedButton(
              onPressed: _discoverReaders, // Discover Bluetooth readers
              child: const Text('Discover Bluetooth Readers'),
            ),
            Text(_cardInfo), // Display reader information
            Text('Connected Device Info: $_connectedDeviceInfo'), // Display connected device info
            const SizedBox(height: 30),
            ElevatedButton(
              onPressed: _makePayment, // Trigger payment of 0.1 cent
              child: const Text('Make 0.1 Cent Payment'),
            ),
            Text(_cardInfo), // Display payment result
            const SizedBox(height: 30),
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 30),
              child: TextField(
                controller: textEditingController,
                decoration: const InputDecoration(
                  labelText: 'Enter UserName',
                ),
              ),
            ),
            const SizedBox(height: 30),
            ElevatedButton(
              onPressed: () async {
                final userName = textEditingController.text;
                await callNativeCode(userName);
                setState(() {});
              },
              child: const Text('Send Data'),
            ),
            Text(resultText),
          ],
        ),
      ),
    );
  }

  // Method to send user data to the native code
  Future<void> callNativeCode(String userName) async {
    try {
      resultText =
          await platform.invokeMethod('userName', {'username': userName});
      setState(() {});
    } catch (e) {
      print("Failed to send user data: $e");
    }
  }
}

Android code =

package com.example.np_casse

import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.os.BatteryManager
import androidx.annotation.NonNull
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import com.stripe.stripeterminal.Terminal
import com.stripe.stripeterminal.external.callable.*
import com.stripe.stripeterminal.external.models.*
import com.stripe.stripeterminal.log.LogLevel
import android.util.Log
import com.stripe.stripeterminal.external.models.Reader

class MainActivity : FlutterActivity() {
    private val CHANNEL = "com.example.case/stripe"
    private var terminalInitialized = false

    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)

        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
            when (call.method) {
                "initializeStripe" -> initializeTerminal(result)
                "discoverReaders" -> {
                    if (terminalInitialized) {
                        discoverReaders(result)
                    } else {
                        result.error("TERMINAL_NOT_INITIALIZED", "Terminal must be initialized first", null)
                    }
                }
                "scanCard" -> {
                    if (terminalInitialized) {
                        startCardScan(result)
                    } else {
                        result.error("TERMINAL_NOT_INITIALIZED", "Terminal must be initialized first", null)
                    }
                }
                "makePayment" -> {
                    if (terminalInitialized) {
                        makePayment(result)
                    } else {
                        result.error("TERMINAL_NOT_INITIALIZED", "Terminal must be initialized first", null)
                    }
                }
                "getBatteryLevel" -> getBatteryLevel(result)
                "getConnectedDeviceInfo" -> getConnectedDeviceInfo(result) // Fetch device info
                else -> result.notImplemented()
            }
        }
    }

    private fun initializeTerminal(result: MethodChannel.Result) {
        if (!Terminal.isInitialized()) {
            try {
                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")
        }
    }

    private fun discoverReaders(result: MethodChannel.Result) {
        if (checkLocationPermissions() && checkBluetoothPermissions()) {
            val discoveryConfig = DiscoveryConfiguration.BluetoothDiscoveryConfiguration(isSimulated = false)
            val discoveryCallback = object : Callback {
                override fun onSuccess() {
                    result.success("Reader discovery started")
                }

                override fun onFailure(e: TerminalException) {
                    result.error("DISCOVERY_ERROR", e.message, null)
                }
            }

            val discoveryListener = object : DiscoveryListener {
                override fun onUpdateDiscoveredReaders(readers: List<Reader>) {
                    if (readers.isEmpty()) {
                        result.success("No readers detected.")
                    } else {
                        for (reader in readers) {
                            result.success("Reader found: ${reader.label}")
                        }
                    }
                }
            }

            Terminal.getInstance().discoverReaders(discoveryConfig, discoveryListener, discoveryCallback)
        } else {
            requestPermissions()
        }
    }

    // New method to get connected device info
    private fun getConnectedDeviceInfo(result: MethodChannel.Result) {
        val reader: Reader? = Terminal.getInstance().connectedReader
if (reader != null) {
    val deviceInfo = "Label: ${reader.label}, Serial Number: ${reader.serialNumber}"
    result.success(deviceInfo)
} else {
    result.success("No reader connected")
}
    }

    private fun startCardScan(result: MethodChannel.Result) {
        val reader = Terminal.getInstance().connectedReader
        if (reader != null) {
            val paymentIntentParams = PaymentIntentParameters.Builder(listOf(PaymentMethodType.CARD_PRESENT))
                .setAmount(1)
                .setCurrency("usd")
                .build()

            Terminal.getInstance().createPaymentIntent(paymentIntentParams, object : PaymentIntentCallback {
                override fun onSuccess(paymentIntent: PaymentIntent) {
                    Terminal.getInstance().collectPaymentMethod(paymentIntent, object : PaymentIntentCallback {
                        override fun onSuccess(paymentIntent: PaymentIntent) {
                            Terminal.getInstance().confirmPaymentIntent(paymentIntent, object : PaymentIntentCallback {
                                override fun onSuccess(paymentIntent: PaymentIntent) {
                                    result.success("Payment Successful")
                                }

                                override fun onFailure(e: TerminalException) {
                                    result.error("PAYMENT_CONFIRM_ERROR", "Error confirming payment: ${e.message}", null)
                                }
                            })
                        }

                        override fun onFailure(e: TerminalException) {
                            result.error("PAYMENT_METHOD_ERROR", "Error collecting payment method: ${e.message}", null)
                        }
                    })
                }

                override fun onFailure(e: TerminalException) {
                    result.error("PAYMENT_INTENT_ERROR", "Error creating payment intent: ${e.message}", null)
                }
            })
        } else {
            result.error("NO_READER", "No reader connected", null)
        }
    }

    private fun makePayment(result: MethodChannel.Result) {
        val reader = Terminal.getInstance().connectedReader
        if (reader != null) {
            val paymentIntentParams = PaymentIntentParameters.Builder(listOf(PaymentMethodType.CARD_PRESENT))
                .setAmount(1)
                .setCurrency("usd")
                .build()

            Terminal.getInstance().createPaymentIntent(paymentIntentParams, object : PaymentIntentCallback {
                override fun onSuccess(paymentIntent: PaymentIntent) {
                    Terminal.getInstance().collectPaymentMethod(paymentIntent, object : PaymentIntentCallback {
                        override fun onSuccess(paymentIntent: PaymentIntent) {
                            Terminal.getInstance().confirmPaymentIntent(paymentIntent, object : PaymentIntentCallback {
                                override fun onSuccess(paymentIntent: PaymentIntent) {
                                    result.success("Payment Successful")
                                }

                                override fun onFailure(e: TerminalException) {
                                    result.error("PAYMENT_CONFIRM_ERROR", "Error confirming payment: ${e.message}", null)
                                }
                            })
                        }

                        override fun onFailure(e: TerminalException) {
                            result.error("PAYMENT_METHOD_ERROR", "Error collecting payment method: ${e.message}", null)
                        }
                    })
                }

                override fun onFailure(e: TerminalException) {
                    result.error("PAYMENT_INTENT_ERROR", "Error creating payment intent: ${e.message}", null)
                }
            })
        } else {
            result.error("NO_READER", "No reader connected", null)
        }
    }

    private fun getBatteryLevel(result: MethodChannel.Result) {
        val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
        val batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
        result.success(batteryLevel)
    }

    private fun checkLocationPermissions(): Boolean {
        return ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
    }

    private fun checkBluetoothPermissions(): Boolean {
        return ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH) == PackageManager.PERMISSION_GRANTED
    }

    private fun requestPermissions() {
        ActivityCompat.requestPermissions(
            this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.BLUETOOTH), 1
        )
    }
}

I tried different things but I am not able to find any solution. If someone had faces same issue and knows how to fix it then please let me know. Thank you


Solution

  • reader.label does not exist. you can check for serialNumber and deviceType. For me it is working like that =

           if (checkPermissions()) {
                if (BluetoothAdapter.getDefaultAdapter()?.isEnabled == false) {
                    BluetoothAdapter.getDefaultAdapter().enable()
                }
    
                val discoveryConfig = DiscoveryConfiguration.BluetoothDiscoveryConfiguration(isSimulated = false)
                val discoveredReaders = mutableListOf<Reader>()
    
                val discoveryListener = object : DiscoveryListener {
        override fun onUpdateDiscoveredReaders(readers: List<Reader>) {
            if (readers.isNotEmpty()) {
                val firstReader = readers.first()
    
                // Sending the first reader's details
                val readerInfo = mapOf(
                    "serialNumber" to (firstReader.serialNumber ?: "Unknown"),
                    "deviceType" to (firstReader.deviceType.name ?: "Unknown")
                )
                
                // Send the first reader to Flutter
                result.success(readerInfo)
                
            } else {
                result.success(null) // Send null if no readers are found
            }
        }
    }