Flutter苹果钱包通行证生成插件pkpass的使用

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

Flutter苹果钱包通行证生成插件pkpass的使用

简介

pkpass 是一个用于解析 Apple Wallet 通行证(Passbook)文件的 Dart 库。它可以在纯 Dart 环境中运行,无需依赖特定平台的功能。该库支持解析任何 Passbook 文件、校验和验证、提取元数据等功能,并且可以轻松集成到 Flutter 项目中。

主要功能

  • 无平台依赖:纯 Dart 实现,不依赖于 dart:iodart:ui,可以在 CLI 和 Flutter 中使用。
  • 解析 Passbook 文件:可以从二进制数据中解析通行证文件。
  • 校验和验证:支持校验和验证,确保文件完整性。
  • 提取元数据:可以提取通行证中的所有元数据。
  • 多语言支持:支持多语言本地化,提供高阶方法获取本地化资源。
  • 条形码 API:提供高阶条形码 API,支持多种编码格式。

尚未支持的功能

  • 签名验证:尚未实现使用 Apple 证书的 PKCS #7 签名验证。
  • NFC 支持:尚未实现 Apple Pay 的卡支付信息。
  • 推送服务:仅支持手动刷新,推送服务尚未实现。

本地化支持

pkpass 库支持通行证中的本地化内容。可以通过 getLocalized... 方法获取本地化值,例如 myPass.getLocalizedDescription()。如果请求的语言不可用,库会按照以下顺序进行回退:

  1. 英语 (en)
  2. 汉语 (zh)
  3. 印地语 (hi)
  4. 西班牙语 (es)
  5. 法语 (fr)
  6. 如果以上语言都不可用,则选择任意可用的语言。

条形码编码

Passbook 标准对条形码字符串编码的规定较为模糊,理论上支持所有 IANA 字符集名称。pkpass 库默认支持以下编码器:

  • Latin1Codec(默认):iso-8859-1,也支持 iso-8859iso8859
  • Utf8Codecutf-8,也支持 utf8

可以通过扩展 PassBarcode.supportedCodecs 来添加更多编码器。

依赖项和兼容性

pkpass 库使用了以下几个依赖项来正确解析 PkPass 文件:

  • archive:用于解析 ZIP 格式的 PkPass 文件。
  • barcode:用于生成和处理条形码。
  • charset:用于确定 PkPass 文件中的字符编码。
  • crypto:用于 SHA1 签名验证。
  • intl:用于本地化资源的查找。

使用示例

完整的 Flutter 示例代码

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:intl/locale.dart';
import 'package:pkpass/pkpass.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter PkPass Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: PassDetailsScreen(),
    );
  }
}

class PassDetailsScreen extends StatefulWidget {
  [@override](/user/override)
  _PassDetailsScreenState createState() => _PassDetailsScreenState();
}

class _PassDetailsScreenState extends State<PassDetailsScreen> {
  PassFile? _pass;
  String? _error;

  Future<void> _loadPass(String path) async {
    try {
      final file = File(path);
      final contents = await file.readAsBytes();
      final pass = await PassFile.parse(contents);
      setState(() {
        _pass = pass;
        _error = null;
      });
    } catch (e) {
      setState(() {
        _error = e.toString();
      });
    }
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter PkPass Example'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            if (_error != null)
              Text(
                'Error: $_error',
                style: TextStyle(color: Colors.red),
              ),
            if (_pass != null)
              Expanded(
                child: ListView(
                  children: [
                    ListTile(
                      title: Text('Logo Image Length: ${_pass?.getLogo(scale: 2, locale: Locale.fromSubtags(languageCode: 'fr'))?.length}'),
                    ),
                    ListTile(
                      title: Text('First Barcode: ${_pass?.metadata.barcodes.firstOrNull?.message}'),
                    ),
                    ListTile(
                      title: Text('Location: ${_pass?.metadata.locations.firstOrNull?.relevantText}'),
                    ),
                    ListTile(
                      title: Text('Relevant Date: ${_pass?.metadata.relevantDate}'),
                    ),
                    ListTile(
                      title: Text(
                        'Boarding Pass Header Field: ${_pass?.metadata.boardingPass?.headerFields.firstOrNull?.getLocalizedLabel(_pass!, Locale.fromSubtags(languageCode: 'tlh'))}',
                      ),
                    ),
                  ],
                ),
              ),
            if (_pass == null && _error == null)
              ElevatedButton(
                onPressed: () async {
                  final path = await _selectFile();
                  if (path != null) {
                    await _loadPass(path);
                  }
                },
                child: Text('Select Pass File'),
              ),
          ],
        ),
      ),
    );
  }

  // A simple file picker function for demonstration purposes.
  // In a real app, you would use a proper file picker package like `file_picker`.
  Future<String?> _selectFile() async {
    // For simplicity, we'll just prompt the user to enter a file path.
    final path = await showDialog<String>(
      context: context,
      builder: (context) => AlertDialog(
        title: Text('Enter Pass File Path'),
        content: TextField(
          decoration: InputDecoration(hintText: 'File path'),
          onSubmitted: (value) => Navigator.of(context).pop(value),
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(context).pop(null),
            child: Text('Cancel'),
          ),
          TextButton(
            onPressed: () => Navigator.of(context).pop((context as Element).findRenderObject()?.toString()),
            child: Text('OK'),
          ),
        ],
      ),
    );
    return path;
  }
}

更多关于Flutter苹果钱包通行证生成插件pkpass的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter苹果钱包通行证生成插件pkpass的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


