第一章 介绍
1.1.关于Pass Kit
Pass(通行证)是用数字化表示的信息,这些信息可能需要被打印在小纸片或者塑料上。在现实世界中,pass让用户像使用登机牌、会员卡、优惠卷一样的方式在使用。pass库包含了用户的通行证。用户可以使用Passbook应用程序来浏览和管理他们的通行证。
【注:便于理解,下面红色部分是译者根据下图表达的意思,增加进去的】
从下图,我们可以看出Pass(通行证)使用的步骤:
1、 服务器端创建一个pass,客户端可以通过email,url或者普通的应用程序进行安装
2、 一段时间过后,服务器端可能由于某种原因(例如登机牌的登机时间改变),把pass更新了,这是服务器端通过苹果的APNS,把消息推送给客户端
3、 客户端收到推送消息后,请求服务器,以查询详细信息
4、 服务器端把pass改变的详细信息返回给客户端
最后,用户可以在POS终端上显示pass,以进行消费

1.1.1.概述
本文档包含了Pass Kit技术的关键概念,以及解释了使用Pass Kit的方法。
1.1.2.创建和发布Pass(通行证)
创建一个pass的方法:通过编写一个pass.json文件,并提供适当的图片和本地化数据。所有的这些文件都被放在一个被签名和压缩过的pass包里面。
pass的发布可以通过电子邮件,网站或者你的应用程序。
相关章节:“创建一个pass(通行证)”,“构建和发布一个pass(通行证)”
1.1.3.更新Pass(通行证)
通过使用推送通知和web service进行pass的更新。当用户的设备收到了一条关于pass已经更新了的推送通知,那么设备就可以通过查询你的服务器来获取已经更新了的pass的最新版本。
1.1.4.与用户的Pass(通行证)进行交互
你的应用程序可以通过使用Objective-C API 进行pass(通行证)的添加,更新和移除,来给用户提供更丰富的体验。
第二章 创建一个pass(通行证)
Pass(通行证)被创建为一个包,这个包包含如下几个部分:
n pass.json文件:定义了pass(通行证)。在创建pass时,该文件需要做的工作内容最多。
n 图片:例如你的logo和通行证icon。
n 本地化本地化工程(.lproj文件夹):包含了字符串文件和本地化图片。
n manifest.json文件:包含了在包中每个文件的哈希。
n 签名文件:包含了所有文件的签名哈希,用于创建pass的源。
2.1. 设置开发环境
为了创建和发布pass(通行证),你需要一个pass类型标示符和签名证书。
前往开发者门户网站,请求一个pass类型标示符和证书。这个证书用在“构建和发布一个pass(通行证)”,以进行pass签名,并用于在“更新一个pass(通行证)”,以通过推送通知进行pass更新。
2.2.Pass.json文件的顶层
pass.json文件是一个JSON字典,列表1-1显示了pass的一部分:
列表1-1 pass的顶层key
{
"passTypeIdentifier" :"pass.com.example.myExamplePass",
"formatVersion" : 1,
"organizationName" : "Example Company",
"serialNumber" : "123456",
"teamIdentifier" : "A1B2C3D4E5",
"description" : "What this pass is called",
...
}
pass type identifier、teamidentifier和organization name与开发者门户网站设置的内容相匹配。pass type identifier与bundle identifier或类名是相同的概念。它定义了pass的类或者类别。例如,5%或10%折扣的优惠券应该使用相同的pass type identifier,但是礼品卡应该使用与优惠券不同的pass type identifier。准确的来说,每一个pass type identifier是有你和你的程序决定的。在应用程序的Entitlements File内部可以指定允许与应用程序发生交互的pass类型;这样做了之后,在合适的时候也允许你在应用程序之间创建一定数量的分离。
serial number 是唯一标示pass的一个字符串。可以是单调递增的整数,或者是通过很方便的方法赋予的一串序号,但是,你也可以使用任意方法,来赋予它你认为有意义的内容。在PassKit 框架中,serial number被认为是不透明的数据。如果两个pass具有相同的pass typeidentifier和serial number,这这两个pass会被认为是同一个pass------在pass中其它任意数据都可以通过更新pass的方式进行改变。
iOSaccessibility把description用来描述pass。Description值是由PKPass的方法localizedDescription返回的。
2.3.设置Pass(通行证)的风格
不同类别的pass显示不同的视觉风格,因此你需要提供不同的域(field)。这与你自己定义的pass type identifier是不同的,你只能从固定的列表中选择pass风格,这是由API决定的。你指定使用哪种风格的pass,这种风格的key在Pass Kit包格式参考的“Style-Specific Dictionary Keys”中有列出。key的值是一个字典,它包含的域指定了pass的风格。除了登机牌需要一个transitTpyekey外,这些pass的风格的字典内容大多数都是相同的。例如,列表1-2显示的登机牌pass,是从San Francisco至London的航班。
列表1-2 登机牌pass的格式域
{
"passTypeIdentifier" :"pass.com.example.myExamplePass",
"formatVersion" : 1,
"serialNumber" : "123456",
"description" : "What this pass is called",
"boardingPass" : {
"primaryFields" : [
{
"key" : "origin",
"label" : "SanFrancisco",
"value" : "SFO"
},
{
"key" :"destination",
"label" : "London",
"value" : "LHR"
}
],
"secondaryFields" : [
{
"key" :"board-gate",
"changeMessage" : "Gatechanged to %@.",
"label" : "Gate",
"value" : "F12"
},
{
"key" :"board-time",
"changeMessage" :"Boarding time changed to %@.",
"dateStyle" :"PKDateStyleFull",
"timeStyle" :"PKTimeStyleFull",
"label" : "Boards",
"value" : "2012-04-01T0700-8"
},
],
"auxilaryFields" : [
{
"key" : "seat",
"label" : "Seat",
"value" : "7A"
},
{
"key" :"passenger-name",
"label" :"Passenger",
"value" : "JohnAppleseed"
}
],
"backFields" : [
{
"key" :"freq-flier-num",
"label" : "Frequentflier number",
"value" :"1234-5678"
},
]
"transitType" : "PKTransitTypeAir"
}
}
2.4.声明Pass(通行证)的域
pass的域包含的信息用于以特定的方式显示给用户。pass里的大多数信息都是域的一部分。一些域的例子:一个事件的名称和场所,某航班登机牌的出发时间,以及礼品卡的当前余额。一些域总是需要用到,一些则需要指出pass风格,而有些则是可选的。
每一个域都有一个唯一的key。Objective-CAPI提供localizedValueForFieldKey:方法来访问pass数据。
域包含一个value和一个label(在UI上显示),一个唯一key,以及域如何显示的相关信息。这些信息存储在域字典中。列表1-3显示了一个具有3个域的入场券:乐队名称,地点和音乐会时间。列表中的event-name域具有最简的域:只有key和value。列表中的start-time域给出了入场的开始时间和日期,这个域同时还给出了域信息如何显示,以及如果域的值有改变了,那么消息应该如何显示给用户。
当域的值改变了,通过使用changeMessage key来提供message信息。如果不提供change message,那么值会静默更新。
列表1-3 入场券示例
{
"passTypeIdentifier" :"pass.com.example.myExamplePass",
"formatVersion" : 1,
"serialNumber" :"123456",
"description" : "Whatthis pass is called",
...
"eventTicket" : {
"primaryFields" : {
"key":"event-name",
"value" : "The HecticGlow in concert",
},
{
"key":"venue-name",
"label" : "Venue",
"value" : "Joe\u2019sCoffee Shop",
},
{
"changeMessage" :"Concert time changed to %@.",
"dateStyle" :"PKDateStyleMedium",
"timeStyle" :"PKTimeStyleMedium",
"isRelative" : true,
"label" : "Startsin",
"key" :"start-time",
"value" :"2012-12-31T20:03Z"
}
}
}
2.5.格式化Pass(通行证)的域
带有一个数字或者日期的域能够指定自身格式化的方式。
为了指定一个数字的格式串,这里提供一个numberStyle key的值即可。“Pass Kit包格式参考”中的“Number Style Keys”列出了所支持的number 风格。十进制数代表了money,这里提供了currencyCode key的ISO货币代码。
为了指定日期和时间的格式串,提供dateStyle和timeStyle两个key的值即可。参考“Pass Kit包格式参考”里面的“Date Style Keys”。为了显示为相关日期,需要把isRelative key设置为true。
为了在某个域的值中插入一个换行,可以使用\n。
2.6.添加相关信息
为了在lock screen中,能够访问你的pass(通行证),需要提供时间和位置。这属于相关信息,使用relevantDate和locations两个key即可。这样在lockscreen进行集成,用户会更加方便和快捷的找到他们需要的pass。列表1-4显示了pass示例中的相关信息部分内容。
1-4 pass的相关信息
{
"passTypeIdentifier" :"pass.com.example.myExamplePass",
"formatVersion" : 1,
"serialNumber" :"123456",
"description" : "Whatthis pass is called",
...
"locations" : [
{"latitude" : 37.296,"longitude" : -122.038 },
{"latitude" : 37.302,"longitude" : -122.103}
{"altitude" : 67.0,"latitude" : 37.331, "longitude" : -122.029 },
],
"relevantDate" :"2010-02-05T09:00-08"
}
Location是一个location数组的字典,每一个location包含了经度、维度和可选的海拔高度,这里描述的位置表示用户可以在该处做一些与pass相关的动作。一个pass最多可以指定10个location。例如,一张store 卡可以存储与用户最近的store位置信息,或者存储用户访问频率最高的store位置信息。就像pass中的其它数据一样,相关信息也是可以被更新的,具体请参考“更新pass(通行证)”。例如,如果某个航班的时间改变了,你可以推送一个携带最新时间的更新,或者用户在你的网站上更新了他的个人信息,你可以更新位置,以匹配用户当前所在城市。
2.7.添加一个条形码(Barcode)
pass可以包含一个条形码,对于在快速又准确的给各种各样的应用程序输入信息时,这是非常方便的方法——例如,在结账时或上火车前。为了添加条形码,需要制定条形码类型和它的内容。pass可以有几种格式来显示条形码:QR,PDF417,Aztec以及纯文本。列表1-5显示了一个关于条形码所包含内容的。你必须指定message的编码。通常使用ISO 8859-1编码(也叫Latin-1),条形码扫描器和英语程序通常都支持这种编码,当然,如果你的设备支持其它的编码,你也可以使用其它的编码。
注意:激光扫描仪从iOS设备屏幕中读取条形码,通常识别率都不好。建议使用光条形码扫描仪来代替。所有支持的条形码格式都是二维的。
列表1-5 条形码信息
{
"passTypeIdentifier" :"pass.com.example.myExamplePass",
"formatVersion" : 1,
"serialNumber" :"123456",
"description" : "Whatthis pass is called",
...
"barcode" : {
"format" : PKBarcodeFormatQR,
"message" : "Helloworld!",
"messageEncoding" :"iso-8859-1"
}
}
2.8.本地化Pass(通行证)
为了本地化一个pass,需要为每个区域提供一个字符串文件和本地化图片,相关内容定义在Bundle Programming Guide的“Localized Resources in Bundles”里面,以及Internationalization Programming Topics的“Localizing String Resources”里面。
例如,下面给出的pass片段
{
"formatVersion" : 1,
"organizationName" :"Example Company",
...
}
作为人为的一个例子,这里把字符串文件本地化为倒序的英文,如下:
/* Organizationname */
"ExampleCompany" = "ynapmoC elpmaxE";
除了字符串文件,本地化至倒序的英语还需要在pass里面查询相关的图片,并提供一个需要用到的所有内容的倒序英文版。
第三章 构建和发布一个Pass(通行证)
在准备发布pass之前,需要对pass做如下事情:
l 创建manifest文件
l 通过加密签名manifest
l 压缩pass
3.1.制作Manifest
manifest是一个JSON字典,命名为manifest.json。key是pass包中的文件路径。每个key的值是那个文件内容的SHA-1哈希。例如,图2-1显示了一个pass包的目录结构,而列表2-1显示了相应的manifest:
图2-1 一个pass的目录结构

