Une bibliothèque standard .NET pour générer des packages Passbook pour iOS Wallet (anciennement Passbook)
Si vous utilisez dotnet-passbook, pensez à m'offrir une tasse de café
dotnet-passbook est également disponible en téléchargement depuis NuGet
Install-Package dotnet-passbook
Créer des passes pour le Passbook d'Apple est assez simple, mais nécessite l'utilisation de PKI pour signer les fichiers manifestes, ce qui n'est pas si simple ! Au cours de la création de PassVerse.com (n'est plus disponible), j'ai créé une bibliothèque .Net qui effectue toutes les étapes. J'ai décidé d'ouvrir cette bibliothèque pour d'autres développeurs .NET.
La solution nécessite Visual Studio 2017 ou supérieur. La bibliothèque est conçue pour .NET Standard 2.0.
Avant d'exécuter PassGenerator, vous devez vous assurer que tous les certificats nécessaires sont installés. Il y en a deux obligatoires.
Tout d’abord, vous avez besoin de votre certificat Passbook, que vous obtenez sur le portail des développeurs. Vous devez disposer d'un compte développeur iOS.
Deuxièmement, vous avez besoin du certificat Apple WWDR (WorldWide Developer Relations). Vous pouvez le télécharger ici http://www.apple.com/certificateauthority/.
Selon le moment où vous avez généré votre certificat Passbook, vous aurez besoin du certificat G1 ou G4. Si vous avez généré votre certificat sur livret au plus tard le 27 janvier 2022, utilisez G1. Sinon, utilisez G4.
Les autres certificats « Worldwide Developer Relations » répertoriés ici ne fonctionneront pas avec Apple Wallet. ("Désolé, votre Pass ne peut pas être installé sur Passbook pour le moment.")
Il y a des instructions sur mon blog pour générer un certificat à l'aide d'IIS si vous utilisez une machine Windows
Si vous utilisez Linux/macOS ou si vous préférez utiliser OpenSSL sous Windows, consultez using-openssl.md pour obtenir des instructions sur la façon de créer les certificats nécessaires à l'aide d'OpenSSL.
Lorsque vous déplacez des certificats, assurez-vous que votre certificat Passbook inclut toujours le composant de clé privée, sinon la signature échouera.
Pour générer un pass, commencez par déclarer un PassGenerator.
PassGenerator generator = new PassGenerator ( ) ;
Ensuite, créez un PassGeneratorRequest. Il s'agit d'une requête brute qui vous donne tout le pouvoir d'ajouter tous les champs nécessaires au pass que vous souhaitez créer. Chaque passe est décomposé en plusieurs sections. Chaque section est rendue d'une manière différente, en fonction du style de la passe que vous essayez de produire. Pour plus d'informations à ce sujet, veuillez consulter le Guide de programmation Passbook d'Apple. L'exemple ci-dessous montre comment créer une carte d'embarquement très basique.
Étant donné que chaque pass comporte un ensemble de données obligatoires, remplissez-les en premier.
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 " ;
Les couleurs peuvent être spécifiées au format HTML ou au format RVB.
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) " ;
Vous devez fournir à la fois le WWDR Apple et votre certificat Passbook en tant qu'instances X509Certificate. REMARQUE : Il s'agit d'un changement par rapport aux versions précédentes.
request . AppleWWDRCACertificate = new X509Certificate ( .. . ) ;
request . PassbookCertificate = new X509Certificate ( .. . ) ;
Lorsque vous créez les instances X509Certificate dans le cloud, vous pouvez rencontrer des problèmes avec le processus de signature. Je vous recommande d'utiliser MachineKeySet et Exportable X509KeyStorageFlags.
X509KeyStorageFlags flags = X509KeyStorageFlags . MachineKeySet | X509KeyStorageFlags . Exportable ;
X509Certificate2 certificate = new X509Certificate2 ( bytes , password , flags ) ;
Ensuite, définissez les images que vous souhaitez utiliser. Vous devez toujours inclure des images de taille standard et rétine. Les images sont fournies sous forme d'octet[].
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] " ) ) ) ;
Vous pouvez désormais fournir des informations plus spécifiques au laissez-passer. Le style doit être défini, puis toutes les informations sont ensuite ajoutées aux champs des sections requises. Pour une carte d'embarquement, les champs sont répartis en trois sections ; primaire, secondaire et auxiliaire.
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 ;
Vous pouvez ajouter un code-barres.
request . AddBarcode ( BarcodeType . PKBarcodeFormatPDF417 , " 01927847623423234234 " , " ISO-8859-1 " , " 01927847623423234234 " ) ;
À partir d'iOS 9, plusieurs codes-barres sont désormais pris en charge. Cette méthode d'assistance prend en charge cette nouvelle fonctionnalité. Si vous souhaitez prendre en charge iOS 8 et versions antérieures, vous pouvez utiliser la méthode SetBarcode().
Pour lier le pass à une application existante, vous pouvez ajouter l'identifiant Apple de l'application au tableau AssociatedStoreIdentifiers.
request . AssociatedStoreIdentifiers . Add ( 551768478 ) ;
Enfin, générez le pass en passant la requête dans l'instance du Generator. Cela créera le manifeste signé et regroupera tous les fichiers image dans un fichier zip.
byte [ ] generatedPass = generator . Generate ( request ) ;
Si vous utilisez ASP.NET MVC par exemple, vous pouvez renvoyer cet octet[] sous forme de package Passbook
return new FileContentResult ( generatedPass , " application/vnd.apple.pkpass " ) ;
iOS 15 a introduit la possibilité de regrouper et de distribuer plusieurs passes à l'aide d'un seul fichier .pkpasses. Vous pouvez également générer des ensembles de passes en transmettant un dictionnaire de valeurs de requêtes et de clés de chaîne qui représentent le nom de fichier de chaque requête individuelle.
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 ) ;
Le tableau d'octets résultant est traité presque de la même manière qu'un fichier .pkpass
unique, mais avec une extension et un type MIME ( pkpasses ) différents.
return new FileContentResult ( generatedBundle , " application/vnd.apple.pkpasses " )
{
FileDownloadName = " tickets.pkpasses.zip "
} ;
La classe PassGenerator
est conforme à l'interface IPassGenerator
qui expose les méthodes utilisées pour générer les passes. Si vous disposez d'une logique personnalisée qui crée la demande de génération de passe, vous pouvez facilement simuler l'interface IPassGenerator
à l'aide de n'importe quelle bibliothèque simulée et tester si votre logique appelle la méthode Generate
avec la demande de générateur correcte.
Supposons que vous disposiez d'un service qui reçoit une demande de générateur via DI et génère un laissez-passer basé sur la demande.
class PassGeneratorService ( IPassGenerator passGenerator )
{
public byte [ ] GeneratePassWithLogoTextAndBackgroundColor ( String logoText , String backgroundColor )
{
// make a request based on parameters
....
passGenerator . Generate ( request ) ;
}
}
Vous pouvez facilement tester ce service en simulant l'interface IPassGenerator
et en vérifiant que la méthode Generate
est appelée avec la requête correcte. Voici un exemple utilisant NSubstitute et 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 "
} ) ) ;
}
Une autre façon de tester si votre logique fonctionne bien consiste à tester l'objet PassGeneratorRequest
créé lui-même. Vous pouvez créer un objet de requête et définir les propriétés en fonction de votre logique, puis tester si l'objet de requête est créé correctement. Considérez ce générateur de requêtes de base :
class PassGeneratorRequestBuilder
{
public PassGeneratorRequest BuildRequestWithLogoTextAndBackgroundColor ( String logoText , String backgroundColor )
{
var request = new PassGeneratorRequest ( ) ;
request . LogoText = logoText ;
request . BackgroundColor = backgroundColor ;
return request ;
}
}
Vous pouvez tester ce générateur en créant un objet de requête et en vérifiant si les propriétés sont correctement définies.
[ 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 ) ;
}
Il ne vous reste plus qu'à vous assurer que la requête n'est pas modifiée/mutée lors de son chemin vers la classe PassGenerator
.
Si les passes que vous créez ne semblent pas s'ouvrir sur iOS ou dans le simulateur, la charge utile n'est probablement pas valide. Pour faciliter le dépannage, j'ai créé cet outil simple - https://pkpassvalidator.azurewebsites.net - exécutez simplement votre fichier pkpass
via celui-ci et cela pourrait vous donner une idée de ce qui ne va pas. L'outil est nouveau (juillet 2018) et ne vérifie pas absolument tout. Je vais essayer d'ajouter plus de validation au générateur lui-même.
Si vous exécutez le code de signature dans une application IIS, vous pourriez rencontrer des problèmes pour accéder à la clé privée de vos certificats. Pour résoudre ce problème, ouvrez le composant logiciel enfichable MMC => Ajouter des certificats (ordinateur local) => Certificats (ordinateur local) => Personnel => Certificats => Cliquez avec le bouton droit sur le certificat d'intérêt => Toutes les tâches => Gérer la clé privée => Ajouter IIS AppPoolAppPoolName et accordez-lui un contrôle total. Remplacez « AppPoolName » par le nom du pool d'applications sous lequel votre application s'exécute. (parfois IIS_IUSRS
)
Pour pouvoir mettre à jour votre pass, vous devez lui fournir un rappel. Lors de la génération de votre requête, vous devez lui fournir un AuthenticationToken et un WebServiceUrl. Ces deux valeurs sont requises. Le WebServiceUrl doit être HTTPS par défaut, mais vous pouvez désactiver cette exigence dans les options du développeur iOS sur n'importe quel appareil sur lequel vous testez.
Le jeton d'authentification est une chaîne qui sera incluse dans l'en-tête de toutes les requêtes adressées à votre API. Il est de votre responsabilité de valider ce token.
request . AuthenticationToken = " <a secret to ensure authorized access> " ;
request . WebServiceUrl = " https://<your api> " ;
Le service Web vers lequel vous pointez doit prendre en charge le protocole Apple, décrit dans la référence du service Web PassKit.
Depuis la version 2.0.1, les clés NFC sont désormais supportées. Pour les utiliser, définissez simplement la propriété Nfc avec un nouvel objet NFC. Le message et les valeurs de clé publique codées sont obligatoires.
PassGeneratorRequest request = new PassGeneratorRequest ( ) ;
request . Nfc = new Nfc ( " THE NFC Message " , " <encoded private key> " ) ;
Malheureusement, je ne peux fournir aucune information sur les valeurs requises car elles ne sont pas disponibles publiquement. Si quelqu'un sait ce qui se passe ici, je serais plus qu'heureux d'ajouter des modifications à ma bibliothèque pour prendre en charge cette clé.
Toutes les pull request sont les bienvenues ! Si vous rencontrez un problème que vous ne pouvez pas résoudre, veuillez signaler un problème ou envoyez-moi un e-mail à [email protected] ou suivez-moi sur Twitter @tomasmcguinness
dotnet-passbook est distribué sous licence MIT : http://tomasmcguinness.mit-license.org/