Una biblioteca estándar .NET para generar paquetes Passbook para iOS Wallet (anteriormente Passbook)
Si usa dotnet-passbook, considere invitarme a una taza de café.
dotnet-passbook también está disponible para descargar desde NuGet
Install-Package dotnet-passbook
Crear pases para Passbook de Apple es bastante simple, pero requiere el uso de PKI para firmar archivos de manifiesto, ¡lo cual no es tan simple! Durante el proceso de construcción de PassVerse.com (ya no está disponible), creé una biblioteca .Net que realiza todos los pasos. Decidí abrir esta biblioteca para otros desarrolladores de .NET.
La solución requiere Visual Studio 2017 o superior. La biblioteca está diseñada para .NET Standard 2.0.
Antes de ejecutar PassGenerator, debe asegurarse de tener todos los certificados necesarios instalados. Se requieren dos.
En primer lugar, necesita su certificado Passbook, que obtiene del Portal de desarrollador. Debes tener una cuenta de desarrollador de iOS.
En segundo lugar, necesita el certificado Apple WWDR (WorldWide Developer Relations). Puede descargarlo desde aquí http://www.apple.com/certificateauthority/.
Dependiendo de cuándo generó su certificado Passbook, necesitará el certificado G1 o G4. Si generó su certificado de libreta el 27 de enero de 2022 o antes, utilice G1. De lo contrario utilice G4.
Los otros certificados de "Relaciones mundiales con desarrolladores" enumerados aquí no funcionarán con Apple Wallet. ("Lo sentimos, su Pass no se puede instalar en Passbook en este momento").
Hay instrucciones en mi blog para generar un certificado usando IIS si estás usando una máquina con Windows.
Si está en Linux/macOS o prefiere usar OpenSSL en Windows, consulte usando-openssl.md para obtener instrucciones sobre cómo crear los certificados necesarios usando OpenSSL.
Al mover certificados, asegúrese de que su certificado Passbook siempre incluya el componente de clave privada; de lo contrario, la firma fallará.
Para generar un pase, comience declarando un PassGenerator.
PassGenerator generator = new PassGenerator ( ) ;
A continuación, cree una PassGeneratorRequest. Esta es una solicitud sin formato que le brinda todo el poder para agregar todos los campos necesarios para el pase que desea crear. Cada pase se divide en varias secciones. Cada sección se representa de una manera diferente, según el estilo del pase que intentas producir. Para obtener más información sobre esto, consulte la Guía de programación Passbook de Apple. El siguiente ejemplo mostrará cómo crear una tarjeta de embarque muy básica.
Dado que cada pase tiene un conjunto de datos obligatorios, complételo primero.
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 " ;
Los colores se pueden especificar en formato HTML o en formato 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) " ;
Debe proporcionar tanto el WWDR de Apple como su certificado Passbook como instancias de X509Certificate. NOTA: Este es un cambio con respecto a versiones anteriores.
request . AppleWWDRCACertificate = new X509Certificate ( .. . ) ;
request . PassbookCertificate = new X509Certificate ( .. . ) ;
Cuando crea instancias de X509Certificate en la nube, puede experimentar problemas con el proceso de firma. Le recomiendo que utilice MachineKeySet y Exportable X509KeyStorageFlags.
X509KeyStorageFlags flags = X509KeyStorageFlags . MachineKeySet | X509KeyStorageFlags . Exportable ;
X509Certificate2 certificate = new X509Certificate2 ( bytes , password , flags ) ;
A continuación, defina las imágenes que desea utilizar. Siempre debes incluir imágenes tanto de tamaño estándar como de retina. Las imágenes se suministran 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] " ) ) ) ;
Ahora puede proporcionar más información específica del pase. Se debe configurar el estilo y luego toda la información se agrega a los campos de las secciones requeridas. Para una tarjeta de embarque, los campos se agregan a tres secciones; primaria, secundaria y 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 ;
Puede agregar un código de barras.
request . AddBarcode ( BarcodeType . PKBarcodeFormatPDF417 , " 01927847623423234234 " , " ISO-8859-1 " , " 01927847623423234234 " ) ;
A partir de iOS 9, ahora se admiten varios códigos de barras. Este método auxiliar admite esta nueva característica. Si desea admitir iOS 8 y versiones anteriores, puede utilizar el método SetBarcode().
Para vincular el pase a una aplicación existente, puede agregar el ID de Apple de la aplicación a la matriz AssociatedStoreIdentifiers.
request . AssociatedStoreIdentifiers . Add ( 551768478 ) ;
Finalmente, genere el pase pasando la solicitud a la instancia del Generador. Esto creará el manifiesto firmado y empaquetará todos los archivos de imagen en zip.
byte [ ] generatedPass = generator . Generate ( request ) ;
Si está utilizando ASP.NET MVC, por ejemplo, puede devolver este byte[] como un paquete Passbook.
return new FileContentResult ( generatedPass , " application/vnd.apple.pkpass " ) ;
iOS 15 introdujo la capacidad de agrupar y distribuir múltiples pases utilizando un archivo .pkpasses singular. También puede generar paquetes de pases pasando un diccionario de valores de solicitudes y claves de cadena que representen el nombre de archivo de cada solicitud 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 ) ;
La matriz de bytes resultante se trata de forma casi idéntica a un archivo .pkpass
singular, pero con una extensión y un tipo MIME diferentes ( pkpasses ).
return new FileContentResult ( generatedBundle , " application/vnd.apple.pkpasses " )
{
FileDownloadName = " tickets.pkpasses.zip "
} ;
La clase PassGenerator
se ajusta a la interfaz IPassGenerator
que expone los métodos utilizados para generar los pases. Si tiene alguna lógica personalizada que genere la solicitud de generación de pase, puede simular fácilmente la interfaz IPassGenerator
utilizando cualquier biblioteca simulada y probar si su lógica llama al método Generate
con la solicitud de generador correcta.
Supongamos que tiene un servicio que recibe una solicitud de generador a través de DI y genera un pase basado en la solicitud.
class PassGeneratorService ( IPassGenerator passGenerator )
{
public byte [ ] GeneratePassWithLogoTextAndBackgroundColor ( String logoText , String backgroundColor )
{
// make a request based on parameters
....
passGenerator . Generate ( request ) ;
}
}
Puede probar fácilmente este servicio burlándose de la interfaz IPassGenerator
y verificando que se llame al método Generate
con la solicitud correcta. Aquí hay un ejemplo usando NSubstitute y 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 "
} ) ) ;
}
Otra forma de probar si su lógica funciona bien es probar el objeto PassGeneratorRequest
creado. Puede crear un objeto de solicitud y establecer las propiedades según su lógica y luego probar si el objeto de solicitud se creó correctamente. Considere este generador de solicitudes básico:
class PassGeneratorRequestBuilder
{
public PassGeneratorRequest BuildRequestWithLogoTextAndBackgroundColor ( String logoText , String backgroundColor )
{
var request = new PassGeneratorRequest ( ) ;
request . LogoText = logoText ;
request . BackgroundColor = backgroundColor ;
return request ;
}
}
Puede probar este constructor creando un objeto de solicitud y verificando si las propiedades están configuradas correctamente.
[ 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 ) ;
}
Ahora solo tiene que asegurarse de que la solicitud no cambie/mute en su camino a la clase PassGenerator
.
Si los pases que crea no parecen abrirse en iOS o en el simulador, es probable que la carga útil no sea válida. Para ayudar a solucionar problemas, he creado esta sencilla herramienta: https://pkpassvalidator.azurewebsites.net. Simplemente ejecute su archivo pkpass
a través de ella y podría darle una idea de lo que está mal. La herramienta es nueva (julio 18) y no verifica absolutamente todo. Intentaré agregar más validación al propio generador.
Si está ejecutando el código de firma dentro de una aplicación IIS, es posible que tenga algunos problemas para acceder a la clave privada de sus certificados. Para resolver esto, abra MMC => Complemento Agregar certificados (computadora local) => Certificados (computadora local) => Personal => Certificados => Haga clic derecho en el certificado de interés => Todas las tareas => Administrar clave privada => Agregar IIS AppPoolAppPoolName y concédale control total. Reemplace "AppPoolName" con el nombre del grupo de aplicaciones en el que se ejecuta su aplicación. (a veces IIS_IUSRS
)
Para poder actualizar su pase, debe proporcionarle una devolución de llamada. Al generar tu solicitud, debes proporcionarle un AuthenticationToken y un WebServiceUrl. Ambos valores son obligatorios. WebServiceUrl debe ser HTTPS de forma predeterminada, pero puede desactivar este requisito en las opciones de desarrollador de iOS en cualquier dispositivo en el que esté realizando la prueba.
El token de autenticación es una cadena que se incluirá en el encabezado de todas las solicitudes realizadas a su API. Es tu responsabilidad validar este token.
request . AuthenticationToken = " <a secret to ensure authorized access> " ;
request . WebServiceUrl = " https://<your api> " ;
El servicio web al que apunte debe ser compatible con el protocolo de Apple, descrito en la Referencia del servicio web PassKit.
A partir de la versión 2.0.1, ahora se admiten las claves NFC. Para usarlos, simplemente configure la propiedad Nfc con un nuevo objeto NFC. Tanto el mensaje como los valores de clave pública codificados son obligatorios.
PassGeneratorRequest request = new PassGeneratorRequest ( ) ;
request . Nfc = new Nfc ( " THE NFC Message " , " <encoded private key> " ) ;
Lamentablemente, no puedo proporcionar ninguna información sobre los valores requeridos ya que no está disponible públicamente. Si alguien sabe lo que sucede aquí, estaría más que feliz de agregar cambios a mi biblioteca para admitir esta clave.
¡Todas las solicitudes de extracción son bienvenidas! Si encuentra un problema que no puede solucionar, plantéelo, envíeme un correo electrónico a [email protected] o sígueme en Twitter @tomasmcguinness.
dotnet-passbook se distribuye bajo la licencia MIT: http://tomasmcguinness.mit-license.org/