Стандартная библиотека .NET для создания пакетов Passbook для кошелька iOS (ранее Passbook).
Если вы используете 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 (WorldWide Developer Relations). Вы можете скачать его здесь http://www.apple.com/certificateauthority/.
В зависимости от того, когда вы создали сертификат Passbook, вам понадобится сертификат G1 или G4. Если вы сгенерировали сертификат сберегательной книжки не позднее 27 января 2022 г., используйте G1. В противном случае используйте G4.
Другие перечисленные здесь сертификаты «Worldwide Developer Relations» не будут работать с Apple Wallet. («К сожалению, в настоящее время ваш Pass невозможно установить в Passbook».)
В моем блоге есть инструкции по созданию сертификата с помощью IIS, если вы используете компьютер с Windows.
Если вы используете Linux/macOS или предпочитаете использовать OpenSSL в Windows, ознакомьтесь с инструкциями по созданию необходимых сертификатов с помощью OpenSSL на сайте using-openssl.md.
При перемещении сертификатов убедитесь, что ваш сертификат 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 ) ;
Затем определите изображения, которые вы будете использовать. Вы всегда должны включать как стандартные изображения, так и изображения размера Retina. Изображения предоставляются в формате 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
, и это может дать некоторое представление о том, что не так. Инструмент новый (18 июля) и не проверяет абсолютно все. Я постараюсь добавить дополнительную проверку к самому генератору.
Если вы используете код подписи в приложении 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> " ;
Веб-служба, на которую вы указываете, должна поддерживать протокол Apple, описанный в справочнике по веб-службе PassKit.
Начиная с версии 2.0.1 теперь поддерживаются ключи NFC. Чтобы использовать их, просто установите свойство Nfc с новым объектом NFC. Значения как сообщения, так и закодированного открытого ключа являются обязательными.
PassGeneratorRequest request = new PassGeneratorRequest ( ) ;
request . Nfc = new Nfc ( " THE NFC Message " , " <encoded private key> " ) ;
К сожалению, я не могу предоставить никакой информации о необходимых значениях, поскольку она недоступна публично. Если кто-нибудь знает, что здесь происходит, я был бы более чем рад добавить изменения в свою библиотеку для поддержки этого ключа.
Все запросы на вытягивание приветствуются! Если вы столкнулись с проблемой, которую не можете устранить, поднимите ее или напишите мне по адресу [email protected] или подпишитесь на меня в твиттере @tomasmcguinness.
dotnet-passbook распространяется по лицензии MIT: http://tomasmcguinness.mit-license.org/