Uma biblioteca .NET Standard para gerar pacotes Passbook para iOS Wallet (anteriormente Passbook)
Se você usa dotnet-passbook, considere me comprar uma xícara de café
dotnet-passbook também está disponível para download no NuGet
Install-Package dotnet-passbook
Criar passes para o Passbook da Apple é bastante simples, mas requer o uso de PKI para assinar arquivos de manifesto, o que não é tão simples! Durante a construção do PassVerse.com (não está mais disponível), criei uma biblioteca .Net que executa todas as etapas. Decidi abrir o código-fonte desta biblioteca para outros desenvolvedores .NET.
A solução requer Visual Studio 2017 ou superior. A biblioteca foi criada para .NET Standard 2.0.
Antes de executar o PassGenerator, você precisa garantir que todos os certificados necessários estejam instalados. São dois obrigatórios.
Em primeiro lugar, você precisa do seu certificado Passbook, obtido no Portal do Desenvolvedor. Você deve ter uma conta de desenvolvedor iOS.
Em segundo lugar, você precisa do certificado Apple WWDR (WorldWide Developer Relationships). Você pode baixá-lo aqui http://www.apple.com/certificateauthority/.
Dependendo de quando você gerou seu certificado Passbook, você precisará do certificado G1 ou G4. Se você gerou seu certificado de caderneta em ou antes de 27 de janeiro de 2022, use G1. Caso contrário, use G4.
Os outros certificados de "Relações Mundiais com Desenvolvedores" listados aqui não funcionarão com a Apple Wallet. ("Desculpe, seu Pass não pode ser instalado no Passbook neste momento.")
Há instruções no meu blog para gerar um certificado usando IIS se você estiver usando uma máquina Windows
Se você usa Linux/macOS ou prefere usar OpenSSL no Windows, consulte using-openssl.md para obter instruções sobre como criar os certificados necessários usando OpenSSL.
Ao mover certificados, certifique-se de que seu certificado Passbook sempre inclua o componente de chave privada, caso contrário a assinatura falhará.
Para gerar um passe, comece declarando um PassGenerator.
PassGenerator generator = new PassGenerator ( ) ;
Em seguida, crie um PassGeneratorRequest. Esta é uma solicitação bruta que lhe dá total poder para adicionar todos os campos necessários para o passe que deseja criar. Cada passagem é dividida em várias seções. Cada seção é renderizada de uma maneira diferente, com base no estilo do passe que você está tentando produzir. Para obter mais informações sobre isso, consulte o Guia de programação do Passbook da Apple. O exemplo abaixo mostrará como criar um cartão de embarque muito básico.
Como cada passe possui um conjunto de dados obrigatórios, preencha-os primeiro.
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 " ;
As cores podem ser especificadas no formato HTML ou 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) " ;
Você deve fornecer o Apple WWDR e seu certificado Passbook como instâncias X509Certificate. NOTA: Esta é uma alteração em relação às versões anteriores.
request . AppleWWDRCACertificate = new X509Certificate ( .. . ) ;
request . PassbookCertificate = new X509Certificate ( .. . ) ;
Ao criar as instâncias X509Certificate na nuvem, você poderá enfrentar problemas com o processo de assinatura. Eu recomendo que você use MachineKeySet e Exportable X509KeyStorageFlags.
X509KeyStorageFlags flags = X509KeyStorageFlags . MachineKeySet | X509KeyStorageFlags . Exportable ;
X509Certificate2 certificate = new X509Certificate2 ( bytes , password , flags ) ;
A seguir, defina as imagens que você deseja usar. Você deve sempre incluir imagens de tamanho padrão e retina. As imagens são fornecidas como 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] " ) ) ) ;
Agora você pode fornecer mais informações específicas do passe. O Estilo deve ser definido e então todas as informações são adicionadas aos campos nas seções obrigatórias. Para um cartão de embarque, os campos são somados em três seções; primário, secundário e auxiliar.
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 ;
Você pode adicionar um código de barras.
request . AddBarcode ( BarcodeType . PKBarcodeFormatPDF417 , " 01927847623423234234 " , " ISO-8859-1 " , " 01927847623423234234 " ) ;
A partir do iOS 9, vários códigos de barras agora são suportados. Este método auxiliar oferece suporte a esse novo recurso. Se você quiser oferecer suporte ao iOS 8 e versões anteriores, poderá usar o método SetBarcode().
Para vincular o passe a um aplicativo existente, você pode adicionar o ID Apple do aplicativo à matriz AssociatedStoreIdentifiers.
request . AssociatedStoreIdentifiers . Add ( 551768478 ) ;
Por fim, gere o passe passando a solicitação para a instância do Gerador. Isso criará o manifesto assinado e empacotará todos os arquivos de imagem em zip.
byte [ ] generatedPass = generator . Generate ( request ) ;
Se você estiver usando ASP.NET MVC, por exemplo, você pode retornar este byte[] como um pacote Passbook
return new FileContentResult ( generatedPass , " application/vnd.apple.pkpass " ) ;
O iOS 15 introduziu a capacidade de agrupar e distribuir vários passes usando um único arquivo .pkpasses. Você também pode gerar pacotes de passes passando um dicionário de valores de solicitações e chaves de string que representam o nome do arquivo para cada solicitação individual.
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 ) ;
A matriz de bytes resultante é tratada quase de forma idêntica a um arquivo .pkpass
singular, mas com uma extensão e tipo MIME diferente ( pkpasses )
return new FileContentResult ( generatedBundle , " application/vnd.apple.pkpasses " )
{
FileDownloadName = " tickets.pkpasses.zip "
} ;
A classe PassGenerator
está em conformidade com a interface IPassGenerator
que expõe os métodos usados para gerar os passes. Se você tiver alguma lógica personalizada que cria a solicitação de geração de passagem, poderá simular facilmente a interface IPassGenerator
usando qualquer biblioteca de simulação e testar se sua lógica chama o método Generate
com a solicitação correta do gerador.
Digamos que você tenha um serviço que recebe uma solicitação do gerador por meio de DI e gera um passe com base na solicitação.
class PassGeneratorService ( IPassGenerator passGenerator )
{
public byte [ ] GeneratePassWithLogoTextAndBackgroundColor ( String logoText , String backgroundColor )
{
// make a request based on parameters
....
passGenerator . Generate ( request ) ;
}
}
Você pode testar facilmente esse serviço simulando a interface IPassGenerator
e verificando se o método Generate
é chamado com a solicitação correta. Aqui está um exemplo usando NSubstitute e 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 "
} ) ) ;
}
Outra forma de testar se sua lógica funciona bem é testar o próprio objeto PassGeneratorRequest
criado. Você pode criar um objeto de solicitação e definir as propriedades com base em sua lógica e depois testar se o objeto de solicitação foi criado corretamente. Considere este construtor de solicitação básico:
class PassGeneratorRequestBuilder
{
public PassGeneratorRequest BuildRequestWithLogoTextAndBackgroundColor ( String logoText , String backgroundColor )
{
var request = new PassGeneratorRequest ( ) ;
request . LogoText = logoText ;
request . BackgroundColor = backgroundColor ;
return request ;
}
}
Você pode testar esse construtor criando um objeto de solicitação e verificando se as propriedades estão configuradas corretamente.
[ 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 ) ;
}
Agora você só precisa ter certeza de que a solicitação não foi alterada/mutada no caminho para a classe PassGenerator
.
Se os passes criados não parecem abrir no iOS ou no simulador, a carga provavelmente é inválida. Para ajudar na solução de problemas, criei esta ferramenta simples - https://pkpassvalidator.azurewebsites.net - basta executar seu arquivo pkpass
por meio dele e isso pode dar uma ideia do que está errado. A ferramenta é nova (julho/18) e não verifica absolutamente tudo. Vou tentar adicionar mais validação ao próprio gerador.
Se você estiver executando o código de assinatura em um aplicativo IIS, poderá ter alguns problemas ao acessar a chave privada dos seus certificados. Para resolver isso, abra o snap-in MMC => Adicionar certificados (computador local) => Certificados (computador local) => Pessoal => Certificados => Clique com o botão direito no certificado de interesse => Todas as tarefas => Gerenciar chave privada => Adicionar IIS AppPoolAppPoolName e conceda a ele controle total. Substitua "AppPoolName" pelo nome do pool de aplicativos em que seu aplicativo está sendo executado. (às vezes IIS_IUSRS
)
Para poder atualizar seu passe, você deve fornecer um retorno de chamada. Ao gerar sua solicitação, você deve fornecer um AuthenticationToken e um WebServiceUrl. Ambos os valores são obrigatórios. O WebServiceUrl deve ser HTTPS por padrão, mas você pode desativar esse requisito nas opções do desenvolvedor iOS em qualquer dispositivo em que estiver testando.
O token de autenticação é uma string que será incluída no cabeçalho de todas as solicitações feitas à sua API. É sua responsabilidade validar este token.
request . AuthenticationToken = " <a secret to ensure authorized access> " ;
request . WebServiceUrl = " https://<your api> " ;
O serviço da web para o qual você aponta deve oferecer suporte ao protocolo da Apple, descrito na Referência do serviço da Web do PassKit
A partir da versão 2.0.1, as chaves NFC agora são suportadas. Para utilizá-los, basta definir a propriedade NFC com um novo objeto NFC. Tanto a mensagem quanto os valores da chave pública codificada são obrigatórios.
PassGeneratorRequest request = new PassGeneratorRequest ( ) ;
request . Nfc = new Nfc ( " THE NFC Message " , " <encoded private key> " ) ;
Infelizmente, não posso fornecer nenhuma informação sobre os valores exigidos, uma vez que não estão disponíveis publicamente. Se alguém souber o que está acontecendo aqui, ficarei feliz em adicionar alterações à minha biblioteca para oferecer suporte a essa chave.
Todas as solicitações pull são bem-vindas! Se você encontrar um problema que não consegue resolver, levante-o ou envie-me um e-mail para [email protected] ou siga-me no Twitter @tomasmcguinness
dotnet-passbook é distribuído sob a licença do MIT: http://tomasmcguinness.mit-license.org/