Flutter支付终端插件stripe_terminal的使用

发布于 1周前 作者 yibo5220 来自 Flutter

Flutter支付终端插件stripe_terminal的使用

stripe_terminal 是一个用于扫描 Stripe 读卡器并连接到它们以获取支付方法的 Flutter 插件。本文将介绍如何在 Flutter 应用中使用该插件。

安装

Android

无需配置,开箱即用。

iOS

需要在 Info.plist 文件中提供权限请求字符串,并授权后台模式。

示例内容:

<key>NSLocationWhenInUseUsageDescription</key>
<string>Location access is required in order to accept payments.</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>Bluetooth access is required in order to connect to supported bluetooth card readers.</string>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app uses Bluetooth to connect to supported card readers.</string>

后台模式授权:

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

使用

初始化 SDK

首先需要初始化 SDK 并获取连接令牌。

import 'package:stripe_terminal/stripe_terminal.dart';

StripeTerminal stripeTerminal = StripeTerminal(
  fetchToken: () async {
    // 调用后端 API 获取连接令牌
    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 digit is ${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 上的蓝牙设备)
  • 检查连接状态
  • 检查已连接的设备
  • 从设备读取支付方式

缺少的功能

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

示例 Demo

以下是一个完整的示例代码,展示了如何使用 stripe_terminal 插件:

import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:stripe_terminal/stripe_terminal.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(baseUrl: "YOUR_BACKEND_URL"));

  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");
    return invoice.data['data']["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"),
            ),
            ListTile(
              onTap: () async {
                await Permission.bluetooth.request();
                await Permission.bluetoothScan.request();
                await Permission.bluetoothConnect.request();
              },
              title: const Text("Ask Bluetooth Permission"),
            ),
            TextButton(child: const Text("Init Stripe"), onPressed: _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("Connected 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.connectBluetoothReader(e.serialNumber, locationId: "your_location_id").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("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");
                });
              },
            ),
          ],
        ),
      ),
    );
  }

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

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

1 回复

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


当然,下面是一个关于如何在Flutter应用中使用stripe_terminal插件来实现支付终端功能的代码示例。请注意,实际使用中你需要确保已经正确设置了Stripe账户和相关的API密钥。

首先,确保你的Flutter项目已经添加了stripe_terminal依赖。在pubspec.yaml文件中添加以下依赖:

dependencies:
  flutter:
    sdk: flutter
  stripe_terminal: ^x.y.z  # 替换为最新版本号

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

接下来,我们将展示一个基本的Flutter应用,它使用stripe_terminal插件来连接到一个支付终端并执行支付。

1. 初始化Stripe Terminal

在你的主Dart文件中(例如main.dart),你需要初始化Stripe Terminal并设置必要的回调。

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

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

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  StripeTerminal? _stripeTerminal;
  String _status = '';

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

  Future<void> _initStripeTerminal() async {
    // 替换为你的Stripe Secret Key
    String publishableKey = 'your_publishable_key';
    _stripeTerminal = StripeTerminal(
      options: StripeTerminalOptions(
        publishableKey: publishableKey,
      ),
    );

    _stripeTerminal!.addListener(_stripeTerminalListener);

    try {
      await _stripeTerminal!.discoverReaders();
      setState(() {
        _status = 'Readers discovered';
      });
    } catch (e) {
      setState(() {
        _status = 'Failed to discover readers: $e';
      });
    }
  }

  void _stripeTerminalListener() {
    // 监听Stripe Terminal的状态变化
    setState(() {
      _status = _stripeTerminal!.status.toString();
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Stripe Terminal Example'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text('Status: $_status'),
              SizedBox(height: 20),
              ElevatedButton(
                onPressed: _connectReader,
                child: Text('Connect Reader'),
              ),
              SizedBox(height: 20),
              ElevatedButton(
                onPressed: _collectPaymentMethod,
                child: Text('Collect Payment Method'),
                enabled: _stripeTerminal!.connectedReader != null,
              ),
            ],
          ),
        ),
      ),
    );
  }

  Future<void> _connectReader() async {
    if (_stripeTerminal!.readers.isNotEmpty) {
      try {
        await _stripeTerminal!.connectReader(_stripeTerminal!.readers.first.serialNumber);
        setState(() {
          _status = 'Reader connected';
        });
      } catch (e) {
        setState(() {
          _status = 'Failed to connect reader: $e';
        });
      }
    } else {
      setState(() {
        _status = 'No readers available';
      });
    }
  }

  Future<void> _collectPaymentMethod() async {
    if (_stripeTerminal!.connectedReader != null) {
      try {
        PaymentIntentResult result = await _stripeTerminal!.collectPaymentMethod(
          PaymentMethodParameters(
            amount: 1000,  // 支付金额,单位:分
            currency: 'usd',
          ),
        );
        setState(() {
          _status = 'Payment successful: ${result.paymentIntent.id}';
        });
      } catch (e) {
        setState(() {
          _status = 'Failed to collect payment method: $e';
        });
      }
    } else {
      setState(() {
        _status = 'No reader connected';
      });
    }
  }

  @override
  void dispose() {
    _stripeTerminal?.removeListener(_stripeTerminalListener);
    _stripeTerminal?.cancelConnection();
    super.dispose();
  }
}

注意事项

  1. API密钥:确保你使用的是你的Stripe发布密钥(publishable key),而不是你的秘密密钥(secret key)。秘密密钥不应该在客户端代码中暴露。

  2. 支付金额:在_collectPaymentMethod方法中,amount字段表示支付金额,单位为分。例如,1000表示10美元。

  3. 错误处理:示例代码中对一些可能的错误进行了简单的处理,但在实际生产环境中,你可能需要更复杂的错误处理和用户反馈机制。

  4. 依赖管理:确保你的Flutter环境是最新的,并且所有依赖项都已正确安装。

  5. 硬件要求:为了使用Stripe Terminal,你需要一个兼容的支付读卡器。

  6. 支付处理:在服务器端处理支付结果,确保支付的安全性。

这个示例提供了一个基本的框架,展示了如何在Flutter应用中使用Stripe Terminal插件。根据你的具体需求,你可能需要进一步扩展和定制代码。

回到顶部