{"id":34328159,"url":"https://github.com/tomasmcguinness/dotnet-passbook","last_synced_at":"2026-04-01T20:44:11.882Z","repository":{"id":3723871,"uuid":"4796832","full_name":"tomasmcguinness/dotnet-passbook","owner":"tomasmcguinness","description":"A .Net Library for generating Apple Passbook (Wallet) files for iOS. Please get involved by creating pull requests and opening issues!","archived":false,"fork":false,"pushed_at":"2025-11-18T09:45:34.000Z","size":15966,"stargazers_count":341,"open_issues_count":6,"forks_count":119,"subscribers_count":28,"default_branch":"master","last_synced_at":"2025-12-21T02:41:42.996Z","etag":null,"topics":["apple","c-sharp","dotnet","dotnet-standard","passbook","passkit"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tomasmcguinness.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"license.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"ko_fi":"tomasmcguinness","github":"tomasmcguinness"}},"created_at":"2012-06-26T15:23:20.000Z","updated_at":"2025-11-27T19:24:08.000Z","dependencies_parsed_at":"2023-07-05T17:15:25.775Z","dependency_job_id":null,"html_url":"https://github.com/tomasmcguinness/dotnet-passbook","commit_stats":{"total_commits":344,"total_committers":27,"mean_commits":12.74074074074074,"dds":0.4331395348837209,"last_synced_commit":"190e938c5232a335122c63e3f6f3868d5e4d702e"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/tomasmcguinness/dotnet-passbook","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomasmcguinness%2Fdotnet-passbook","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomasmcguinness%2Fdotnet-passbook/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomasmcguinness%2Fdotnet-passbook/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomasmcguinness%2Fdotnet-passbook/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tomasmcguinness","download_url":"https://codeload.github.com/tomasmcguinness/dotnet-passbook/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomasmcguinness%2Fdotnet-passbook/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31291782,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T13:12:26.723Z","status":"ssl_error","status_checked_at":"2026-04-01T13:12:25.102Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["apple","c-sharp","dotnet","dotnet-standard","passbook","passkit"],"created_at":"2025-12-17T17:04:05.653Z","updated_at":"2026-04-01T20:44:11.874Z","avatar_url":"https://github.com/tomasmcguinness.png","language":"C#","funding_links":["https://ko-fi.com/tomasmcguinness","https://github.com/sponsors/tomasmcguinness","https://ko-fi.com/G2G11TQK5'"],"categories":[],"sub_categories":[],"readme":"# dotnet-passbook\n\nA .NET Standard Library for generating Passbook packages for iOS Wallet (formerly Passbook)\n\n[![.NET Core](https://github.com/tomasmcguinness/dotnet-passbook/actions/workflows/dotnetcore.yml/badge.svg)](https://github.com/tomasmcguinness/dotnet-passbook/actions/workflows/dotnetcore.yml)\n\n## Supporting the project\n\nIf you use dotnet-passbook, please consider buying me a cup of coffee\n\n\u003ca href='https://ko-fi.com/G2G11TQK5' target='_blank'\u003e\u003cimg height='36' style='border:0px;height:36px;' src='https://cdn.ko-fi.com/cdn/kofi2.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /\u003e\u003c/a\u003e\n\n## Installing\n\ndotnet-passbook is also available to download from NuGet\n\n```ps\nInstall-Package dotnet-passbook\n```\n\n## Why\n\nCreating passes for Apple's Passbook is pretty simple, but requires the use of PKI for signing manifest files, which isn't so simple! During the course of building PassVerse.com (no longer available), I created a .Net library that performs all the steps. I decided to open source this library for other .NET developers.\n\n## Requirements\n\nThe solution requires Visual Studio 2017 or higher. The library is built for .NET Standard 2.0.\n\n## Certificates\n\nBefore you run the PassGenerator, you need to ensure you have all the necessary certificates installed. There are two required.\n\nFirstly, you need to your Passbook certificate, which you get from the Developer Portal. You must have an iOS developer account.\n\nSecondly, you need the Apple WWDR (WorldWide Developer Relations) certificate. You can download that from here [http://www.apple.com/certificateauthority/](http://www.apple.com/certificateauthority/).\n\nDepending on when you generated your Passbook certificate you'll need either the G1 or G4 certificate. If you generated your passbook certificate on or before the January 27, 2022, use G1. Otherwise use G4.\n\nThe other \"Worldwide Developer Relations\" certificates listed here won't work with Apple Wallet. *(\"Sorry, your Pass cannot be installed to Passbook at this time.\")*\n\nThere are [instructions on my blog](http://www.tomasmcguinness.com/2012/06/28/generating-an-apple-ios-certificate-using-windows/) for generating a certificate using IIS if you're using a Windows machine\n\nIf you're on Linux/macOS or would prefer to use OpenSSL on Windows, check out [using-openssl.md](using-openssl.md) for instructions on how to create the necessary certificates using OpenSSL.\n\nWhen moving certificates around, be sure that your Passbook certificate always includes the private key component, else the signing will fail.\n\n## Technical Stuff\n\nTo generate a pass, start by declaring a PassGenerator.\n\n```cs\nPassGenerator generator = new PassGenerator();\n```\n\nNext, create a PassGeneratorRequest. This is a raw request that gives you the full power to add all the fields necessary for the pass you wish to create. Each pass is broken down into several sections. Each section is rendered in a different way, based on the style of the pass you are trying to produce. For more information on this, please consult Apple's Passbook Programming Guide. The example below here will show how to create a very basic boarding pass.\n\nSince each pass has a set of mandatory data, fill that in first.\n\n```cs\nPassGeneratorRequest request = new PassGeneratorRequest();\nrequest.PassTypeIdentifier = \"pass.tomsamcguinness.events\";   \nrequest.TeamIdentifier = \"RW121242\";\nrequest.SerialNumber = \"121212\";\nrequest.Description = \"My first pass\";\nrequest.OrganizationName = \"Tomas McGuinness\";\nrequest.LogoText = \"My Pass\";\n```\n\nColours can be specified in HTML format or in RGB format.\n\n```cs\nrequest.BackgroundColor = \"#FFFFFF\";\nrequest.LabelColor = \"#000000\";\nrequest.ForegroundColor = \"#000000\";\n\nrequest.BackgroundColor = \"rgb(255,255,255)\";\nrequest.LabelColor = \"rgb(0,0,0)\";\nrequest.ForegroundColor = \"rgb(0,0,0)\";\n```\n\nYou must provide both the Apple WWDR and your Passbook certificate as X509Certificate instances. NOTE: This is a change from previous versions.\n\n```cs\nrequest.AppleWWDRCACertificate = new X509Certificate(...);\nrequest.PassbookCertificate = new X509Certificate(...);\n```\n\nWhen you are creating the X509Certificate instances in the cloud, you may experience issues with the signing process. I recommend you use the MachineKeySet and Exportable X509KeyStorageFlags.\n\n```cs\nX509KeyStorageFlags flags = X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable;\nX509Certificate2 certificate = new X509Certificate2(bytes, password, flags);\n```\n\nNext, define the images you with to use. You must always include both standard and retina sized images. Images are supplied as byte[].\n\n```cs\nrequest.Images.Add(PassbookImage.Icon, System.IO.File.ReadAllBytes(Server.MapPath(\"~/Icons/icon.png\")));\nrequest.Images.Add(PassbookImage.Icon2X, System.IO.File.ReadAllBytes(Server.MapPath(\"~/Icons/icon@2x.png\")));\nrequest.Images.Add(PassbookImage.Icon3X, System.IO.File.ReadAllBytes(Server.MapPath(\"~/Icons/icon@3x.png\")));\n```\n\nYou can now provide more pass specific information. The Style must be set and then all information is then added to fields to the required sections. For a boarding pass, the fields are add to three sections;  primary, secondary and auxiliary.\n\n```cs\nrequest.Style = PassStyle.BoardingPass;\n\nrequest.AddPrimaryField(new StandardField(\"origin\", \"San Francisco\", \"SFO\"));\nrequest.AddPrimaryField(new StandardField(\"destination\", \"London\", \"LDN\"));\n\nrequest.AddSecondaryField(new StandardField(\"boarding-gate\", \"Gate\", \"A55\"));\n\nrequest.AddAuxiliaryField(new StandardField(\"seat\", \"Seat\", \"G5\" ));\nrequest.AddAuxiliaryField(new StandardField(\"passenger-name\", \"Passenger\", \"Thomas Anderson\"));\n\nrequest.TransitType = TransitType.PKTransitTypeAir;\n```\n\nYou can add a BarCode.\n\n```cs\nrequest.AddBarcode(BarcodeType.PKBarcodeFormatPDF417, \"01927847623423234234\", \"ISO-8859-1\", \"01927847623423234234\");\n```\n\nStarting with iOS 9, multiple barcodes are now supported. This helper method supports this new feature. If you wanted to support iOS 8 and earlier, you can use the method SetBarcode().\n\nTo link the pass to an existing app, you can add the app's Apple ID to the AssociatedStoreIdentifiers array.\n\n```cs\nrequest.AssociatedStoreIdentifiers.Add(551768478);\n```\n\nFinally, generate the pass by passing the request into the instance of the Generator. This will create the signed manifest and package all the the image files into zip.\n\n```cs\nbyte[] generatedPass = generator.Generate(request);\n```\n\nIf you are using ASP.NET MVC for example, you can return this byte[] as a Passbook package\n\n```cs\nreturn new FileContentResult(generatedPass, \"application/vnd.apple.pkpass\");\n```\n\niOS 15 introduced the ability to bundle and distribute multiple passes using a singular .pkpasses file. You can generate pass bundles as well by passing in a dictionary of requests values and string keys that represent the filename for each individual request.\n\n```cs\nPassGeneratorRequest myFirstRequest = new PassGeneratorRequest();\nPassGeneratorRequest mySecondRequest = new PassGeneratorRequest();\n\n// Build out your requests\n\nList\u003cPassGeneratorRequest\u003e requests = new List\u003cPassGeneratorRequest\u003e;\n\nrequests.Add(myFirstRequest);\nrequest.Add(mySecondRequest);\n\nbyte[] generatedBundle = generator.Generate(requests);\n```\n\nThe resulting byte array is treated almost identically to a singular `.pkpass` file, but with a different extension and MIME type (*pkpasses*)\n\n```cs\nreturn new FileContentResult(generatedBundle, \"application/vnd.apple.pkpasses\")\n{\n    FileDownloadName = \"tickets.pkpasses.zip\"\n};\n```\n\n### Testing pass generator\n\n#### Strategy 1: Mocking and DI\n\n`PassGenerator` class conforms to `IPassGenerator` interface that exposes methods used to generate the passes. If you have some custom logic that builds the pass generation request, you can use easily mock the `IPassGenerator` interface using any mocking library and test whether your logic calls the `Generate` method with correct generator request.\n\nSay you have a service that receives a generator request through DI and generates a pass based on the request.\n\n```cs\nclass PassGeneratorService(IPassGenerator passGenerator)\n{\n    public byte[] GeneratePassWithLogoTextAndBackgroundColor(String logoText, String backgroundColor)\n    {\n        // make a request based on parameters\n        ....\n        passGenerator.Generate(request);\n    }\n}\n```\n\nYou can easily test this service by mocking the `IPassGenerator` interface and verifying that the `Generate` method is called with the correct request. Here's a sample using NSubstitute and xUnit.\n\n```cs\n[Fact]\nvoid ServiceUsesPassedParamsForRequest()\n{\n    // Arrange\n    var passGeneratorMock = Substitute.For\u003cIPassGenerator\u003e();\n    var sut = new PassGeneratorService(passGeneratorMock);\n\n    // Act\n    sut.GeneratePassWithLogoTextAndBackgroundColor(\"Cup'o'Joe\", \"#0CAFE0\");\n\n    // Assert/Verify\n    passGeneratorMock.Received().Generate(Arg.Is\u003cPassGeneratorRequest\u003e(r =\u003e\n    {\n        r.LogoText == \"Cup'o'Joe\" \u0026\u0026 r.BackgroundColor == \"#0CAFE0\"\n    }));\n}\n```\n\n#### Strategy 2: Testing generator request instead\n\nAnother way to test if your logic works well is to test the created `PassGeneratorRequest` object itself. You can create a request object and set the properties based on your logic and then test if the request object is created correctly. Consider this basic request builder:\n\n```cs\nclass PassGeneratorRequestBuilder\n{\n    public PassGeneratorRequest BuildRequestWithLogoTextAndBackgroundColor(String logoText, String backgroundColor)\n    {\n        var request = new PassGeneratorRequest();\n        request.LogoText = logoText;\n        request.BackgroundColor = backgroundColor;\n        return request;\n    }\n}\n```\n\nYou can test this builder by creating a request object and verifying if the properties are set correctly.\n\n```cs\n[Fact]\nvoid RequestBuilderSetsPropertiesCorrectly()\n{\n    // Arrange\n    var sut = new PassGeneratorRequestBuilder();\n\n    // Act\n    var request = sut.BuildRequestWithLogoTextAndBackgroundColor(\"Cup'o'Joe\", \"#0CAFE0\");\n\n    // Assert\n    Assert.Equal(\"Cup'o'Joe\", request.LogoText);\n    Assert.Equal(\"#0CAFE0\", request.BackgroundColor);\n}\n```\n\nNow you only have to make sure that the request is not changed/mutated on its way to the `PassGenerator` class.\n\n### Troubleshooting Passes\n\nIf the passes you create don't seem to open on iOS or in the simulator, the payload is probably invalid. To aid troubleshooting, I've created this simple tool - https://pkpassvalidator.azurewebsites.net - just run your `pkpass` file through this and it might give some idea what's wrong. The tool is new (Jul'18) and doesn't check absolutely everything. I'll try and add more validation to the generator itself.\n\n### IIS Security\n\nIf you're running the signing code within an IIS application, you might run into some issues accessing the private key of your certificates.  To resolve this open MMC =\u003e Add Certificates (Local computer) snap-in =\u003e Certificates (Local Computer) =\u003e Personal =\u003e Certificates =\u003e Right click the certificate of interest =\u003e All tasks =\u003e Manage private key =\u003e Add IIS AppPool\\AppPoolName and grant it Full control. Replace \"AppPoolName\" with the name of the application pool that your app is running under. (sometimes `IIS_IUSRS`)\n\n## Updating passes\n\nTo be able to update your pass, you must provide it with a callback. When generating your request, you must provide it with an AuthenticationToken and a WebServiceUrl. Both of these values are required. The WebServiceUrl must be HTTPS by default, but you can disable this requirement in the iOS developer options on any device you're testing on.\n\nThe authentication token is a string that will be included in the header of all requests made to your API. It's your responsibility to validate this token.\n\n```cs\nrequest.AuthenticationToken = \"\u003ca secret to ensure authorized access\u003e\";\nrequest.WebServiceUrl = \"https://\u003cyour api\u003e\";\n```\n\nThe web service you point to must support Apple's protocol, outlined in the [PassKit Web Service Reference](https://developer.apple.com/library/archive/documentation/PassKit/Reference/PassKit_WebService/WebService.html#//apple_ref/doc/uid/TP40011988)\n\n## NFC Support\n\nAs of version 2.0.1, the NFC keys are now supported. To use them, just set the Nfc property with a new NFC object. Both the message and encoded public key values are mandatory.\n\n```cs\nPassGeneratorRequest request = new PassGeneratorRequest();\nrequest.Nfc = new Nfc(\"THE NFC Message\", \"\u003cencoded private key\u003e\");\n```\n\nUnfortunately, I cannot supply any information as to the values required since it's not available publically. If anyone knows what goes here, I'd be more than happy to add changes to my library to support this key.\n\n## Contribute\n\nAll pull requests are welcomed! If you come across an issue you cannot fix, please raise an issue or drop me an email at tomas@tomasmcguinness.com or follow me on twitter @tomasmcguinness\n\n## License\n\ndotnet-passbook is distributed under the MIT license: [http://tomasmcguinness.mit-license.org/](http://tomasmcguinness.mit-license.org/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftomasmcguinness%2Fdotnet-passbook","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftomasmcguinness%2Fdotnet-passbook","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftomasmcguinness%2Fdotnet-passbook/lists"}