iOS Wallet(이전의 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(WorldWide Developer Relations) 인증서가 필요합니다. 여기(http://www.apple.com/certificateauthority/)에서 다운로드할 수 있습니다.
Passbook 인증서를 생성한 시기에 따라 G1 또는 G4 인증서가 필요합니다. 2022년 1월 27일 또는 그 이전에 통장인증서를 생성한 경우 G1을 사용하세요. 그렇지 않으면 G4를 사용하십시오.
여기에 나열된 다른 "Worldwide Developer Relations" 인증서는 Apple Wallet에서 작동하지 않습니다. ("죄송합니다. 현재 패스북에 패스를 설치할 수 없습니다.")
Windows 컴퓨터를 사용하는 경우 IIS를 사용하여 인증서를 생성하는 방법에 대한 지침이 내 블로그에 있습니다.
Linux/macOS를 사용 중이거나 Windows에서 OpenSSL을 사용하려는 경우 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 및 내보내기 가능한 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
파일을 실행하면 무엇이 잘못되었는지 알 수 있습니다. 이 도구는 새로운 도구(7월 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> " ;
가리키는 웹 서비스는 PassKit 웹 서비스 참조에 설명된 Apple 프로토콜을 지원해야 합니다.
버전 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/)에 따라 배포됩니다.