在Flutter中生成Apple Wallet通行证(.pkpass文件)可以通过使用pkpass库来实现。虽然Flutter本身并没有直接提供生成.pkpass文件的插件,但你可以通过调用原生iOS代码来实现这一功能。下面是一个如何在Flutter中集成并使用pkpass库生成.pkpass文件的示例。

步骤 1: 创建Flutter项目

首先,确保你已经安装了Flutter SDK,并创建了一个新的Flutter项目。

flutter create apple_wallet_pkpass
cd apple_wallet_pkpass

步骤 2: 添加平台依赖

ios/Podfile中,添加对PKPassKit(一个生成.pkpass文件的iOS库)的依赖。注意,这一步可能需要手动操作,因为Flutter社区可能还没有提供直接的插件封装。

# 在 Podfile 中添加
platform :ios, '11.0'

target 'Runner' do
  use_frameworks!
  config = use_native_modules!

  # 添加 PKPassKit 依赖
  pod 'PKPassKit', '~> 0.9.4'

  # Flutter Pod
  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end

然后运行pod install来安装依赖。

步骤 3: 创建原生iOS代码

ios/Runner目录下,创建一个新的Swift文件,例如PassGenerator.swift,并添加生成.pkpass文件的代码。

// ios/Runner/PassGenerator.swift

import Foundation
import PKPassKit

class PassGenerator: NSObject {
    static func generatePass(completion: @escaping (Result<URL, Error>) -> Void) {
        let passTypeIdentifier = "com.example.pass.generic"
        let teamIdentifier = "YOUR_TEAM_IDENTIFIER"
        let serialNumber = "123456789"
        let webServiceURL = URL(string: "https://your-server.com/passes/")!
        let authenticationToken = "your-authentication-token"

        // 配置 pass.json 数据
        let passData: [String: Any] = [
            "formatVersion": 1,
            "passTypeIdentifier": passTypeIdentifier,
            "serialNumber": serialNumber,
            "teamIdentifier": teamIdentifier,
            "organizationName": "Your Organization",
            "description": "Your Pass Description",
            "logoText": "Your Logo Text",
            "foregroundColor": "#ffffff",
            "backgroundColor": "#000000",
            "labelColor": "#ffffff",
            "barcode": [
                "message": serialNumber,
                "format": "PKBarcodeFormatQR",
                "altText": "QR code for \(serialNumber)"
            ],
            "webServiceURL": webServiceURL.absoluteString,
            "authenticationToken": authenticationToken,
            // 其他必要的字段...
        ]

        // 创建 pass.json 文件
        guard let passJSONData = try? JSONSerialization.data(withJSONObject: passData, options: []) else {
            return completion(.failure(NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to create pass.json data"])))
        }

        let passBundlePath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("\(serialNumber).pkpass")
        do {
            try FileManager.default.createDirectory(atPath: passBundlePath.path, withIntermediateDirectories: true, attributes: nil)

            let passJSONURL = passBundlePath.appendingPathComponent("pass.json")
            try passJSONData.write(to: passJSONURL)

            // 添加其他必要的文件,如 icon.png, logo.png, thumbnail.png, strip.png, background.png 等
            // 这里省略了添加这些文件的代码,你需要根据实际需求添加

            // 打包成 .pkpass 文件
            let passPackager = PKPassPackager(archivePath: passBundlePath.path)
            passPackager?.packagePass(withCompletion: { error in
                if let error = error {
                    completion(.failure(error))
                } else {
                    completion(.success(passBundlePath))
                }
            })
        } catch {
            completion(.failure(error))
        }
    }
}

步骤 4: 在Flutter中调用原生代码

使用MethodChannel在Flutter中调用上面的原生代码。

lib目录下创建一个新的Dart文件,例如pass_generator.dart

// lib/pass_generator.dart

import 'dart:async';
import 'package:flutter/services.dart';

class PassGenerator {
  static const MethodChannel _channel = MethodChannel('com.example.apple_wallet_pkpass/pass_generator');

  static Future<String?> generatePass() async {
    try {
      final result = await _channel.invokeMethod('generatePass');
      return result;
    } on PlatformException catch (e) {
      print("Failed to generate pass: '${e.message}'.");
      return null;
    }
  }
}

然后在你的Flutter应用中调用这个方法。

// lib/main.dart

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

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

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

class _MyAppState extends State<MyApp> {
  String? _result;

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

  Future<void> _generatePass() async {
    String? result = await PassGenerator.generatePass();

    // 注意:这里生成的URL在iOS模拟器上可能无法直接访问,需要在真机上测试
    setState(() {
      _result = result;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Apple Wallet PKPass Generator'),
        ),
        body: Center(
          child: _result != null
              ? Text('Pass URL: $_result')
              : CircularProgressIndicator(),
        ),
      ),
    );
  }
}

步骤 5: 配置Info.plist

确保在ios/Runner/Info.plist中添加了必要的权限配置,特别是如果你需要访问文件系统或网络。

注意

  1. 证书和配置:生成.pkpass文件需要Apple的开发者证书和正确的配置。确保你的Apple Developer Account已经配置好,并且你已经创建了必要的Pass Type IDs。
  2. 测试:在模拟器上测试可能无法完全模拟真实设备的行为,特别是与Apple Wallet相关的功能。建议在真机上进行测试。
  3. 安全性:不要在代码中硬编码敏感信息,如authenticationToken。这些信息应该从安全的存储或服务器获取。

这个示例提供了一个基本的框架,你可能需要根据自己的需求进行调整和扩展。

回到顶部