BreX900 / mek-packages

10 stars 13 forks source link

Cannot connect to BBPOS WisePad 3... #49

Closed hrueger closed 6 months ago

hrueger commented 6 months ago

Hi @BreX900, thanks for this library. It looks really cool! However, I'm not able to connect to my BBPOS WisePad 3. I'm using the very simple demo code from below. When using isSimulated: true, everything works as expected. However, when isSimulated: false, the following happens: the reader does appear in the list, when selecting it, I get the bluetooth popup and the reader now beeps and displays the bluetooth icon continuously. However, Connected to reader [...] is never printed, the connectBluetoothReader() method does not seem to return.

Do you have any idea what the problem could be? The properties of the reader say that there's no update available, though I don't know if this would even be set correctly before being fully connected.

Thanks in advance!

pubspec.yaml

name: pos
description: A new Flutter project.
publish_to: 'none'
version: 1.0.0+1

environment:
  sdk: '>=3.1.0 <4.0.0'
dependencies:
  flutter:
    sdk: flutter
  mek_stripe_terminal: ^3.2.1
  permission_handler: ^11.1.0
  stripe: ^4.11.0

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^2.0.0

flutter:
  uses-material-design: true

main.dart

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:mek_stripe_terminal/mek_stripe_terminal.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:stripe/stripe.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  Terminal? stripeTerminal;

  var _readers = const <Reader>[];

  Stripe stripe = Stripe(
      "[[redacted]]");

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            TextButton(
                onPressed: () async {
                  final permissions = [
                    Permission.locationWhenInUse,
                    Permission.bluetooth,
                    Permission.bluetoothScan,
                    Permission.bluetoothConnect,
                  ];
                  await permissions.request();
                  print("Got permissions");
                },
                child: const Text('1. Request Permissions')),
            const Divider(),
            TextButton(
              onPressed: () async {
                stripeTerminal = await Terminal.getInstance(
                  fetchToken: () async {
                    return await createTerminalConnectionToken();
                  },
                );
                print("Initialized Terminal");
              },
              child: const Text('2. Initialize Terminal'),
            ),
            const Divider(),
            TextButton(
              onPressed: () async {
                setState(() => _readers = const <Reader>[]);
                await stripeTerminal
                    ?.setSimulatorConfiguration(const SimulatorConfiguration());
                stripeTerminal
                    ?.discoverReaders(const BluetoothDiscoveryConfiguration(
                        isSimulated: false))
                    .listen((List<Reader> readers) {
                  setState(() => _readers = readers);
                });
              },
              child: const Text('3. Discover Readers'),
            ),
            ListView.builder(
              shrinkWrap: true,
              itemCount: _readers.length,
              itemBuilder: (BuildContext context, int index) {
                final reader = _readers[index];
                return ListTile(
                  title: Text(reader.serialNumber),
                  subtitle: Text(reader.batteryStatus.toString()),
                  onTap: () async {
                    try {
                      print('Connecting to reader ${reader.serialNumber}...');
                      await stripeTerminal?.connectBluetoothReader(reader,
                          locationId: "[[redacted]]");
                      print('Connected to reader ${reader.serialNumber}');
                    } catch (error, stackTrace) {
                      print('$error\n$stackTrace');
                    }
                  },
                );
              },
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }

  Future<String> createTerminalConnectionToken() async {
    try {
      final terminalToken =
          await stripe.client.post('/terminal/connection_tokens');
      print(jsonEncode(terminalToken));
      return terminalToken['secret'] as String;
    } catch (error, stackTrace) {
      print('$error\n$stackTrace');
      rethrow;
    }
  }
}
hrueger commented 6 months ago

One more thing I just noticed: the terminal does appear in the stripe dashboard. Therefore, the connection process must somehow work, the method just doesn't return...

Edit: It also works just fine with a WisePos E via Internet.

Edit2: I just managed to get a com.stripe.stripeterminal.external.models.TerminalException: Update failed due to low battery. I'll charge it up and try again!

hrueger commented 6 months ago

Never mind. Turns out: the automatic update after connecting can take very long (>20 minutes for me), I was just not patient enough. You only get an "Updating..." message on the screen after about 15 minutes. For anyone googling, I used the following code:

class MyPhysicalReaderDelegate extends PhysicalReaderDelegate {
  @override
  FutureOr<void> onStartInstallingUpdate(
      ReaderSoftwareUpdate update, Cancellable cancelUpdate) {
    print('Starting to install update ${update.version}...');
    return super.onStartInstallingUpdate(update, cancelUpdate);
  }

  @override
  FutureOr<void> onReportAvailableUpdate(ReaderSoftwareUpdate update) {
    print('Update ${update.version} is available');
    return super.onReportAvailableUpdate(update);
  }

  @override
  FutureOr<void> onReportReaderEvent(ReaderEvent event) {
    print('Received reader event: $event');
    return super.onReportReaderEvent(event);
  }

  @override
  FutureOr<void> onRequestReaderDisplayMessage(ReaderDisplayMessage message) {
    print('Received reader display message: $message');
    return super.onRequestReaderDisplayMessage(message);
  }

  @override
  FutureOr<void> onRequestReaderInput(List<ReaderInputOption> options) {
    print('Received reader input request: $options');
    return super.onRequestReaderInput(options);
  }

  @override
  FutureOr<void> onReportBatteryLevelUpdate(
    double batteryLevel,
    BatteryStatus? batteryStatus,
    bool isCharging,
  ) {
    print('Received battery level update: $batteryLevel');
    return super
        .onReportBatteryLevelUpdate(batteryLevel, batteryStatus, isCharging);
  }

  @override
  FutureOr<void> onReportLowBatteryWarning() {
    print('Received low battery warning');
    return super.onReportLowBatteryWarning();
  }

  @override
  FutureOr<void> onReportReaderSoftwareUpdateProgress(double progress) {
    print('Received reader software update progress: $progress');
    return super.onReportReaderSoftwareUpdateProgress(progress);
  }

  @override
  FutureOr<void> onFinishInstallingUpdate(
    ReaderSoftwareUpdate? update,
    TerminalException? exception,
  ) {
    if (exception != null) {
      print('Failed to install update ${update?.version}: $exception');
    } else {
      print('Finished installing update ${update?.version}');
    }
    return super.onFinishInstallingUpdate(update, exception);
  }
}
PhysicalReaderDelegate delegate = MyPhysicalReaderDelegate();
print('Connecting to reader ${reader.serialNumber}...');
await stripeTerminal?.connectBluetoothReader(reader,
    delegate: delegate, locationId: "tml_FZ0cHwWA4UgWlC");
print('Connected to reader ${reader.serialNumber}');