Flutter支付终端管理插件stripe_terminalx的使用

Flutter支付终端管理插件stripe_terminalx的使用


stripe_terminalx

pub package

一个用于扫描Stripe读卡器并连接到它们以获取支付方式的Flutter插件。


安装

Android

无需配置,开箱即用。

iOS

您需要在Info.plist文件中提供权限请求字符串。示例内容如下:

<key>NSLocationWhenInUseUsageDescription</key>
<string>位置访问是为了接受付款所必需的。</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>蓝牙访问是为了连接支持的蓝牙卡读卡器所必需的。</string>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>此应用使用蓝牙连接到支持的卡读卡器。</string>

您还需要授权后台模式以启用bluetooth-central。将以下内容添加到您的Info.plist文件中:

<key>UIBackgroundModes</key>
<array>
    <string>bluetooth-central</string>
</array>

使用

初始化SDK

stripeTerminal = StripeTerminal(
  fetchToken: () async {
    // 调用后端以获取连接令牌并返回给此函数
    // 示例令牌可以是:
    const token = "pst_test_XXXXXXXXXX...."; 

    return token;
  },
);

示例后端代码(Node.js)

import Stripe from "stripe";
import express from "express";

const stripe = new Stripe("sk_test_XXXXXXXXXXXXXXXXXX", {
  apiVersion: "2020-08-27",
});

const app = express();

app.get("/connectionToken", async (req, res) => {
  const token = await stripe.terminal.connectionTokens.create();
  res.send({
    success: true,
    data: token.secret,
  });
});

app.post("/createPaymentIntent", async (req, res) => {
  const pi = await stripe.paymentIntents.create({
    amount: 1000,
    currency: "USD",
    capture_method: "manual",
    payment_method_types: ["card_present"],
  });

  res.send({
    success: true,
    paymentIntent: pi,
  });
});

app.listen(8000, () => {
  console.log("Server started");
});

发现附近的设备并显示给用户

stripeTerminal
    .discoverReaders(simulated: true)
    .listen((List<StripeReader> readers) {
  setState(() {
    this.readers = readers;
  });
});

连接到蓝牙读卡器

bool connected = await stripeTerminal.connectBluetoothReader(readers[0].serialNumber);
if (connected) {
  print("Connected to a device");
}

从读卡器读取卡片信息

stripeTerminal
    .readReusableCardDetail()
    .then((StripePaymentMethod paymentMethod) {
  print("A card was read, the last four digits are ${paymentMethod.card?.last4}");
});

使用刷卡、插卡或轻触方式从读卡器扫描支付方式

// 从后端获取此内容,创建新的支付意图
Future<String> createPaymentIntent() async {
  Response invoice = await _dio.post("/createPaymentIntent");
  return invoice.data["paymentIntent"]["client_secret"];
}

String payment_intent_client_secret = await createPaymentIntent();

stripeTerminal
    .collectPaymentMethod(payment_intent_client_secret)
    .then((StripePaymentIntent paymentIntent) {
  print("A payment intent has captured a payment method, send this payment intent to your backend to capture the payment");
});

完成!


当前支持的功能

  • 初始化终端SDK
  • 扫描读卡器
  • 连接到设备(仅限Android上的蓝牙设备)
  • 检查连接状态
  • 检查已连接的设备
  • 从设备读取支付方式

缺少的功能

  • 创建支付意图
  • 处理支付
  • 捕获支付

未来计划

如果您需要更多功能,请随时发送PR或在仓库中创建相关问题。

由于我无法长期维护此包,一旦flutter_stripe在其SDK中添加支持,此包将会被弃用。


支持创作者

免费开发软件需要时间和努力,请考虑买一杯咖啡支持我。这一定会让我开心,并激励我贡献更多。

Buy Me A Coffee

❤️ Sponsor


完整示例代码

以下是完整的示例代码,展示了如何使用stripe_terminalx插件。

import 'dart:convert';

import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:async';

import 'package:stripe_terminalx/stripe_terminalx.dart';

void main() {
  runApp(
    const MaterialApp(home: MyApp()),
  );
}

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

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

class _MyAppState extends State<MyApp> {
  final Dio _dio = Dio(
    BaseOptions(
      // TODO: THIS URL does not work
      baseUrl: "https://deb8-103-163-182-241.in.ngrok.io",
    ),
  );

  Future<String> getConnectionString() async {
    Response response = await _dio.get("/connectionToken");
    if (!(response.data)["success"]) {
      throw Exception(
        "Failed to get connection token because ${response.data["message"]}",
      );
    }

    return (response.data)["data"];
  }

