Flutter支付集成插件payjoin_flutter的使用

Flutter支付集成插件payjoin_flutter的使用

payjoin_flutter 是一个用于在 Flutter 应用中集成支付功能的库。它基于 Payjoin Dev Kit 实现,支持比特币支付协议。

如何使用

要在项目中使用 payjoin_flutter 插件,请将其添加为依赖项到项目的 pubspec.yaml 文件中:

dependencies:
  payjoin_flutter: 0.20.0

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

要求

  • Flutter 版本:3.0 或更高版本。
  • Android 最低 SDK 版本:API 23 或更高版本。
  • iOS 部署目标:12.0 或更高版本。

构建和运行代码

首先,需要在本地设置 bitcoin coreesploraregtest 网络中。如果没有这些工具,可以参考以下页面进行安装:

或者可以安装 Nigiri Bitcoin 工具来简化运行本地实例的过程。你可以参考以下链接进行安装:

一旦 Nigiri Bitcoin 启动运行,你需要挖一些区块。可以参考以下链接了解如何挖区块:

运行集成测试

Regtest 网络中正确设置了 Bitcoin core 后,可以通过以下步骤运行集成测试:

git clone https://github.com/LtbLightning/payjoin-flutter.git
cd payjoin-flutter

cd example
# 运行集成测试
flutter test integration_test

示例代码

以下是一个完整的示例代码,展示了如何使用 payjoin_flutter 插件实现支付功能。

import 'dart:convert';

import 'package:bdk_flutter/bdk_flutter.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:payjoin_flutter/common.dart' as common;
import 'package:payjoin_flutter/uri.dart' as pay_join_uri;
import 'package:payjoin_flutter_example/bdk_client.dart';
import 'package:payjoin_flutter_example/payjoin_library.dart';

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

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

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

class _MyAppState extends State<MyApp> {
  [@override](/user/override)
  void initState() {
    super.initState();
  }

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

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

  [@override](/user/override)
  State<PayJoin> createState() => _PayJoinState();
}

class _PayJoinState extends State<PayJoin> {
  static const primaryColor = 0xffC71585;
  PayJoinLibrary payJoinLibrary = PayJoinLibrary();
  final sender = BdkClient(
      "wpkh(tprv8ZgxMBicQKsPdgsqhkRVYkBBULxG3HvyXtwhWKEgfH4bsU8bmaqhdbZvxq4Z7BLFtUrT58ynRDrBcfG3vNpNHsKTV5xCEgRoKaNNzcVW3HW/84'/1'/0'/0/*)#ln3hfgcf",
      Network.signet);
  final receiver = BdkClient(
      "wpkh(tprv8ZgxMBicQKsPfKJjrApLfm2BhWhV1JpL3StS8UPagm91Y215JGZktQKTtvErD92RKxEDYD9Sfc9eGZVkuH94NgEHPhz7rpgzhiNm2UPs1G1/84'/1'/0'/0/*)#h8uywf09",
      Network.signet);

  String displayText = "";
  String pjUri = "";
  late PartiallySignedTransaction senderPsbt;
  late PartiallySignedTransaction processedAndFinalizedPsbt;

