Xamarin.iOS 中的 PassKit
iOS 电子钱包应用允许用户在其设备上存储数字通行证。 这些通行证由商家生成,并通过电子邮件、URL 或商家自己的 iOS 应用发送给客户。 这些通行证可以代表从电影票到会员卡再到登机牌的各种事物。 PassKit 框架允许开发人员以编程方式与通行证进行交互。
本文档介绍了电子钱包以及如何将 PassKit API 与 Xamarin.iOS 结合使用。
要求
本文档中讨论的 PassKit 功能需要 iOS 6 和 Xcode 4.5 以及 Xamarin.iOS 6.0。
介绍
PassKit 主要解决条形码的分发和管理问题。 关于条形码当前使用方式的一些现实示例包括:
- 在线购买电影票 – 通常会通过电子邮件向客户发送代表其门票的条形码。 该条形码将被打印出来并带到电影院进行入场扫描。
- 会员卡 — 顾客在电子钱包中携带许多不同的商店专用卡,以便在购买商品时展示和扫描。
- 优惠券 – 优惠券通过电子邮件、可打印网页、信箱以及报纸和杂志上的条形码形式分发。 顾客将它们带到商店进行扫描即可获得商品、服务或折扣。
- 登机牌 — 与购买电影票类似。
PassKit 为每种情况提供了替代方案:
- 电影票 – 购买后,客户添加活动门票(通过电子邮件或网站链接)。 电影放映时间临近时,会自动在锁屏界面上显示通行证作为提醒,客户到达电影院后,可以轻松检索通行证,它也会显示在电子钱包中以便扫描。
- 会员卡 – 商店可以通过电子邮件或在网站登录后发放商店卡通行证,而不提供实体卡(也可以另外提供实体卡)。 商店可以提供额外的功能,例如通过推送通知更新通行证上的帐户余额以及使用地理定位服务,这样,当顾客靠近商店位置时,就会自动在锁屏界面上显示通行证。
- 优惠券 – 可以轻松生成具有独特特征的优惠券通行证,以便进行跟踪,并通过电子邮件或网站链接分发。 当用户靠近特定位置和/或在给定日期(例如当到期日临近时),可以自动在锁屏界面上显示下载的优惠券。 由于优惠券存储在用户的手机上,因此始终可以方便使用,并且不会丢失。 优惠券可能会鼓励客户下载配套应用程序,因为应用商店链接可以并入通行证中,从而增加与客户的互动。
- 登机牌 — 在线办理登机手续后,客户将通过电子邮件或链接收到登机牌。 运输服务提供商提供的配套应用程序可以包括办理登机手续的流程,并且还允许客户执行其他功能,例如选择座位或餐食。 如果交通延误或取消,运输服务提供商可以通过推送通知来更新通行证。 登机时间临近时,会在锁屏界面上显示通行证作为提醒,并且可以快速访问它们。
PassKit 的核心是提供一种简单便捷的方法来在 iOS 设备上存储和显示条形码。 凭借额外的时间和位置锁屏集成、推送通知和配套应用程序集成,它为非常复杂的销售、票务和计费服务奠定了基础。
PassKit 生态系统
PassKit 不仅仅是 CocoaTouch 中的一个 API,它还是由应用、数据和服务组成的更大生态系统的一部分,可促进条形码和其他数据的安全共享和管理。 此高级关系图显示了创建和使用通行证时可能涉及的不同实体:
生态系统的每个部分都有明确定义的角色:
- 电子钱包 – Apple 内置的 iOS 应用程序,用于存储和显示通行证。 这是显示通行证以在现实世界中使用的唯一位置(即显示条形码以及通行证中的所有本地化数据)。
- 配套应用 – 由通行证提供商构建的 iOS 6 应用程序,用于扩展他们发行的通行证的功能,例如为商店卡充值、更改登机牌上的座位或其他特定于业务的功能。 通行证不需要配套应用程序即可发挥作用。
- 服务器 – 可以在其中生成通行证并签名以进行分发的安全服务器。 配套应用可能会连接到服务器以生成新通行证或请求更新现有通行证。 可以选择实施电子钱包调用的 Web 服务 API 来更新通行证。
- APNS 服务器 – 服务器能够使用 APNS 通知电子钱包给定设备上的通行证更新。 将通知推送到钱包,然后钱包将联系服务器以获取更改的详细信息。 配套应用不需要为此功能实施 APNS(它们可以侦听
PKPassLibraryDidChangeNotification
)。 - 管道应用 – 不直接操作通行证的应用程序(如配套应用),但可以通过识别通行证并允许将其添加到电子钱包来提高其实用性。 邮件客户端、社交网络浏览器和其他数据聚合应用都可能会遇到附加或关联通行证的情况。
整个生态系统看起来很复杂,因此值得注意的是,某些组件是可选的,并且可以实现更简单的 PassKit 实施。
什么是通行证?
通行证是代表门票、优惠券或卡的数据集合。 它可能供个人使用(因此包含航班号和座位分配等详细信息),也可能是由任意数量的用户共享的多个使用令牌(如折扣优惠券)。 Apple 的关于通行证文件文档中提供了详细说明。
类型
目前支持五种类型,可以在电子钱包应用中通过通行证的布局和上边缘来区分:
- 活动门票 - 小半圆形切口。
- 登机牌 – 侧面有凹口,可以指定特定于交通工具的图标(例如公共汽车、火车、飞机)。
- 商店卡 - 圆形顶部,如信用卡或借记卡。
- 优惠券 – 沿顶部穿孔。
- 通用 – 与商店卡相同,圆顶。
此屏幕截图显示了五种通行证类型(按顺序分别为:优惠券、通用、商店卡、登机牌和活动门票):
文件结构
通行证文件实际上是一个带有 .pkpass 扩展名的 ZIP 存档,包含一些特定的 JSON 文件(必需)、各种图像文件(可选)以及本地化字符串(也是可选)。
- pass.json – 必需。 包含通行证的所有信息。
- manifest.json – 必需。 包含通行证中除签名文件和此文件 (manifest.json) 之外的每个文件的 SHA1 哈希值。
- 签名 – 必需。 通过使用 iOS 预配门户中生成的证书对
manifest.json
文件进行签名来创建。 - logo.png – 可选。
- background.png – 可选。
- icon.png – 可选。
- Localizable strings files – 可选。
通行证文件的目录结构如下所示(这是 ZIP 存档的内容):
pass.json
JSON 是一种格式,因为通行证通常是在服务器上创建的 - 这意味着生成代码在服务器上与平台无关。 每个通行证中的三个关键信息是:
- teamIdentifier – 这会将生成的所有通行证链接到 App Store 帐户。 此值在 iOS 预配门户中可见。
- passTypeIdentifier – 在预配门户中注册以将通行证分组在一起(如果生成多种类型)。 例如,咖啡店可能会创建商店卡通行证类型以允许其客户赚取会员积分,但也可能创建单独的优惠券通行证类型以创建和分发折扣优惠券。 同一家咖啡店甚至可能举办现场音乐活动并为这些活动发放活动门票。
- serialNumber – 此
passTypeidentifier
中的唯一字符串。 该值对电子钱包来说是不透明的,但对于在与服务器通信时跟踪特定通行证非常重要。
每个通行证中还有大量其他 JSON 键,示例如下:
{
"passTypeIdentifier":"com.xamarin.passkitdoc.banana", //Type Identifier (iOS Provisioning Portal)
"formatVersion":1, //Always 1 (for now)
"organizationName":"Xamarin", //The name which appears on push notifications
"serialNumber":"12345436XYZ", //A number for you to identify this pass
"teamIdentifier":"XXXAAA1234", //Your Team ID
"description":"Xamarin Demo", //
"foregroundColor":"rgb(54,80,255)", //color of the data text (note the syntax)
"backgroundColor":"rgb(209,255,247)", //color of the background
"labelColor":"rgb(255,15,15)", //color of label text and icons
"logoText":"Banana ", //Text that appears next to logo on top
"barcode":{ //Specification of the barcode (optional)
"format":"PKBarcodeFormatQR", //Format can be QR, Text, Aztec, PDF417
"message":"FREE-BANANA", //What to encode in barcode
"messageEncoding":"iso-8859-1" //Encoding of the message
},
"relevantDate":"2012-09-15T15:15Z", //When to show pass on screen. ISO8601 formatted.
/* The following fields are specific to which type of pass. The name of this object specifies the type, e.g., boardingPass below implies this is a boarding pass. Other options include storeCard, generic, coupon, and eventTicket */
"boardingPass":{
/*headerFields, primaryFields, secondaryFields, and auxiliaryFields are arrays of field object. Each field has a key, label, and value*/
"headerFields":[ //Header fields appear next to logoText
{
"key":"h1-label", //Must be unique. Used by iOS apps to get the data.
"label":"H1-label", //Label of the field
"value":"H1" //The actual data in the field
},
{
"key":"h2-label",
"label":"H2-label",
"value":"H2"
}
],
"primaryFields":[ //Appearance differs based on pass type
{
"key":"p1-label",
"label":"P1-label",
"value":"P1"
}
],
"secondaryFields":[ //Typically appear below primaryFields
{
"key":"s1-label",
"label":"S1-label",
"value":"S1"
}
],
"auxiliaryFields":[ //Appear below secondary fields
{
"key":"a1-label",
"label":"A1-label",
"value":"A1"
}
],
"transitType":"PKTransitTypeAir" //Only present in boradingPass type. Value can
//Air, Bus, Boat, or Train. Impacts the picture
//that shows in the middle of the pass.
}
}
条形码
仅支持 2D 格式:PDF417、Aztec、QR。 Apple 声称 1D 条形码不适合在背光手机屏幕上扫描。
条形码下方显示的替代文本是可选的 - 一些商家希望能够手动读取/输入。
ISO-8859-1 编码是最常见的,请检查将读取通行证的扫描系统使用哪种编码。
相关性(锁屏)
有两种类型的数据可能会导致通行证显示在锁屏界面上:
位置
通行证中最多可以指定 10 个位置,例如顾客经常光顾的商店、电影院或机场的位置。 客户可以通过配套应用设置这些位置,提供商也可以根据使用数据(如果在客户许可的情况下收集)来确定它们。
当通行证显示在锁屏界面上时,系统会计算围栏距离,以便当用户离开该区域时,在锁屏界面上隐藏通行证。 半径与通行证风格相关,以防止滥用。
日期和时间
一张通行证中只能指定一个日期/时间。 日期和时间对于触发登机牌和活动门票的锁屏提醒非常有用。
可以通过推送或通过 PassKit API 进行更新,以便在多次使用门票(例如剧院或体育场馆的季票)的情况下更新日期/时间。
本地化
将通行证翻译成多种语言类似于本地化 iOS 应用程序 - 使用 .lproj
扩展名创建特定于语言的目录并将本地化元素放入其中。 文本翻译应输入到 pass.strings
文件中,而本地化图像应与其在 Pass 根目录中替换的图像具有相同的名称。
安全性
通行证使用在 iOS 预配门户中生成的私有证书进行签名。 对通行证进行签名的步骤是:
- 为通行证目录中的每个文件计算 SHA1 哈希值(不包括
manifest.json
或signature
文件,无论如何,这两个文件在此阶段都不应该存在)。 - 将
manifest.json
写入每个文件名及其哈希值的 JSON 键/值列表。 - 使用证书对
manifest.json
文件进行签名,并将结果写入名为signature
的文件中。 - 将所有内容压缩并为生成的文件提供
.pkpass
文件扩展名。
由于需要私钥来为通行证签名,因此此过程只能在由你控制的安全服务器上完成。 请勿分发密钥以尝试在应用程序中生成通行证。
配置和设置
本部分包含帮助设置预配详细信息并创建你的第一个通行证的说明。
预配 PassKit
若要让通行证在应用商店中上线,必须将其链接到开发者帐户。 这需要两个步骤:
- 必须使用唯一标识符(称为通行证类型 ID)注册通行证。
- 必须生成有效的证书才能使用开发人员的数字签名对通行证进行签名。
若要创建通行证类型 ID,请执行以下操作。
创建通行证类型 ID
第一步是为要支持的每种不同类型的通行证设置通行证类型 ID。 通行证 ID(或通行证类型标识符)会为通行证创建唯一标识符。 我们将使用此 ID 通过证书将通行证与你的开发人员帐户链接起来。
在 iOS 预配门户的“证书、标识符和配置文件”部分中,导航到“标识符”并选择“通行证类型 ID”。 选择 + 按钮创建新的通行证类型:
提供通行证的描述(名称)和标识符(唯一字符串)。 请注意,所有通行证类型 ID 必须以字符串
pass.
开头。在此示例中,我们使用pass.com.xamarin.coupon.banana
:按“注册”按钮确认通行证 ID。
生成证书
若要为此通行证类型 ID 创建新证书,请执行以下操作:
-
然后,选择“创建证书...”:
按照以下步骤创建证书签名请求 (CSR)。
按开发人员门户上的“继续”按钮并上传 CSR 以生成证书。
下载证书并双击它以将其安装到你的密钥链中。
现在,我们已经为此通行证类型 ID 创建了证书,下一节将介绍如何手动构建通行证。
有关电子钱包预配的更多信息,请参阅使用功能指南。
手动创建通行证
现在我们已经创建了通行证类型,接下来可以手动制作一个通行证以在模拟器或设备上进行测试。 创建通行证的步骤是:
- 创建一个包含通行证文件的目录。
- 创建一个包含所有必需数据的 pass.json 文件。
- 在文件夹中包含图像(如果需要)。
- 计算文件夹中每个文件的 SHA1 哈希值,并将其写入manifest.json。
- 使用下载的证书 .p12 文件对 manifest.json 进行签名。
- 压缩目录内容并使用 .pkpass 扩展名重命名。
本文的示例代码中有一些源文件可用于生成通行证。 使用 CreateAPassManually 目录的 CouponBanana.raw
目录中的文件。 存在以下文件:
打开 pass.json 并编辑 JSON。 必须至少更新 passTypeIdentifier
和 teamIdentifer
,使其与你的 Apple 开发人员帐户匹配。
"passTypeIdentifier" : "pass.com.xamarin.coupon.banana",
"teamIdentifier" : "?????????",
然后,必须计算每个文件的哈希值并创建 manifest.json
文件。 完成后,它将如下所示:
{
"icon@2x.png" : "30806547dcc6ee084a90210e2dc042d5d7d92a41",
"icon.png" : "87e9ffb203beb2cce5de76113f8e9503aeab6ecc",
"pass.json" : "c83cd1441c17ecc6c5911bae530d54500f57d9eb",
"logo.png" : "b3cd8a488b0674ef4e7d941d5edbb4b5b0e6823f",
"logo@2x.png" : "3ccd214765507f9eab7244acc54cc4ac733baf87"
}
接下来,必须使用为此通行证类型 ID 生成的证书(.p12 文件)为此文件生成签名。
在 Mac 上签名
从 Apple 下载项站点下载电子钱包种子支持材料。 使用 signpass
工具将文件夹转换为通行证(这还将计算 SHA1 哈希值并将输出压缩为.pkpass 文件)。
测试
如果要检查这些工具的输出(通过将文件名设置为 .zip 然后打开它),你将看到以下文件(注意添加了 manifest.json
和 signature
文件):
对该文件进行签名、压缩和重命名(例如,重命名为 BananaCoupon.pkpass
)后,可以将其拖到模拟器中进行测试,或通过电子邮件将其发送给自己以在真实设备上检索。 你应该会看到一个添加通行证的屏幕,如下所示:
通常,该过程将在服务器上自动执行,但是对于仅创建不需要后端服务器支持的优惠券的小型企业来说,可能可以选择手动创建通行证。
钱包
电子钱包是 PassKit 生态系统的核心部分。 此屏幕截图显示了空的电子钱包,以及通行证列表和各个通行证的外观:
电子钱包的功能包括:
- 这是唯一一个以条形码形式显示通行证以供扫描的地方。
- 用户可以更改更新设置。 如果启用相应功能,推送通知可以触发通行证中数据的更新。
- 用户可以启用或禁用锁屏集成。 如果启用相应功能,该通行证将根据通行证中嵌入的相关时间和位置数据自动显示在锁定屏幕上。
- 如果通行证 JSON 中提供了 Web 服务器 URL,则通行证的反面将支持拉取刷新。
- 如果通行证 JSON 中提供了应用程序的 ID,则可以打开(或下载)配套应用。
- 可以删除通行证(带有可爱的粉碎动画)。
将通行证添加到电子钱包
可以通过以下方式将通行证添加到电子钱包:
管道应用 – 这些应用程序不会直接操作通行证,它们只是加载通行证文件并向用户提供将其添加到电子钱包的选项。
配套应用 – 由提供商编写,用于分发通行证并提供浏览或编辑通行证的附加功能。 Xamarin.iOS 应用程序可以完全访问 PassKit API 来创建和操作通行证。 然后可以使用
PKAddPassesViewController
将通行证添加到电子钱包中。 本文档的配套应用程序部分更详细地介绍了此过程。
管道应用程序
管道应用程序是可能代表用户接收通行证的中间应用程序,并且应该进行编程以识别其内容类型并提供添加到电子钱包的功能。 管道应用程序的示例包括:
- 邮件 – 将附件识别为通行证。
- Safari – 单击通行证 URL 链接时识别通行证内容类型。
- 其他自定义应用 – 任何接收附件或打开链接的应用(社交媒体客户端、邮件阅读器等)。
此屏幕截图显示了 iOS 6 中的邮件如何识别通行证附件并(在触摸时)将其添加到电子钱包。
如果你正在构建一个可以作为通行证管道的应用,则可以通过以下方式识别它们:
- 文件扩展名 - .pkpass
- MIME 类型 - application/vnd.apple.pkpass
- UTI – com.apple.pkpass
管道应用程序的基本操作是检索通行证文件并调用 PassKit 的 PKAddPassesViewController
为用户提供将通行证添加到其电子钱包的选项。 该视图控制器的实施将在下一节“配套应用程序”中介绍。
管道应用程序不需要像配套应用程序那样针对特定的通行证类型 ID 进行预配。
配套应用程序
配套应用程序提供了用于处理通行证的附加功能,包括创建通行证、更新与通行证关联的信息以及以其他方式管理与应用程序关联的通行证。
配套应用程序不应尝试复制电子钱包的功能。 它们并未设计为显示通行证以供扫描。
本节的其余部分介绍如何构建与 PassKit 交互的基本配套应用。
预配
由于电子钱包是一种商店技术,因此该应用程序需要单独预配,并且无法使用团队预配配置文件或通配符应用 ID。 请参阅使用功能指南,为电子钱包应用程序创建唯一的应用 ID 和预配配置文件。
权利
Entitlements.plist 文件应包含在所有最近的 Xamarin.iOS 项目中。 若要添加新的 Entitlements.plist 文件,请按照使用权利指南中的步骤操作。
若要设置权利,请执行以下操作:
双击 Solution Pad 中的 Entitlements.plist 文件以打开 Entitlements.plist 编辑器:
在“电子钱包”部分下,选择“启用电子钱包”选项
应用的默认选项是允许所有通行证类型。 但是,可以限制应用并仅允许团队通行证类型的子集。 若要启用此功能,请选择“允许团队通行证类型的子集”,然后输入你希望允许的子集的通行证类型标识符。
调试
如果在部署应用程序时遇到问题,请检查是否使用了正确的预配配置文件,以及是否在 iPhone 捆绑签名选项中选择 Entitlements.plist
作为自定义权利文件。
如果在部署时遇到此错误:
Installation failed: Your code signing/provisioning profiles are not correctly configured (error: 0xe8008016)
则 pass-type-identifiers
权利数组不正确(或与预配配置文件不匹配)。 验证通行证类型 ID 和团队 ID 是否正确。
类
以下 PassKit 类可用于应用访问通行证:
- PKPass – 一个通行证实例。
- PKPassLibrary – 提供用于访问设备上的通行证的 API。
- PKAddPassesViewController – 用于显示用户保存在电子钱包中的通行证。
- PKAddPassesViewControllerDelegate – Xamarin.iOS 开发人员
示例
请参阅本文示例中的 PassLibrary 项目。 它演示了电子钱包配套应用程序所需的以下常见功能:
检查电子钱包是否可用
电子钱包在 iPad 上不可用,因此应用程序应在尝试访问 PassKit 功能之前进行检查。
if (PKPassLibrary.IsAvailable) {
// create an instance and do stuff...
}
创建通行证库实例
PassKit 库不是单一实例,应用程序应该创建并存储实例来访问 PassKit API。
if (PKPassLibrary.IsAvailable) {
library = new PKPassLibrary ();
// do stuff...
}
获取通行证列表
应用程序可以从库请求通行证列表。 此列表由 PassKit 自动筛选,因此你只能看到使用你的团队 ID 创建且在你的权利中列出的通行证。
var passes = library.GetPasses (); // returns PKPass[]
请注意,模拟器不会筛选返回的通行证列表,因此应始终在真实设备上测试此方法。 该列表可以显示在 UITableView 中。 添加两张优惠券后,示例应用如下所示:
显示通行证
一组有限的信息可用于在配套应用程序中呈现通行证。
从这组标准属性中进行选择以显示通行证列表,如示例代码所示。
string passInfo =
"Desc:" + pass.LocalizedDescription
+ "\nOrg:" + pass.OrganizationName
+ "\nID:" + pass.PassTypeIdentifier
+ "\nDate:" + pass.RelevantDate
+ "\nWSUrl:" + pass.WebServiceUrl
+ "\n#" + pass.SerialNumber
+ "\nPassUrl:" + pass.PassUrl;
该字符串在示例中显示为警报:
你还可以使用 LocalizedValueForFieldKey()
方法从设计的通行证中的字段检索数据(因为你知道应该显示哪些字段)。 示例代码没有显示这一点。
从文件加载通行证
因为只有在用户许可的情况下才能将通行证添加到电子钱包中,所以必须提供一个视图控制器来让他们决定。 此代码用于示例中的“添加”按钮,以加载嵌入应用中的预构建通行证(应该将其替换为已签名的通行证):
NSData nsdata;
using ( FileStream oStream = File.Open (newFilePath, FileMode.Open ) ) {
nsdata = NSData.FromStream ( oStream );
}
var err = new NSError(new NSString("42"), -42);
var newPass = new PKPass(nsdata,out err);
var pkapvc = new PKAddPassesViewController(newPass);
NavigationController.PresentModalViewController (pkapvc, true);
通行证会显示“添加”和“取消”选项:
替换现有通行证
替换现有通行证不需要用户许可,但如果通行证尚不存在,则替换将失败。
if (library.Contains (newPass)) {
library.Replace (newPass);
}
编辑通行证
PKPass 不可变,因此无法更新代码中的通行证对象。 若要更改通行证中的数据,应用程序必须有权访问 Web 服务器,该服务器可以保存通行证记录并生成具有应用程序可以下载的更新值的新通行证文件。
必须在服务器上完成通行证创建,因为通行证必须使用必须私密且安全的证书进行签名。
生成更新的通行证文件后,使用 Replace
方法覆盖设备上的旧数据。
显示通行证以供扫描
如前所述,只有电子钱包可以显示通行证以供扫描。 可以使用 OpenUrl
方法显示通行证,如下所示:
UIApplication.SharedApplication.OpenUrl (p.PassUrl);
接收更改通知
应用程序可以使用 PKPassLibraryDidChangeNotification
侦听对通行证库所做的更改。 更改可能是由在后台触发更新的通知引起的,因此最好在应用程序中侦听它们。
noteCenter = NSNotificationCenter.DefaultCenter.AddObserver (PKPassLibrary.DidChangeNotification, (not) => {
BeginInvokeOnMainThread (() => {
new UIAlertView("Pass Library Changed", "Notification Received", null, "OK", null).Show();
// refresh the list
var passlist = library.GetPasses ();
table.Source = new TableSource (passlist, library);
table.ReloadData ();
});
}, library); // IMPORTANT: must pass the library in
注册通知时传递库实例非常重要,因为 PKPassLibrary 不是单一实例。
服务器处理
本介绍性文章并未详细讨论如何构建服务器应用程序以支持 PassKit。
请参阅 dotnet-passbook 开源 C# 服务器端代码。
推送通知
本介绍性文章并未详细讨论如何使用推送通知更新通行证。
你需要实施 Apple 定义的类似 REST 的 API,以在需要更新时响应电子钱包中的 Web 请求。
有关详细信息,请参阅 Apple 的更新通行证指南。
总结
本文介绍了 PassKit,概述了它之所以有效的部分原因,并且描述了完整 PassKit 解决方案必须实施的不同部分。 它描述了配置 Apple 开发人员帐户以创建通行证所需的步骤、手动创建通行证的过程以及如何从 Xamarin.iOS 应用程序访问 PassKit API。
相关链接
- 适用于开发人员的电子钱包
- 电子钱包开发人员指南
- 框架 – Apple Pay 和电子钱包(WWDC 视频)
- PassKit 框架参考
- Passbook Web 服务参考
- 关于通行证文件
- dotnet-passbook,一个用于生成 iOS 电子钱包程序包的开源库
- iOS 6 简介