  Future<void> _pushLogs(StripeLog log) async {
    debugPrint(log.code);
    debugPrint(log.message);
  }

  Future<String> createPaymentIntent() async {
    Response invoice = await _dio.post("/createPaymentIntent", data: {
      "email": "awazgyawali@gmail.com",
      "order": {"test": "1"},
      "ticketCount": 3,
      "price": 5,
    });
    return jsonDecode(invoice.data)["paymentIntent"]["client_secret"];
  }

  late StripeTerminal stripeTerminal;
  [@override](/user/override)
  void initState() {
    super.initState();
    _initStripe();
  }

  _initStripe() async {
    stripeTerminal = await StripeTerminal.getInstance(
      fetchToken: getConnectionString,
    );
    stripeTerminal.onNativeLogs.listen(_pushLogs);
  }

  bool simulated = true;

  StreamSubscription? _sub;
  List<StripeReader>? readers;
  String? paymentIntentId;
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Plugin example app'),
      ),
      body: Center(
        child: Column(
          children: [
            ListTile(
              onTap: () {
                setState(() {
                  simulated = !simulated;
                  _initStripe();
                });
              },
              title: const Text("Scanning mode"),
              trailing: Text(simulated ? "Simulator" : "Real"),
            ),
            TextButton(
              child: const Text("Init Stripe"),
              onPressed: () async {
                _initStripe();
              },
            ),
            TextButton(
              child: const Text("Get Connection Token"),
              onPressed: () async {
                String connectionToken = await getConnectionString();
                _showSnackbar(connectionToken);
              },
            ),
            if (_sub == null)
              TextButton(
                child: const Text("Scan Devices"),
                onPressed: () async {
                  setState(() {
                    readers = [];
                  });
                  _sub = stripeTerminal
                      .discoverReaders(
                    DiscoverConfig(
                      discoveryMethod: DiscoveryMethod.bluetooth,
                      simulated: simulated,
                    ),
                  )
                      .listen((readers) {
                    setState(() {
                      this.readers = readers;
                    });
                  });
                },
              ),
            if (_sub != null)
              TextButton(
                child: const Text("Stop Scanning"),
                onPressed: () async {
                  setState(() {
                    _sub?.cancel();
                    _sub = null;
                  });
                },
              ),
            TextButton(
              child: const Text("Connection Status"),
              onPressed: () async {
                stripeTerminal.connectionStatus().then((status) {
                  _showSnackbar("Connection status: ${status.toString()}");
                });
              },
            ),
            TextButton(
              child: const Text("Connected Device"),
              onPressed: () async {
                stripeTerminal
                    .fetchConnectedReader()
                    .then((StripeReader? reader) {
                  _showSnackbar("Connection Device: ${reader?.toJson()}");
                });
              },
            ),
            if (readers != null)
              ...readers!.map(
                (e) => ListTile(
                  title: Text(e.serialNumber),
                  trailing: Text(describeEnum(e.batteryStatus)),
                  leading: Text(e.locationId ?? "No Location Id"),
                  onTap: () async {
                    await stripeTerminal
                        .connectToReader(
                      e.serialNumber,
                      locationId: "tml_EoMcZwfY6g8btZ",
                    )
                        .then((value) {
                      _showSnackbar("Connected to a device");
                    }).catchError((e) {
                      if (e is PlatformException) {
                        _showSnackbar(e.message ?? e.code);
                      }
                    });
                  },
                  subtitle: Text(describeEnum(e.deviceType)),
                ),
              ),
            TextButton(
              child: const Text("Read Reusable Card Detail"),
              onPressed: () async {
                stripeTerminal
                    .readReusableCardDetail()
                    .then((StripePaymentMethod paymentMethod) {
                  _showSnackbar(
                    "A card was read: ${paymentMethod.card?.toJson()}",
                  );
                });
              },
            ),
            TextButton(
              child: const Text("Set reader display"),
              onPressed: () async {
                stripeTerminal.setReaderDisplay(
                  ReaderDisplay(
                    type: DisplayType.cart,
                    cart: DisplayCart(
                      currency: "USD",
                      tax: 130,
                      total: 1000,
                      lineItems: [
                        DisplayLineItem(
                          description: "hello 1",
                          quantity: 1,
                          amount: 500,
                        ),
                        DisplayLineItem(
                          description: "hello 2",
                          quantity: 1,
                          amount: 500,
                        ),
                      ],
                    ),
                  ),
                );
              },
            ),
            TextButton(
              child: const Text("Collect Payment Method"),
              onPressed: () async {
                paymentIntentId = await createPaymentIntent();
                stripeTerminal
                    .collectPaymentMethod(paymentIntentId!)
                    .then((StripePaymentIntent paymentIntent) async {
                  _dio.post("/confirmPaymentIntent", data: {
                    "paymentIntentId": paymentIntent.id,
                  });
                  _showSnackbar(
                    "A payment method was captured",
                  );
                });
              },
            ),
            TextButton(
              child: const Text("Misc Button"),
              onPressed: () async {
                StripeReader.fromJson(
                  {
                    "locationStatus": 2,
                    "deviceType": 3,
                    "serialNumber": "STRM26138003393",
                    "batteryStatus": 0,
                    "simulated": false,
                    "availableUpdate": false,
                  },
                );
              },
            ),
          ],
        ),
      ),
    );
  }

  _showSnackbar(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        behavior: SnackBarBehavior.floating,
        content: Text(
          message,
          style: const TextStyle(color: Colors.red),
        ),
      ),
    );
  }
}

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

