Flutter财务管理插件flutter_billpocket的使用

Flutter财务管理插件flutter_billpocket的使用

Billpocket插件

介绍Billpocket:简化Flutter应用中的移动卡支付和终端集成。

展示一些爱心并给仓库加星以支持项目

开始使用

Android

在Android设备上,你需要添加以下权限来请求蓝牙访问:

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

Dart

设置Billpocket的令牌和开发环境:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Billpocket.config(
      isProduction: true,
      token: '{YOUR_TOKEN}');
  runApp(const MyAppPage());
}

文档

SDK状态

你可以通过调用以下代码行来检查SDK初始化的状态:

await Billpocket.getStatusSDK();

这将返回一个bool值。

终端列表

你可以通过以下方法获取所有已通过蓝牙配对的终端列表:

await Billpocket.getReaders();

这将返回一个List<Reader>值。

连接终端

一旦你选择了一个终端,必须调用以下方法,并发送从Reader中获得的参数:

await Billpocket.connectReader(
          readerType: readerType,
          readerMacAddress: readerMacAddress,
          name: name);

这将返回一个bool值。

开始交易

开始交易的方法如下:

await Billpocket.doTransaction(
                              amount: "10",
                              tip: "0",
                              latitude: 19.42691938620286,
                              longitude: -99.16780320031096,
                              description: "description");

运行此方法将触发一个事件流。处理这些事件的方法如下:

Billpocket.transactionStream().listen((event) {
      final eventName = event['event'];
      final message = event['message'];

      switch (eventName) {
        case 'onTransactionAborted':
          // 处理onTransactionAborted事件
          print('Transaction aborted: $message');
          break;
        case 'onBeforeTransaction':
          // 处理onBeforeTransaction事件
          print('Transaction before: $message');
          break;
        case 'onCardRead':
          // 处理onCardRead事件
          print('Transaction card read: $message');
          break;
        case 'getSignature':
          // 处理getSignature事件
          print('Transaction get signature: $message');
          break;
        case 'onReaderWaitingForCard':
          // 处理onReaderWaitingForCard事件
          print('Transaction reader waiting for card: $message');
          break;
        case 'onMsiDefined':
          // 处理onMsiDefined事件
          final list = event['list'];
          print('Transaction msi defined: $message');
          print('MSI list: $list');
          showMSI(list);
          break;
        case 'onGetPin':
          // 处理onGetPin事件
          print('Transaction get pin: $message');
          break;
        case 'onMagneticCardFound':
          // 处理onMagneticCardFound事件
          print('Transaction magnetic card found: $message');
          break;
        case 'onTransactionFinished':
          // 处理onTransactionFinished事件
          print('Transaction finished: $message');
          break;
        case 'onTransactionSuccessful':
          // 处理onTransactionSuccessful事件
          print('Transaction successful: $message');
          break;
        case 'resultStartTransaction':
          // 处理resultStartTransaction事件
          print('Transaction result start: $message');
          break;
        case 'resultStartTransactionSuccess':
          // 处理resultStartTransactionSuccess事件
          print('Transaction result start success: $message');
          break;
        case 'resultStartTransactionError':
          // 处理resultStartTransactionError事件
          print('Transaction result start error: $message');
          break;
        // 其他事件类似处理
        default:
          print('Unknown event received: $eventName');
          break;
      }

事件解释

  • onGetPingetSignature: 这些方法会打开原生屏幕,因此无需处理响应。

  • onMsiDefined: 此方法将返回一个字符串,必须解析为List<Installment>。请查看示例

  • 其他方法: 这些方法是事务性的,取决于具体用例。

无息月数

如果你在doTransaction中输入的金额超过无息月数的限制,上述onMsiDefined事件将被触发。由于会得到一个列表,并且必须选择一个无息月数选项才能继续交易,因此需要调用以下方法并发送所选的参数:

Billpocket.continueWithMsi(
                            commission: installment[pos].commission!,
                            installments: installment[pos].value!,
                            minAmount: installment[pos].minAmount!,
                          );

调用该方法将继续在上述流中进行。

问题

请在GitHub仓库提交任何问题、错误或功能请求。

贡献

如果您希望对此仓库进行更改,请发送拉取请求

致谢

此包最初由Abel TarazonaJared González创建。

完整示例Demo

以下是完整的示例代码:

import 'dart:async';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_billpocket/billpocket.dart';
import 'package:flutter_billpocket/installment.dart';
import 'package:flutter_billpocket/reader.dart';
import 'package:permission_handler/permission_handler.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Billpocket.config(
      isProduction: true,
      token: '{YOUR_TOKEN}');
  runApp(const MyAppPage());
}

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

  [@override](/user/override)
  Widget build(BuildContext context) {
    return const MaterialApp(home: MyApp());
  }
}

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

  [@override](/user/override)
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  bool _statusSDK = false;
  bool _statusBluetooth = false;
  bool _statusConnection = false;
  List<Reader> _readers = [];
  StreamSubscription<Map<dynamic, dynamic>>? _subscription;
  TextEditingController amountController = TextEditingController();

  List<String> log = [];

  [@override](/user/override)
  void initState() {
    super.initState();
    getStatusSDK();
    getStatusBluetoothPermission();
    listenTransactionStream();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Billpocket'),
        actions: [
          IconButton(
            onPressed: () async {
              final log = await Billpocket.getLogs();
              if (context.mounted) {
                _showLogs(context, log);
              }
            }, 
            icon: const Text('Logs')
          )
        ],
      ),
      body: Padding(
        padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 15),
        child: SingleChildScrollView(
          physics: const BouncingScrollPhysics(),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const Text(
                'Estados',
                style: TextStyle(fontWeight: FontWeight.bold),
              ),
              const SizedBox(
                height: 20,
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceAround,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Status(
                    title: 'SDK',
                    isActive: _statusSDK,
                  ),
                  Status(
                    title: 'Bluetooth',
                    isActive: _statusBluetooth,
                  ),
                  Status(
                    title: 'Terminal \nConnection',
                    isActive: _statusConnection,
                  )
                ],
              ),
              const SizedBox(
                height: 10,
              ),
              const Divider(),
              const SizedBox(
                height: 10,
              ),
              const Text(
                'Opciones',
                style: TextStyle(fontWeight: FontWeight.bold),
              ),
              const SizedBox(
                height: 20,
              ),
              Row(
                children: [
                  Expanded(
                    child: ElevatedButton(
                        onPressed: () async {
                          await [
                            Permission.bluetoothScan,
                            Permission.bluetoothAdvertise,
                            Permission.bluetoothConnect
                          ].request();

                          if (await Permission.bluetoothConnect
                              .request()
                              .isGranted) {
                            setState(() {
                              _statusBluetooth = true;
                            });
                          }
                        },
                        child: const Text('Activar Bluetooth')),
                  ),
                  const SizedBox(
                    width: 20,
                  ),
                  Expanded(
                    child: ElevatedButton(
                        style: ButtonStyle(
                            backgroundColor:
                                MaterialStateProperty.all(Colors.brown)),
                        onPressed: () async {
                          getListReaders();
                        },
                        child: const Text('Obtener terminales')),
                  ),
                ],
              ),
              const SizedBox(
                height: 10,
              ),
              const Divider(),
              const SizedBox(
                height: 10,
              ),
              const Text(
                'Realizar cobro',
                style: TextStyle(fontWeight: FontWeight.bold),
              ),
              const SizedBox(
                height: 20,
              ),
              TextField(
                controller: amountController,
                keyboardType: TextInputType.phone,
                inputFormatters: [
                  FilteringTextInputFormatter.allow(RegExp(r"[0-9.]"))
                ],
                decoration: const InputDecoration.collapsed(
                  hintText: 'Monto',
                ),
              ),
              const SizedBox(
                height: 20,
              ),
              Row(
                children: [
                  Expanded(
                    child: ElevatedButton(
                        style: ButtonStyle(
                            backgroundColor:
                                MaterialStateProperty.all(Colors.green)),
                        onPressed: () async {
                          FocusManager.instance.primaryFocus?.unfocus();
                          setState(() {
                            log.clear();
                          });
                          await Billpocket.doTransaction(
                              amount: amountController.text,
                              tip: "0",
                              latitude: 19.42691938620286,
                              longitude: -99.16780320031096,
                              description: "description");
                        },
                        child: const Text('Cobrar')),
                  ),
                ],
              ),
              const SizedBox(
                height: 20,
              ),
              const Text('Log de Transacción',
                  style: TextStyle(fontWeight: FontWeight.bold)),
              const SizedBox(
                height: 10,
              ),
              ListView.separated(
                itemBuilder: (context, pos) {
                  if (pos == 0) {
                    return Row(children: [
                      const Icon(Icons.arrow_forward_ios_rounded, size: 15, color: Colors.lightGreen,),
                      const SizedBox(width: 10,),
                      Expanded(child: Text(log[pos], style: const TextStyle(color: Colors.lightGreen, fontWeight: FontWeight.bold),))
                    ],);
                  }
                  return Text(log[pos], style: const TextStyle(color: Colors.grey),);
                },
                itemCount: log.length,
                shrinkWrap: true, separatorBuilder: (BuildContext context, int index) {
                  return Divider();
              },
              )
            ],
          ),
        ),
      ),
    );
  }

  Future<void> _showLogs(BuildContext context, String log) {
    return showDialog(
      context: context, 
      builder: (context) => AlertDialog(
        content: Column(
          children: [
            const Text('LOGS'),
            Expanded(
              child: SingleChildScrollView(
                child: Text(log.isEmpty ? 'Sin logs' : log)
              )
            ),
          ],
        ),
      )
    );
  }

  Future<void> getStatusSDK() async {
    bool statusSDK;
    // 平台消息可能失败,所以我们使用try/catch处理PlatformException。
    // 我们还处理消息可能返回null的情况。
    try {
      statusSDK = await Billpocket.getStatusSDK();
    } on PlatformException {
      statusSDK = false;
    }

    // 如果小部件在异步平台消息还在飞行时从树中移除,我们想丢弃回复而不是调用setState更新我们的不存在的外观。
    if (!mounted) return;

    setState(() {
      _statusSDK = statusSDK;
    });
  }

  getListReaders() async {
    List<Reader> readers;

    try {
      readers = await Billpocket.getReaders();
    } on PlatformException {
      readers = [];
    }

    if (!mounted) return;

    setState(() {
      _readers = readers;
      showTerminals();
    });
  }

  connectReader(
      {required int readerType,
      required String readerMacAddress,
      required String name}) async {
    bool statusConnection;

    try {
      statusConnection = await Billpocket.connectReader(
          readerType: readerType,
          readerMacAddress: readerMacAddress,
          name: name);
    } on PlatformException {
      statusConnection = false;
    }

    if (!mounted) return;

    setState(() {
      _statusConnection = statusConnection;
    });
  }

  getStatusBluetoothPermission() async {
    var status = await Permission.bluetoothScan.status;
    if (status.isGranted) {
      setState(() {
        _statusBluetooth = true;
      });
    } else {
      setState(() {
        _statusBluetooth = false;
      });
    }
  }

  void listenTransactionStream() {
    _subscription = Billpocket.transactionStream().listen((event) {
      final eventName = event['event'];
      final message = event['message'];

      switch (eventName) {
        case 'onTransactionAborted':
          // 处理onTransactionAborted事件
          print('Transaction aborted: $message');
          break;
        case 'onBeforeTransaction':
          // 处理onBeforeTransaction事件
          print('Transaction before: $message');
          break;
        case 'onCardRead':
          // 处理onCardRead事件
          print('Transaction card read: $message');
          break;
        case 'getSignature':
          // 处理getSignature事件
          print('Transaction get signature: $message');
          break;
        case 'onReaderWaitingForCard':
          // 处理onReaderWaitingForCard事件
          print('Transaction reader waiting for card: $message');
          break;
        case 'onMsiDefined':
          // 处理onMsiDefined事件
          final list = event['list'];
          print('Transaction msi defined: $message');
          print('MSI list: $list');
          showMSI(list);
          break;
        case 'onGetPin':
          // 处理onGetPin事件
          print('Transaction get pin: $message');
          break;
        case 'onMagneticCardFound':
          // 处理onMagneticCardFound事件
          print('Transaction magnetic card found: $message');
          break;
        case 'onTransactionFinished':
          // 处理onTransactionFinished事件
          print('Transaction finished: $message');
          break;
        case 'onTransactionSuccessful':
          // 处理onTransactionSuccessful事件
          print('Transaction successful: $message');
          break;
        case 'resultStartTransaction':
          // 处理resultStartTransaction事件
          print('Transaction result start: $message');
          break;
        case 'resultStartTransactionSuccess':
          // 处理resultStartTransactionSuccess事件
          print('Transaction result start success: $message');
          break;
        case 'resultStartTransactionError':
          // 处理resultStartTransactionError事件
          print('Transaction result start error: $message');
          break;
        // 其他事件类似处理
        default:
          print('Unknown event received: $eventName');
          break;
      }

      setState(() {
        log.insert(0, message);
      });
    }, onError: (error) {
      print("Error received: $error");
    });
  }

  [@override](/user/override)
  void dispose() {
    _subscription?.cancel();
    super.dispose();
  }

  void showTerminals() {
    showModalBottomSheet<void>(
        context: context,
        builder: (context) {
          return Padding(
            padding: const EdgeInsets.all(8.0),
            child: SizedBox(
              child: ListView.separated(
                itemBuilder: (context, pos) => Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Text(
                      _readers[pos].name,
                      style: const TextStyle(
                          fontWeight: FontWeight.bold, fontSize: 17),
                    ),
                    ElevatedButton(
                        style: ButtonStyle(
                            backgroundColor:
                                MaterialStateProperty.all(Colors.orange)),
                        onPressed: () {
                          Navigator.pop(context);
                          connectReader(
                              readerType: _readers[pos].type,
                              readerMacAddress: _readers[pos].macAddress,
                              name: _readers[pos].name);
                        },
                        child: const Text('Conectar'))
                  ],
                ),
                itemCount: _readers.length,
                shrinkWrap: true,
                separatorBuilder: (BuildContext context, int index) {
                  return const Divider();
                },
              ),
            ),
          );
        });
  }

  void showMSI(list) {
    List<Installment> installment = List<Installment>.from(
        json.decode(list).map((x) => Installment.fromJson(x)));

    showModalBottomSheet<void>(
        context: context,
        builder: (context) {
          return Padding(
            padding: const EdgeInsets.all(8.0),
            child: SizedBox(
              child: ListView.separated(
                itemBuilder: (context, pos) => Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Text(
                      '${installment[pos].value.toString()} meses sin intereses',
                      style: const TextStyle(
                          fontWeight: FontWeight.bold, fontSize: 17),
                    ),
                    ElevatedButton(
                        style: ButtonStyle(
                            backgroundColor:
                                MaterialStateProperty.all(Colors.orange)),
                        onPressed: () {
                          Billpocket.continueWithMsi(
                            commission: installment[pos].commission!,
                            installments: installment[pos].value!,
                            minAmount: installment[pos].minAmount!,
                          );
                          Navigator.pop(context);
                        },
                        child: const Text('Elegir'))
                  ],
                ),
                itemCount: installment.length,
                shrinkWrap: true,
                separatorBuilder: (BuildContext context, int index) {
                  return const Divider();
                },
              ),
            ),
          );
        });
  }
}

