用于为 iOS 钱包(以前称为 Passbook)生成 Passbook 包的 .NET 标准库
如果您使用 dotnet-passbook,请考虑请我喝杯咖啡
dotnet-passbook 也可以从 NuGet 下载
Install-Package dotnet-passbook
为 Apple Passbook 创建通行证非常简单,但需要使用 PKI 来签署清单文件,这并不简单!在构建 PassVerse.com(不再可用)的过程中,我创建了一个执行所有步骤的 .Net 库。我决定为其他 .NET 开发人员开源这个库。
该解决方案需要 Visual Studio 2017 或更高版本。该库是为 .NET Standard 2.0 构建的。
在运行 PassGenerator 之前,您需要确保已安装所有必需的证书。需要有两个。
首先,您需要从开发者门户获取 Passbook 证书。您必须拥有 iOS 开发者帐户。
其次,你需要Apple WWDR(全球开发者关系)证书。您可以从这里下载:http://www.apple.com/certificateauthority/。
根据您生成 Passbook 证书的时间,您将需要 G1 或 G4 证书。如果您的存折证书是在 2022 年 1 月 27 日或之前生成的,请使用 G1。否则使用G4。
此处列出的其他“全球开发者关系”证书不适用于 Apple Wallet。 (“抱歉,您的 Pass 目前无法安装到 Passbook。”)
如果您使用的是 Windows 计算机,我的博客上有关于使用 IIS 生成证书的说明
如果您使用的是 Linux/macOS 或者更喜欢在 Windows 上使用 OpenSSL,请查看 using-openssl.md 以获取有关如何使用 OpenSSL 创建必要证书的说明。
移动证书时,请确保您的 Passbook 证书始终包含私钥组件,否则签名将失败。
要生成通行证,请首先声明 PassGenerator。
PassGenerator generator = new PassGenerator ( ) ;
接下来,创建一个 PassGeneratorRequest。这是一个原始请求,您可以完全有权添加您想要创建的通行证所需的所有字段。每个通道都分为几个部分。每个部分都以不同的方式呈现,具体取决于您尝试生成的通道的样式。有关详细信息,请参阅 Apple 的 Passbook 编程指南。下面的示例将展示如何创建非常基本的登机牌。
由于每遍都有一组必填数据,因此请先填写。
PassGeneratorRequest request = new PassGeneratorRequest ( ) ;
request . PassTypeIdentifier = " pass.tomsamcguinness.events " ;
request . TeamIdentifier = " RW121242 " ;
request . SerialNumber = " 121212 " ;
request . Description = " My first pass " ;
request . OrganizationName = " Tomas McGuinness " ;
request . LogoText = " My Pass " ;
颜色可以以 HTML 格式或 RGB 格式指定。
request . BackgroundColor = " #FFFFFF " ;
request . LabelColor = " #000000 " ;
request . ForegroundColor = " #000000 " ;
request . BackgroundColor = " rgb(255,255,255) " ;
request . LabelColor = " rgb(0,0,0) " ;
request . ForegroundColor = " rgb(0,0,0) " ;
您必须提供 Apple WWDR 和 Passbook 证书作为 X509Certificate 实例。注意:这是对以前版本的更改。
request . AppleWWDRCACertificate = new X509Certificate ( .. . ) ;
request . PassbookCertificate = new X509Certificate ( .. . ) ;
当您在云中创建 X509Certificate 实例时,您可能会遇到签名过程问题。我建议您使用 MachineKeySet 和 Exportable X509KeyStorageFlags。
X509KeyStorageFlags flags = X509KeyStorageFlags . MachineKeySet | X509KeyStorageFlags . Exportable ;
X509Certificate2 certificate = new X509Certificate2 ( bytes , password , flags ) ;
接下来,定义要使用的图像。您必须始终包含标准尺寸和视网膜尺寸的图像。图像以 byte[] 形式提供。
request . Images . Add ( PassbookImage . Icon , System . IO . File . ReadAllBytes ( Server . MapPath ( " ~/Icons/icon.png " ) ) ) ;
request . Images . Add ( PassbookImage . Icon2X , System . IO . File . ReadAllBytes ( Server . MapPath ( " ~/Icons/[email protected] " ) ) ) ;
request . Images . Add ( PassbookImage . Icon3X , System . IO . File . ReadAllBytes ( Server . MapPath ( " ~/Icons/[email protected] " ) ) ) ;
您现在可以提供更多通行证特定信息。必须设置样式,然后将所有信息添加到所需部分的字段中。对于登机牌,字段添加到三个部分;主要、次要和辅助。
request . Style = PassStyle . BoardingPass ;
request . AddPrimaryField ( new StandardField ( " origin " , " San Francisco " , " SFO " ) ) ;
request . AddPrimaryField ( new StandardField ( " destination " , " London " , " LDN " ) ) ;
request . AddSecondaryField ( new StandardField ( " boarding-gate " , " Gate " , " A55 " ) ) ;
request . AddAuxiliaryField ( new StandardField ( " seat " , " Seat " , " G5 " ) ) ;
request . AddAuxiliaryField ( new StandardField ( " passenger-name " , " Passenger " , " Thomas Anderson " ) ) ;
request . TransitType = TransitType . PKTransitTypeAir ;
您可以添加条形码。
request . AddBarcode ( BarcodeType . PKBarcodeFormatPDF417 , " 01927847623423234234 " , " ISO-8859-1 " , " 01927847623423234234 " ) ;
从 iOS 9 开始,现在支持多个条形码。此辅助方法支持此新功能。如果您想支持 iOS 8 及更早版本,可以使用方法 SetBarcode()。
要将通行证链接到现有应用程序,您可以将应用程序的 Apple ID 添加到 AssociatedStoreIdentifiers 数组。
request . AssociatedStoreIdentifiers . Add ( 551768478 ) ;
最后,通过将请求传递到生成器的实例来生成通行证。这将创建签名清单并将所有图像文件打包到 zip 中。
byte [ ] generatedPass = generator . Generate ( request ) ;
例如,如果您使用 ASP.NET MVC,则可以将此 byte[] 作为 Passbook 包返回
return new FileContentResult ( generatedPass , " application/vnd.apple.pkpass " ) ;
iOS 15 引入了使用单个 .pkpasses 文件捆绑和分发多个通道的功能。您还可以通过传入请求值字典和表示每个单独请求的文件名的字符串键来生成传递包。
PassGeneratorRequest myFirstRequest = new PassGeneratorRequest ( ) ;
PassGeneratorRequest mySecondRequest = new PassGeneratorRequest ( ) ;
// Build out your requests
List < PassGeneratorRequest > requests = new List < PassGeneratorRequest > ;
requests . Add ( myFirstRequest ) ;
request . Add ( mySecondRequest ) ;
byte [ ] generatedBundle = generator . Generate ( requests ) ;
生成的字节数组的处理方式几乎与单个.pkpass
文件相同,但具有不同的扩展名和 MIME 类型 ( pkpasses )
return new FileContentResult ( generatedBundle , " application/vnd.apple.pkpasses " )
{
FileDownloadName = " tickets.pkpasses.zip "
} ;
PassGenerator
类符合IPassGenerator
接口,该接口公开用于生成通行证的方法。如果您有一些构建通行证生成请求的自定义逻辑,您可以使用任何模拟库轻松模拟IPassGenerator
接口,并测试您的逻辑是否使用正确的生成器请求调用Generate
方法。
假设您有一个服务通过 DI 接收生成器请求并根据该请求生成通行证。
class PassGeneratorService ( IPassGenerator passGenerator )
{
public byte [ ] GeneratePassWithLogoTextAndBackgroundColor ( String logoText , String backgroundColor )
{
// make a request based on parameters
....
passGenerator . Generate ( request ) ;
}
}
您可以通过模拟IPassGenerator
接口并验证是否使用正确的请求调用了Generate
方法来轻松测试此服务。这是使用 NSubstitute 和 xUnit 的示例。
[ Fact ]
void ServiceUsesPassedParamsForRequest ( )
{
// Arrange
var passGeneratorMock = Substitute . For < IPassGenerator > ( ) ;
var sut = new PassGeneratorService ( passGeneratorMock ) ;
// Act
sut . GeneratePassWithLogoTextAndBackgroundColor ( " Cup'o'Joe " , " #0CAFE0 " ) ;
// Assert/Verify
passGeneratorMock . Received ( ) . Generate ( Arg . Is < PassGeneratorRequest > ( r =>
{
r . LogoText == " Cup'o'Joe " && r . BackgroundColor == " #0CAFE0 "
} ) ) ;
}
测试逻辑是否正常运行的另一种方法是测试创建的PassGeneratorRequest
对象本身。您可以创建一个请求对象,并根据您的逻辑设置属性,然后测试请求对象是否正确创建。考虑这个基本的请求构建器:
class PassGeneratorRequestBuilder
{
public PassGeneratorRequest BuildRequestWithLogoTextAndBackgroundColor ( String logoText , String backgroundColor )
{
var request = new PassGeneratorRequest ( ) ;
request . LogoText = logoText ;
request . BackgroundColor = backgroundColor ;
return request ;
}
}
您可以通过创建请求对象并验证属性设置是否正确来测试此构建器。
[ Fact ]
void RequestBuilderSetsPropertiesCorrectly ( )
{
// Arrange
var sut = new PassGeneratorRequestBuilder ( ) ;
// Act
var request = sut . BuildRequestWithLogoTextAndBackgroundColor ( " Cup'o'Joe " , " #0CAFE0 " ) ;
// Assert
Assert . Equal ( " Cup'o'Joe " , request . LogoText ) ;
Assert . Equal ( " #0CAFE0 " , request . BackgroundColor ) ;
}
现在您只需确保请求在发送到PassGenerator
类的过程中没有发生更改/突变。
如果您创建的通行证似乎无法在 iOS 或模拟器中打开,则有效负载可能无效。为了帮助排除故障,我创建了这个简单的工具 - https://pkpassvalidator.azurewebsites.net - 只需通过它运行你的pkpass
文件,它可能会告诉你出了什么问题。该工具是新工具(2018 年 7 月),并不能检查所有内容。我将尝试为生成器本身添加更多验证。
如果您在 IIS 应用程序中运行签名代码,则在访问证书的私钥时可能会遇到一些问题。要解决此问题,请打开 MMC => 添加证书(本地计算机)管理单元 => 证书(本地计算机)=> 个人 => 证书 => 右键单击感兴趣的证书 => 所有任务 => 管理私钥 => 添加 IIS AppPoolAppPoolName 并授予其完全控制权。将“AppPoolName”替换为您的应用程序运行所在的应用程序池的名称。 (有时是IIS_IUSRS
)
为了能够更新您的通行证,您必须为其提供回调。生成请求时,您必须为其提供 AuthenticationToken 和 WebServiceUrl。这两个值都是必需的。默认情况下,WebServiceUrl 必须是 HTTPS,但您可以在您正在测试的任何设备上的 iOS 开发人员选项中禁用此要求。
身份验证令牌是一个字符串,将包含在向 API 发出的所有请求的标头中。您有责任验证此令牌。
request . AuthenticationToken = " <a secret to ensure authorized access> " ;
request . WebServiceUrl = " https://<your api> " ;
您指向的 Web 服务必须支持 Apple 的协议,如 PassKit Web 服务参考中所述
从版本 2.0.1 开始,现已支持 NFC 密钥。要使用它们,只需使用新的 NFC 对象设置 Nfc 属性即可。消息和编码的公钥值都是强制性的。
PassGeneratorRequest request = new PassGeneratorRequest ( ) ;
request . Nfc = new Nfc ( " THE NFC Message " , " <encoded private key> " ) ;
不幸的是,我无法提供有关所需值的任何信息,因为它不公开。如果有人知道这里的内容,我会非常乐意向我的库添加更改以支持此密钥。
欢迎所有拉取请求!如果您遇到无法解决的问题,请提出问题或给我发送电子邮件至 [email protected] 或在 Twitter 上关注我 @tomasmcguinness
dotnet-passbook 根据 MIT 许可证分发:http://tomasmcguinness.mit-license.org/