  [@override](/user/override)
  void initState() {
    sender.restoreWallet();
    receiver.restoreWallet();
    setState(() {
      displayText = "sender & receiver restored";
    });
    super.initState();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: const Color(primaryColor),
        elevation: 0,
        centerTitle: false,
        title: Text('PayJoin App',
            style: GoogleFonts.ibmPlexMono(
                fontWeight: FontWeight.w900,
                fontSize: 18,
                color: Colors.white)), // 设置标题高度
      ),
      body: SingleChildScrollView(
        child: Column(
          children: [
            Container(
              margin: const EdgeInsets.only(bottom: 50),
              padding: const EdgeInsets.only(left: 15, right: 15, bottom: 20),
              color: const Color(primaryColor),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.start,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text("Response: ",
                      textAlign: TextAlign.center,
                      style: GoogleFonts.manrope(
                          color: Colors.white,
                          fontSize: 12,
                          fontWeight: FontWeight.w700)),
                  Expanded(
                    child: SelectableText(
                      displayText,
                      maxLines: 3,
                      textAlign: TextAlign.start,
                      style: GoogleFonts.ibmPlexMono(
                          color: Colors.white,
                          fontSize: 12,
                          fontWeight: FontWeight.w700),
                    ),
                  ),
                ],
              ),
            ),
            TextButton(
                onPressed: () async {
                  await sender.syncWallet();
                  await receiver.syncWallet();
                  setState(() {
                    displayText = "sync complete";
                  });
                  debugPrint(
                      "sender balance: ${(sender.getBalance()).toString()}");
                },
                child: Text(
                  "Sync wallets",
                  style: GoogleFonts.manrope(
                      color: Colors.black,
                      fontSize: 14,
                      fontWeight: FontWeight.w800),
                )),
            TextButton(
                onPressed: () async {
                  final address = (receiver.getNewAddress()).address;
                  final res = await payJoinLibrary.buildPjUri(
                      0.0083285, address.toQrUri());
                  setState(() {
                    pjUri = res;
                    displayText = res;
                  });
                },
                child: Text(
                  "Build Receiver pj Uri",
                  style: GoogleFonts.manrope(
                      color: Colors.black,
                      fontSize: 14,
                      fontWeight: FontWeight.w800),
                )),
            TextButton(
                onPressed: () async {
                  final balance = sender.getBalance();
                  debugPrint("Sender Balance: ${balance.toString()}");
                  final uri = await pay_join_uri.Uri.fromStr(pjUri);
                  final address = uri.address();
                  int amount = (((uri.amount()) ?? 0) * 100000000).toInt();
                  final psbt = (await sender.createPsbt(address, amount, 2000));
                  debugPrint(
                    "\nOriginal sender psbt: ${psbt.toString()}",
                  );
                  setState(() {
                    senderPsbt = psbt;
                  });
                },
                child: Text(
                  "Create Sender psbt using receiver pjUri",
                  style: GoogleFonts.manrope(
                      color: Colors.black,
                      fontSize: 14,
                      fontWeight: FontWeight.w800),
                )),
            TextButton(
                onPressed: () async {
                  final (provisionalProposal, contextV1) = await payJoinLibrary
                      .handlePjRequest(senderPsbt.toString(), pjUri, (e) async {
                    final script = ScriptBuf(bytes: e);

                    return (receiver.getAddressInfo(script));
                  });
                  final unspent = receiver.listUnspent();
                  // 选择接收方的 payjoin 输入。
                  Map<BigInt, common.OutPoint> candidateInputs = {
                    for (var input in unspent)
                      input.txout.value: common.OutPoint(
                          txid: input.outpoint.txid.toString(),
                          vout: input.outpoint.vout)
                  };
                  final selectedOutpoint = await provisionalProposal
                      .tryPreservingPrivacy(candidateInputs: candidateInputs);
                  var selectedUtxo = unspent.firstWhere(
                      (i) =>
                          i.outpoint.txid.toString() == selectedOutpoint.txid &&
                          i.outpoint.vout == selectedOutpoint.vout,
                      orElse: () => throw Exception('UTXO not found'));
                  var txoToContribute = common.TxOut(
                    value: selectedUtxo.txout.value,
                    scriptPubkey: selectedUtxo.txout.scriptPubkey.bytes,
                  );

                  var outpointToContribute = common.OutPoint(
                    txid: selectedUtxo.outpoint.txid.toString(),
                    vout: selectedUtxo.outpoint.vout,
                  );

                  await provisionalProposal.contributeWitnessInput(
                      txo: txoToContribute, outpoint: outpointToContribute);
                  final payJoinProposal = await provisionalProposal
                      .finalizeProposal(processPsbt: (e) async {
                    debugPrint("\n Original receiver unsigned psbt: $e");
                    return (await receiver.signPsbt(
                            await PartiallySignedTransaction.fromString(e)))
                        .toString();
                  });
                  final receiverPsbt = await payJoinProposal.psbt();
                  debugPrint("\n Original receiver psbt: $receiverPsbt");
                  final receiverProcessedPsbt = await contextV1.processResponse(
                      response: utf8.encode(receiverPsbt));
                  final senderProcessedPsbt = (await sender.signPsbt(
                      await PartiallySignedTransaction.fromString(
                          receiverProcessedPsbt)));
                  setState(() {
                    processedAndFinalizedPsbt = senderProcessedPsbt;
                  });
                },
                child: Text(
                  "Process and finalize receiver Pj request",
                  style: GoogleFonts.manrope(
                      color: Colors.black,
                      fontSize: 14,
                      fontWeight: FontWeight.w800),
                )),
            TextButton(
                onPressed: () async {
                  final res =
                      await sender.broadcastPsbt(processedAndFinalizedPsbt);
                  debugPrint("Broadcast success: $res");
                },
                child: Text(
                  "Broadcast processed psbt",
                  style: GoogleFonts.manrope(
                      color: Colors.black,
                      fontSize: 14,
                      fontWeight: FontWeight.w800),
                ))
          ],
        ),
      ),
    );
  }
}

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