1 回复

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


stripe_terminalx 是一个用于在 Flutter 应用中集成 Stripe 终端设备的插件。它允许你通过 Stripe Terminal SDK 来管理支付终端设备,如 Stripe Reader,从而处理面对面的支付交易。

以下是如何在 Flutter 中使用 stripe_terminalx 插件的基本步骤:

1. 添加依赖

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

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

然后运行 flutter pub get 来安装依赖。

2. 初始化 Stripe Terminal

在你的 Flutter 应用中,首先需要初始化 Stripe Terminal。通常,你可以在 main.dart 或某个初始化函数中进行初始化。

import 'package:stripe_terminalx/stripe_terminalx.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // 初始化 Stripe Terminal
  await StripeTerminalx.initialize(
    apiKey: 'your_stripe_secret_key',
  );

  runApp(MyApp());
}

3. 发现和连接终端设备

接下来,你可以使用 stripe_terminalx 来发现和连接 Stripe 终端设备。

import 'package:stripe_terminalx/stripe_terminalx.dart';

class PaymentScreen extends StatefulWidget {
  @override
  _PaymentScreenState createState() => _PaymentScreenState();
}

class _PaymentScreenState extends State<PaymentScreen> {
  List<Reader> _readers = [];
  Reader? _selectedReader;

  @override
  void initState() {
    super.initState();
    _discoverReaders();
  }

  Future<void> _discoverReaders() async {
    try {
      List<Reader> readers = await StripeTerminalx.discoverReaders();
      setState(() {
        _readers = readers;
      });
    } catch (e) {
      print('Error discovering readers: $e');
    }
  }

  Future<void> _connectReader(Reader reader) async {
    try {
      await StripeTerminalx.connectReader(reader);
      setState(() {
        _selectedReader = reader;
      });
    } catch (e) {
      print('Error connecting reader: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Stripe Terminal'),
      ),
      body: Column(
        children: [
          if (_readers.isNotEmpty)
            ..._readers.map((reader) {
              return ListTile(
                title: Text(reader.label),
                onTap: () => _connectReader(reader),
              );
            }).toList(),
          if (_selectedReader != null)
            Text('Connected to: ${_selectedReader!.label}'),
        ],
      ),
    );
  }
}

4. 处理支付

一旦你连接了终端设备,你可以开始处理支付。以下是一个简单的支付处理示例:

Future<void> _processPayment() async {
  try {
    // 创建一个支付意图
    PaymentIntent paymentIntent = await StripeTerminalx.createPaymentIntent(
      amount: 1000, // 金额以最小货币单位表示,例如 1000 表示 $10.00
      currency: 'usd',
    );

    // 开始支付流程
    await StripeTerminalx.collectPaymentMethod(paymentIntent);

    // 确认支付
    PaymentIntent confirmedIntent = await StripeTerminalx.confirmPaymentIntent(paymentIntent);

    if (confirmedIntent.status == 'succeeded') {
      print('Payment succeeded!');
    } else {
      print('Payment failed or was canceled.');
    }
  } catch (e) {
    print('Error processing payment: $e');
  }
}

5. 断开连接

在支付完成后,你可以断开与终端设备的连接:

Future<void> _disconnectReader() async {
  try {
    await StripeTerminalx.disconnectReader();
    setState(() {
      _selectedReader = null;
    });
  } catch (e) {
    print('Error disconnecting reader: $e');
  }
}
回到顶部