列表2-1 manifest文件
{
"background.png" :"844a6063e4192f4f4f34b2cf36996b6b06a6f355",
"background@2x.png" :"56c66001a5edb87c2b58180daa3e443dcac887e4",
"pass.json" :"a4f8506e362888755ddf744365cc3cf615e4e6b1",
"es.lproj/pass.strings" :"b698506e362888755ddf744365cc3cf615e4e6b1",
"icon.png" :"105d0f906f633c378d738477fef0d51e0ccec2d2",
"icon@2x.png" :"f5c3db953176da14d6d1c3c27de12e14119173da",
"logo.png" :"78a778accde869cea3364bb828074d7a8f0067ce",
"logo@2x.png" :"af77501cac762637bdb4545b3b758ae4b4632422",
"zh.lproj/pass.strings" :"a4f8506e362888755ddf744365cc3cf615e4e6b1",
"zh.lproj/background.png" :"2888755ddfa4f8506e36744365cc3cf615e4e6b1",
"zh.lproj/background.png@2x": "f8506e362a4888755ddf744365cc3cf615e4e6b1"
}
3.2.制作签名
签名是一个独立的PKCS#7签名清单。获取和安装证书,请看“设置开发环境”。列表2-2演示了使用openssl命令的过程,在开发过程中,你可能会需要使用到。
列表2-2 通过终端命令来创建签名
openssl pkcs12-in "My PassKit Cert.p12" -clcerts -nokeys -out certificate.pem
openssl pkcs12-in "My PassKit Cert.p12" -nocerts -out key.pem
# You can keepcertificate.pem and key.pem to use again later,
# or you candelete them after signing.
openssl smime-binary -sign \
-signer certificate.pem -inkey key.pem\
-in manifest.json -out signature \
-outform DER
创建签名是建立在由你提供的pass之上。这也提供了一个保护,即拒绝一个攻击者发布一个pass,这个pass看起来是来自你这里,由于攻击者没有使用你的pass进行签名,所以可以为你提供一个保护的机制。
3.3.压缩Pass
使用ZIP来把pass包里面的内容压缩为以.pkpass为后缀的单个文件,在发布的时候使用这个文件。例如,在终端执行下面的命令:
zip -rexample.pkpass -x '*.DS_Store' .../path/to/pass_package/*
3.4.向用户发布Pass(通行证)
这里有三种方法来向用户发布通行证:
l 以mail附件的形式发送给用户,用户使用手机中的Mail把pass添加到库中。
l 把pass部署在web server上,并给用户提供一个URL——例如,在网站上或email上放一个链接——使用手机中的Safari浏览器把pass添加到库中。
l 写一个与pass库交互的应用程序,章节“与用户的pass(通行证)进行交互”有描述。
如果是以mail附件的形式或者把pass部署在web上,需要什么MIME type为application/vnd.apple.pkpass,这样Mail或Safari才会将其当做pass来处理。
更新pass是可选的,但是它可以为用户更新有意义的内容。通过更新,可以改变已经添加到用户pass库里面的某个pass的内容。例如,某个航班延误了,可以发送了一个更新通知,可以提醒用户最新的起飞时间。除了pass type identifier和serial number以外,pass中的所有内容都可以通过推送更新来改变。
...
这里有5点需要服务器做出响应。URL中的一部分是由协议定义的,告诉你的服务请求的是什么,而有一些则定义在pass中。图3-1演示了与服务器的典型交互;注意,在更新pass时,这里不仅仅是一个往返。
deviceidentifier由设备决定,用于用户设备和服务之间的共享secret。它是一个设备的唯一标示,并用于授权一个请求。例如,所有pass的请求中,在创建请求时没有提供授权token。授权token由每个pass指定——device identifier可以满足请求的有效。
为了更新pass,服务器首先需要发送一个空的push通知至设备,并并把pass type ID作为push主题。当用户设备收到push通知,它会请求已经改变了的serialnumber列表,然后请求pass的最新版本。
Pass Kit框架提供的Objective-C API 用于与用户的pass库交互。有两类主要的应用程序使用Pass Kit框架:把pass添加到pass库的应用程序,如Mail和Sfari,以及集成了pass库的应用程序,它可以与pass库中的内容进行交互,以提供更丰富的体验。例如,FidoNet mail客户端只需要把pass添加到库中。相反,一个咖啡厅可以提供一个应用程序,让你预定一个座位,然后可以添加pass至pass库中,在结账的时候就可以使用。
PKPassLibrary类代表了pass库,而PKPass类代表了单个pass。框架同样提供了一个view controller,即PKAddPassesViewController类,用来显示某个pass并工用户将pass添加至pass库中。这些类以model-level形式供你访问pass库。你的应用程序负责展现数据。
3、 使用PKPassLibrary的类方法containsPass:来检测pass是否在库中。你的应用程序可以使用该方法来检测pass,可以不用完全的从库中读取pass。
要想替换某个pass,使用PKPassLibrary类的replacePassWithPass:方法。替换的时候pass的pass type identifier和serial number必须要匹配。