class Status extends StatelessWidget {
  const Status({
    super.key,
    required this.isActive,
    required this.title,
  });

  final bool isActive;
  final String title;

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        Container(
          decoration: BoxDecoration(
              shape: BoxShape.circle,
              color: isActive ? Colors.green : Colors.red),
          width: 10,
          height: 10,
        ),
        const SizedBox(
          height: 10,
        ),
        Text(
          title,
          textAlign: TextAlign.center,
          style: const TextStyle(fontWeight: FontWeight.bold),
        )
      ],
    );
  }
}

更多关于Flutter财务管理插件flutter_billpocket的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter财务管理插件flutter_billpocket的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


flutter_billpocket 是一个用于财务管理的 Flutter 插件,它提供了与 Billpocket API 的集成,使开发者能够在 Flutter 应用中轻松处理支付、发票、交易等财务相关的操作。以下是使用 flutter_billpocket 插件的基本步骤和示例代码。

1. 添加依赖

首先,你需要在 pubspec.yaml 文件中添加 flutter_billpocket 插件的依赖:

dependencies:
  flutter:
    sdk: flutter
  flutter_billpocket: ^1.0.0  # 请使用最新版本

然后运行 flutter pub get 来获取依赖。

2. 配置 API 密钥

在使用 flutter_billpocket 之前,你需要在 Billpocket 平台上注册并获取 API 密钥。将 API 密钥配置到你的应用中:

import 'package:flutter_billpocket/flutter_billpocket.dart';

void main() {
  // 初始化插件并设置 API 密钥
  FlutterBillpocket.initialize(apiKey: 'YOUR_API_KEY');
  runApp(MyApp());
}

3. 处理支付

你可以使用 flutter_billpocket 来处理支付。以下是一个简单的支付示例:

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

class PaymentPage extends StatefulWidget {
  @override
  _PaymentPageState createState() => _PaymentPageState();
}

class _PaymentPageState extends State<PaymentPage> {
  final _amountController = TextEditingController();
  final _descriptionController = TextEditingController();

  Future<void> _processPayment() async {
    try {
      final amount = double.parse(_amountController.text);
      final description = _descriptionController.text;

      final response = await FlutterBillpocket.processPayment(
        amount: amount,
        description: description,
      );

      if (response.success) {
        // 支付成功
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('支付成功: ${response.transactionId}')),
        );
      } else {
        // 支付失败
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('支付失败: ${response.errorMessage}')),
        );
      }
    } catch (e) {
      // 处理异常
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('支付过程中发生错误: $e')),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('支付')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            TextField(
              controller: _amountController,
              decoration: InputDecoration(labelText: '金额'),
              keyboardType: TextInputType.number,
            ),
            TextField(
              controller: _descriptionController,
              decoration: InputDecoration(labelText: '描述'),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: _processPayment,
              child: Text('支付'),
            ),
          ],
        ),
      ),
    );
  }
}

4. 查询交易

你还可以使用 flutter_billpocket 来查询交易记录:

Future<void> _fetchTransactions() async {
  try {
    final transactions = await FlutterBillpocket.fetchTransactions();

    // 处理交易记录
    for (var transaction in transactions) {
      print('Transaction ID: ${transaction.id}, Amount: ${transaction.amount}');
    }
  } catch (e) {
    // 处理异常
    print('查询交易记录时发生错误: $e');
  }
}

5. 处理回调

flutter_billpocket 还支持支付回调的处理。你可以在应用中监听支付结果:

FlutterBillpocket.setPaymentCallback((response) {
  if (response.success) {
    // 支付成功
    print('支付成功: ${response.transactionId}');
  } else {
    // 支付失败
    print('支付失败: ${response.errorMessage}');
  }
});

6. 处理错误

在使用过程中,可能会遇到各种错误。你可以通过捕获异常来处理这些错误:

try {
  // 调用 API
} catch (e) {
  // 处理错误
  print('发生错误: $e');
}
回到顶部