1 回复

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


当然,以下是一个关于如何在Flutter应用中集成payjoin_flutter插件的示例代码。payjoin_flutter是一个假想的支付插件,用于演示目的,实际使用中请确保你使用的是真实可用的支付插件。

首先,确保你已经在pubspec.yaml文件中添加了payjoin_flutter依赖:

dependencies:
  flutter:
    sdk: flutter
  payjoin_flutter: ^x.y.z  # 替换为实际的版本号

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

接下来,在你的Flutter应用中配置支付功能。以下是一个简单的示例,展示如何初始化支付插件并处理支付结果。

1. 初始化支付插件

在你的主文件(通常是main.dart)中,导入payjoin_flutter插件并进行初始化:

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Payment Integration',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: PaymentScreen(),
    );
  }
}

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

class _PaymentScreenState extends State<PaymentScreen> {
  final PayJoinFlutter _payJoin = PayJoinFlutter();

  @override
  void initState() {
    super.initState();
    // 初始化支付插件(如果需要的话)
    _initializePaymentPlugin();
  }

  Future<void> _initializePaymentPlugin() async {
    try {
      // 假设有一个初始化方法
      await _payJoin.initialize();
    } catch (e) {
      print('支付插件初始化失败: $e');
    }
  }

  void _startPayment() async {
    try {
      // 假设支付请求需要一些参数,例如订单ID和金额
      Map<String, dynamic> paymentDetails = {
        'orderId': '123456',
        'amount': 100.0,
      };

      // 发起支付请求
      PaymentResult result = await _payJoin.startPayment(paymentDetails);

      if (result.status == PaymentStatus.success) {
        print('支付成功: ${result.transactionId}');
      } else if (result.status == PaymentStatus.failed) {
        print('支付失败: ${result.errorMessage}');
      } else if (result.status == PaymentStatus.canceled) {
        print('支付已取消');
      }
    } catch (e) {
      print('支付过程中发生错误: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('支付集成示例'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: _startPayment,
          child: Text('发起支付'),
        ),
      ),
    );
  }
}

// 假设支付结果的数据结构
class PaymentResult {
  final PaymentStatus status;
  final String? transactionId;
  final String? errorMessage;

  PaymentResult({required this.status, this.transactionId, this.errorMessage});
}

enum PaymentStatus { success, failed, canceled }

2. 处理支付回调(如果需要)

某些支付插件可能需要在应用的其他部分处理支付回调,例如处理从支付网关返回的结果。这通常涉及到在应用的MainActivityAppDelegate中添加一些代码(对于原生部分的处理),但在Flutter插件中,回调通常会通过Dart代码处理。

由于payjoin_flutter是一个假想的插件,具体实现细节可能会有所不同。在实际使用中,请查阅该插件的官方文档以了解如何处理支付回调。

注意事项

  • 确保你使用的支付插件是安全且经过验证的。
  • 在发布应用之前,进行充分的测试以确保支付流程的稳定性和安全性。
  • 遵守相关的支付和隐私法规。

这个示例代码提供了一个基本的框架,用于在Flutter应用中集成支付功能。根据你所使用的具体支付插件,代码可能需要相应的调整。

回到顶部