# Apple Wallet Passes in Rails Apps

Keeping our users engaged beyond just opening our web application can significantly boost retention and user satisfaction. 

That's why integrating with Apple Wallet allows us to put our content directly on users' lock screens and in their daily workflows.

In this article, we will integrate **Apple Wallet Passes in Rails** to create signed passes that users can add to their phones with a single tap. 

Let's start by understanding what they are and why they're useful. <a href="#apple-developer-setup" data-behavior="soft-scroll">Skip to the Apple Developer setup</a> if you're already familiar with Apple Wallet Passes.
## What are Apple Wallet Passes?
They are a digital representation of real-world items like tickets, coupons, membership cards, etc.

However, they're not just static images. They can be dynamic and interactive with:

- **Real-time updates:** we can push updates to passes in case of changes like delays, cancellations, or updates.
- **Expiration:** we can add expiration dates to them so the user's wallet only shows non-expired passes.
- **Points and balances:** we can use them to run a loyalty program with store and gift cards.
- **QR Codes:** our passes can include QR codes or barcodes that we can use for quick scanning and validation at our physical locations.
- **Geolocation triggers:** passes can have geolocation data attached to them which allows them to appear on the user's lock screen when they're near a relevant location.
- **Time triggers:** passes can surface at specific dates or times.

These passes are bundled in a special file format `.pkpass` which is essentially a compressed file that contains a set of common files that constitute an actual Apple Pass.

{{info}}
Apple Wallet Passes are a continuation of Passbook digital passes which were introduced in iOS 6 back in 2012. They are essentially the same, but the Passbook app got renamed to Apple Wallet in 2015 for the iOS 9 release. The key difference is that Apple Wallet handles Apple Pay cards.
{{/info}}
## The structure of a Pass
Passes are just a compressed and signed file that contains the following elements:

- A `manifest.json`: it lists all the files in the pass bundle along with their SHA-1 hashes for integrity verification. It ensures none of the files have been tampered with.
- A `pass.json` file: it contains the actual data about the pass including things like the Apple team ID, background and foreground color, and the information that's actually used to build the passes.
- **A set of images:** these are the images and icons we use for our passes. Because passes are meant to be displayed on many devices, we can include many resolutions like the original image and `2x`, `3x` and so on. Furthermore, the image type (icon, strip, thumbnail, etc) must be supported by the type of pass we want to create, and the name has to match the image type: `icon.png`, `strip.png`, etc.
- **Signature:** the cryptographic signature file that authenticates the entire bundle. It contains the digital signature created by Apple's developer certificate and ensures that the pass was created by the authorized developer.

We could build the `.pkpass` field ourselves, but we will be using the [Passkit gem](https://github.com/coorasse/passkit) to save time.

Let's explore the different types of passes we can create:
## Types of passes
We can create 5 different types of Apple Wallet Passes, each with its intended use case and layout rules:
### Boarding Pass
The Boarding Pass layout has support for three images: logo, icon, and footer and it uses the `boardingPass` key.

It's appropriate for transit system passes like train tickets, airline boarding passes, etc. Typically, each pass corresponds to a single trip with specific starting and ending points.

The layout looks like this:

![Apple Wallet Boarding Pass Layout](https://i.postimg.cc/1zrMcZyD/boarding-pass-layout.webp)

Usually the primary fields represent an **origin** and **destination** using unique 3-letter airport codes.

The auxiliary and secondary fields typically add information like flight number, seat, group, scheduled time, etc.
### Coupon
Coupon passes layout is optimized for promotional offers and discounts, and it uses the `coupon` key.

They typically feature a strip image that we can use for branding or to improve the look and feel of the coupon.

![Coupon Pass Layout](https://i.postimg.cc/k4tF4VTM/coupon-layout.webp)

The primary field typically displays the offer title or discount amount. The header fields can show expiration dates, and secondary fields might be used to reinforce the offer's message.
### Event Ticket
This pass type layout is optimized for concerts, sport events, and other entertainment or educational events. It uses the `eventTicket` key.

It supports three types of images: `logo`, `icon`, and a `strip` image that can span the full width of the pass and is located below the primary field.

![Event Ticket Pass Layout](https://i.postimg.cc/L5nTpYLV/event-tickets-layout.webp)

The primary field typically displays the event name while auxiliary fields show important events like the date, time, and the event's venue.

Secondary fields can include things like seat information, section number, entry gates, etc.

Typically, a pass corresponds to a single event, but a single pass could be used for several events or as a seasonal ticket.
### Store Card
These are usually designed for loyalty programs and membership cards. It uses the key `storeCard`.

It supports three types of images: `logo`,`icon` and `strip`.

The primary fields commonly show the member's name or account number, with secondary and auxiliary fields typically showing things like points balance, expiration dates, etc.

![Store Card Pass Layout](https://i.postimg.cc/3xDg9QFd/store-card-layout.webp)
### Generic
Generic passes offer the most flexibility and can be adapted for various purposes including tickets, vouchers, ID cards, membership passes, etc. It uses the `generic` key.

They support all image types: `logo`, `icon`, `strip`, and `thumbnail`.

The field layout is customizable, allowing us to produce diverse layouts depending on the content we add.

This pass type is ideal for use cases where other passes don't fit our needs or if we think that the default layout is what we need to present the necessary data as we like.

![Generic Pass Layout](https://i.postimg.cc/GpwSxQGn/generic-pass-layout.webp)

{{info}}
Even though we discussed the fields that are commonly used for each type of pass, we can add them and arrange them however we like. For example, we can add many fields to the secondary fields while ignoring the primary field. The same applies to images. The only required file is the `icon.png` file, make sure to include it.
{{/info}}
## What we will build
For this tutorial, we will build AvoPass, a simple event application that has a list of events and allows users to buy tickets for them. 

After a user has purchased a given ticket, we can provide them with access to a ticket details screen with an **Add to Apple Wallet** button where the user would be able to download the `pkpass` file and save it to the wallet.

The final result will look something like this:

{{gumlet video_id="689ae338e250086ff5b805c4" width="1860" height="816" /}}
## Apple Developer Setup
Just like we did in our [Apple Sign In with Rails](https://avohq.io/blog/sign-in-with-apple-rails) tutorial, the first thing we need is to configure things on the Apple side of things.

Concretely, we need to generate or obtain the following:

- An Apple Intermediate Certificate
- Apple Team ID.
- A Pass Type ID.
- A Certificate Signing Request associated with the Pass Type ID.
- A `p12` private certificate and the password used to generate it.

Let's see how to get each one of these:

{{warning}}
As it happens for many features in the Apple ecosystem, an Apple Developer account, which currently costs 99$ per year is required to add Apple Wallet Passes to your application.
{{/warning}}
### Apple Intermediate Certificate
The first step is to navigate to [Apple's Certificate Authority Page](https://www.apple.com/certificateauthority/) and search for the **“Worldwide Developer Relations”** certificate with the longest expiration date. They are located in the **Apple Intermediate Certificates** section.

At the time of writing this, the G6 version which expires in March 2036 is the latest option:

![Apple Worldwide Intermediate Certificate](https://i.postimg.cc/13xjtmJH/apple-worldwide-intermediate-certificate.webp)

We download it and keep the `AppleWWDRCAG6.cer` in a safe place as we will use it later.

{{info}}
The Apple Intermediate Certificate, a.k.a. Apple WWDR certificate is a bridge in Apple's trust between a developer certificate, the one we will create in this tutorial, and Apple's root certificate which is trusted by all Apple devices. In the case of Apple Wallet Passes, we use it to sign each pass we generate 
{{/info}}
### Apple Team ID
The next thing we need is our Apple Team ID. We can do that by going to our [Apple Developer Account](https://developer.apple.com/account) and searching for the **Membership Details** section below the **Emails** section.

Copy the Team ID and save it somewhere:

![Team ID in Apple Developer account overview](https://i.postimg.cc/rmFrN4F9/membership-details-apple-developer.png)
### Pass Type ID
The Pass Type ID is a unique identifier we use to tell Apple who is issuing a given pass. This is required for Apple Wallet to accept and trust the passes we generate.

By itself, it's a string that usually uses a reverse domain name like: `pass.com.avopass.ticket`.

To create one, we have to go to the [Pass Type ID section](https://developer.apple.com/account/resources/identifiers/list/passTypeId) and click on the **Register a Pass Type ID** button:

![Register new Pass Type ID in Apple Developer Console](https://i.postimg.cc/7Y7pSstw/register-new-pass-type-id.webp)

In the next screen, the **Pass Type IDs** option should be selected, and we click on the **Continue** button:

![Register New Pass Type ID step 2](https://i.postimg.cc/VvXD0Tfh/register-new-pass-type-id-2.webp)

Then, we add a description and custom identifier.

![Adding description and Identifier to our Pass Type ID](https://i.postimg.cc/155qq23N/register-new-pass-type-id-step-3.webp)

Finally, we register the Pass Type ID:

![Register Pass Type ID final step](https://i.postimg.cc/gJLPQYs6/register-pass-type-id.webp)

And we should see our new Pass Type ID in the list of identifiers:

![Pass Type ID list](https://i.postimg.cc/9FRqbTyW/generated-pass-id-in-identifiers-list.webp)
### Certificate creation
Now that we have our Pass Type ID, we will use it to generate a certificate. But first, we need to create a **Certificate Signing Request,** which is a request to the **Certificate Authority (CA)**, Apple in this case, in our local machine.

To achieve this, we need to open the **Keychain Access** app, hover over the **Certificate Assistant** option on the self-named menu option, and click on the **Request a Certificate from Certificate Authority** option:

![Request a Certificate from a Certificate Authority](https://i.postimg.cc/xCpB0BxV/request-certificate-from-certificate-authority.webp)

Then, we have to add our information for the CSR:

![CSR adding information step](https://i.postimg.cc/QxmYs2H4/csr-certificate-information.webp)

Then, we click the **Continue** button, and a dialog is shown where we select where we want to store our CSR. Pick a location and save the certificate.

Now, back to our Apple Developer account, we should go to the [Pass Type IDs overview](https://developer.apple.com/account/resources/identifiers/list/passTypeId) and click on the ID we created in the previous step:

![Pass Type ID overview Apple Developer Console](https://i.postimg.cc/SN1LgkXh/pass-type-id-overview-apple-developer-console-copy.webp)

Then, we click on the **Create Certificate** button:

![Create Certificate from Pass Type ID](https://i.postimg.cc/CMtRR1X9/create-certificate-from-pass-type-id.webp)

We have to assign a name for our Pass certificate, so we will keep the same name we used for the CSR: **AvoPass Wallet Pass Generator**.

Then, we have to upload our the CSR before clicking on the **Continue** button:

![Creating a new Certificate name and CSR step](https://i.postimg.cc/0Nz97GTv/creating-new-certificate-with-csr-step.webp)

After we click **Continue**, a new Pass Type Certificate is created. Make sure to download it to your local machine. It should produce a `pass.cer` file.

![Certificate Download Step](https://i.postimg.cc/tR3pd4Ms/certificate-download-step.webp)

With this file, we double-click to open it in the **Keychain Access,** where it should be located under the **My Certificates** section.

In there, we right-click on the certificate and export it, making sure the `p12` format is selected:

![Exporting certificate to p12 format in Keychain Access](https://i.postimg.cc/jq6Ynq8Z/exporting-certificate-to-p12-from-keychain-access.webp)

Before we can finally export the certificate, we will be prompted with a form that makes us input a password and password confirmation. Make sure to remember this password or store it somewhere safe.

After this, we should have a `Certificates.p12` file that we will use later.
### Potential PKCS12 Parse error
At the time of writing this, the Keychain Access app uses the **RC2-40-CBC** cipher to export the `p12` certificate.

If we use the exported certificate directly with Passkit, we will encounter a `PKCS12_parse: unsupported` error when trying to preview a pass. 

This happens because current versions of OpenSSL 3.0 and up don't support the cipher, and we have to either add support to it via configuration, which is not ideal because RC2 is considered insecure and obsolete, or repackage the certificate manually.

The solution is to re-encrypt it using `openssl`. Assuming a `Certificates.p12` file name, we should run the following:

```shell
openssl pkcs12 -legacy -in Certificates.p12 -nodes -out cert-decrypted.tmp # Temporarily decrypt it
```

Then, we use the temporary decrypted cert and export our new certificate:

```shell
openssl pkcs12 -in cert-decrypted.tmp -export -out cert.p12
```

For each one of these steps we need to input the certificate's password.

After running this, we can use the `cert.p12` file normally without any further issues. Don't forget to delete de temporary certificate:

```shell
rm cert-decrypted.tmp
```

Finally, we can test the `cert.p12` using the following command:

```shell
openssl pkcs12 -in cert.p12 -info -nodes
```

Which should produce our Apple identity, certificate data, and private key data.
## Application Setup
Let's start by creating a new Rails application:

```ruby
rails new avopass --css=tailwind --javascript=esbuild
```

Next, let's install the Passkit gem:

```shell
bundle add passkit && bundle install
```

Then, we run the gem's installation command:

```shell
bin/rails g passkit:install
```

This will add migrations and a config `passkit.rb` initializer file we will explain later.

Then, we add the `money_rails` gem to display monetary values:

```shell
bundle add money-rails && bundle install
```

Let's add the environment variables that the gem requires:
### Setting Passkit ENV variables
The gem requires us to set some important values in environment variables. To achieve this, let's use the [Figaro](https://github.com/laserlemon/figaro) gem so we can keep the values in a centralized YAML file.

```shell
bundle add figaro && bundle install
```

Let's run the installation command:

```shell
bundle exec figaro install
```

This creates a commented `config/application.yml` file where we can store our environment variable values.

{{alert}}
The command also adds the file to `.gitignore` but double-check to make sure this file is never committed to our repository.
{{/alert}}

Now, let's add the needed values to our environment variables:

```yaml
# config/application.yml
PASSKIT_WEB_SERVICE_HOST: https://avopass.ngrok.io # The name of our live service
PASSKIT_APPLE_INTERMEDIATE_CERTIFICATE: <%= Rails.root.join("certs/AppleWWDRCAG6.cer") %>
PASSKIT_APPLE_TEAM_IDENTIFIER: 8K7HX2M9PQ # Our Apple Team ID
PASSKIT_PASS_TYPE_IDENTIFIER: pass.com.avopass.ticket # Pass Identifier
PASSKIT_PRIVATE_P12_CERTIFICATE: <%= Rails.root.join("certs/Certificates.p12") %>
PASSKIT_CERTIFICATE_KEY: password
PASSKIT_DASHBOARD_USERNAME: erozas
PASSKIT_DASHBOARD_PASSWORD: password
```

With this environment variables in place, let's create a `certs` directory to store the certificates and move the certificates into it:

```shell
mkdir certs
```

Paste the Apple intermediate certificate `AppleWWDRCAG6.cer` and the p12 certificate `Certificates.p12` into this folder.

Then, unless you are certain that you will deploy this to a private repository, let's add the directory to `.gitignore`:

```
/config/application.yml # Ignore Figaro config file with ENV variables
/certs/*  # Ignore certificates in /certs directory
```

Now that we're done with the setup let's see how we can integrate passes into our application.

{{info}}
You can follow up with every feature for this application in the [Wallet Pass Repository](https://github.com/erozas/wallet_pass) so you can see how we added the feature to a real application.
{{/info}}
## The Passkit gem
The fundamental feature of the Passkit gem is that it lets us create custom passes using Ruby.

Like we explained above, a pass is basically a compressed and signed folder that contains a manifest, a `pass.json` file with all the fields for each type of pass, the images used in the pass, and a signature that proves that the pass is from us (meaning it was signed using the certificate associated with our Apple Team ID and Pass Type ID).

With Passkit, we can build as many passes as we like by creating classes that inherit from `Passkit::BasePass` in the `app/lib/passkit` folder. 

Then, in the class, we have to override the `pass_type` method with the type of pass we want to create, adding the fields we need as instance methods, and also adding the required files to the `private/passkit/#{pass_name}` folder.

However, the gem doesn't just allow us to create passes, it also provides the following features:

- **Serving passes:** using the `Passkit::UrlGenerator` class we can generate URLs to serve our passes by passing it our custom pass and the instance of the `@generator` which is the object in our application we need to extract information from. For example, a `Ticket` instance. Then, we call the `.ios` or `.android` method on the generator instance, and we get a URL from where we can download the `.pkpass` file.
- **Pass Previews:** we can generate pass previews while we develop them by adding our custom pass to the `available_passes` configuration variable in the `passkit.rb` initializer.
- **Dashboard:** we can access an authenticated dashboard at `passkit/dashboard` where we can see the latest created passes, the associated custom pass class, and the devices that accessed them.

![Dashboard Pass Previews with Passkit](https://i.postimg.cc/nLhgHJkG/passkit-dashboard-previews.webp)
## Creating an Apple Wallet Pass
In this section, we will create a ticket pass by defining a custom `Passkit::EventTicket` class.

Ideally, we should start with a design that shows us what our pass will look like. For this application, I downloaded an [Apple Wallet Pass UI Kit](https://www.figma.com/community/file/1097830456312669227) from the Figma store and edited the ticket template with what I thought would look nice, and I came up with the following:

![Apple Pass Ticket design in Figma](https://i.postimg.cc/hjVcWnMP/ticket-pass-design.webp)

You might notice that we're not including anything for the primary field, and we're just adding secondary and auxiliary field values.

The same with the logo text: we're just displaying the logo without any text, and we're using the header fields to show the date and the time for the event.

Furthermore, this ticket template has a barcode, but we will use the QR Code. However, the idea remains the same: as long as we respect the layout, we can obtain a nice-looking pass that's easy to read and adds relevant information.

With this in mind, let's create our custom pass:
### Ticket pass
Before anything, we have to add an `icon.png` file, which is required for every type of pass and is used when the pass is shown on the lock screen or by apps like Mail. This icon should be **29x29 points**.

Then, for this first pass, we will add a `logo.png` file and a `strip.png` file that we exported from Figma to the `private/passkit/event_ticket` folder.

Now, let's create an `EventTicket` class that inherits from `Passkit::BaseClass` and assigns an `@event` instance variable:

```ruby
# app/lib/passkit/event_ticket.rb
module Passkit
  class EventTicket < BasePass
    def initialize(generator)
      super(generator)
      @event = generator.event
    end

    def pass_type
      :eventTicket
    end
  end
end
```

Here, the `generator` parameter is what gets passed to the class initialize method. In our case, that's a `Ticket` instance of our event app:

```ruby
ticket_pass = Passkit::EventTicket.new(Ticket.last)
```

With that in place, we declare an `@event` variable, which is assigned to the event that's associated with the ticket that we pass to the custom pass.

Let's then add colors to our custom ticket class with a custom `hex_to_rgb` helper because the colors have to be defined in RGB and a `folder_name` method that returns the name of the folder where we put the assets in the `private/passkit` directory:

```ruby
module Passkit
  class EventTicket
    # Rest of the code
    
    private

    def folder_name
      "event_ticket"
    end

    def hex_to_rgb(hex)
      return "rgb(0, 0, 0)" if hex.nil? || hex.empty?
      
      hex = hex.gsub('#', '').upcase
      return "rgb(0, 0, 0)" unless hex.match?(/\A[0-9A-F]{6}\z/)
      
      r = hex[0..1].to_i(16)
      g = hex[2..3].to_i(16)
      b = hex[4..5].to_i(16)
      
      "rgb(#{r}, #{g}, #{b})"
    end
  end
end
```

Next, let's add the rest of the methods to fill the information we need for this pass. It should look like this:

```ruby
module Passkit
  class EventTicket < BasePass
    include Rails.application.routes.url_helpers

    def initialize(generator)
      super(generator)
      @event = generator.event
    end

    def pass_type
      :eventTicket
    end

    def description
      @event.description || "Event Ticket"
    end

    def background_color
      hex_to_rgb("43A4F9")
    end

    def foreground_color
      hex_to_rgb("FFFFFF")
    end

    def locations
      [
        {"latitude" => @generator.event_venue_lat.to_f, "longitude" => @generator.event_venue_lng.to_f}
      ]
    end

    def header_fields
      [
        {
          key: "date",
          label: "Date",
          value: @event.event_date.strftime("%b %d")
        },
        {
          key: "time",
          label: "Time",
          value: @event.event_date.strftime("%I:%M")
        }
      ]
    end

    def logo_text
      ""
    end

    def primary_fields
      []
    end

    def secondary_fields
      [
        {
          key: "event",
          label: "Event",
          value: @event.title
        }
      ]
    end

    def auxiliary_fields
      [
        {
          key: "venue",
          label: "Venue",
          value: @generator.truncated_venue_name
        },
        {
          key: "price",
          label: "Price",
          value: ActionController::Base.helpers.humanized_money_with_symbol(Money.new(@event.ticket_price_cents || 0, 'USD'))
        },
        {
          key: "section",
          label: "Section",
          value: @generator.section  
        },
        {
          key: "seat",
          label: "Seat",
          value: @generator.seat
        }
      ]
    end

    def barcodes
      [
        { messageEncoding: "iso-8859-1",
          format: "PKBarcodeFormatQR",
          message: @generator.barcode_message.to_json,
        }
      ]
    end

    def file_name
      @file_name ||= SecureRandom.uuid
    end

    private
    def folder_name
      "event_ticket"
    end

    def hex_to_rgb(hex)
      return "rgb(0, 0, 0)" if hex.nil? || hex.empty?
      
      hex = hex.gsub('#', '').upcase
      return "rgb(0, 0, 0)" unless hex.match?(/\A[0-9A-F]{6}\z/)
      
      r = hex[0..1].to_i(16)
      g = hex[2..3].to_i(16)
      b = hex[4..5].to_i(16)
      
      "rgb(#{r}, #{g}, #{b})"
    end
  end
end

```

As you can see, the fields match the name for the layout examples we saw above, and we need to pass an array of hashes with the following format: `{key: "name", label: "Name", value: "Ruby on Rails Workshop" }`.

We can leave fields empty by passing an empty array or not declaring the method at all as they're already declared in the `Passkit::BasePass` class.

The `locations` field can accept up to 10 relevant locations, something that's important for stores or memberships but not so much for events, so we're only adding the venue's location.

Now, for the barcode/QR code, we have to pick a `messageEncoding` standard, a `format` and then pass the information we want the code to contain as a string.

The `barcode_message` that's declared in the `Ticket` class looks like this:

```ruby
def barcode_message
  {
    ticket_number: ticket_number,
    event_title: event.title,
    purchaser_id: user.id,
  }
end
```

In this case, we could use that information to scan the device at the event's entrance and perform validations to make sure the ticket is valid and not yet used.

With this in place, we can generate the URL for the pass:

```ruby
url = Passkit::UrlGenerator.new(Passkit::EventTicket, Ticket.last).ios
```

Then, we can access this URL, and the server should respond with a `.pkpass` file. To test this file, let's open an iOS simulator:

```shell
xcrun simctl boot "iPhone 15 Pro"
open -a Simulator
```

Replace the **“iPhone 15 Pro”** simulator with whatever you have installed on your computer: you can find which simulator devices you have by running `xcrun simctl list devices`.

Now, with the simulator running, we drag our `.pkpass` file to it, and we should see the pass:

![Exported PKPass in iOS device](/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6NDYyLCJwdXIiOiJibG9iX2lkIn19--31d1edb6c989b15e00157a55a15341755761ebdd/exported-pkpass-in-ios-device.webp)

Now, let's learn how to make the images in our passes dynamic so we can attach the actual cover for the event:
### Dynamic images
Because the passes are just a compressed and signed file where the images have to follow the expected naming, `strip.png` for example, to use images that are [uploaded to the cloud with Active Storage](https://avohq.io/blog/cloudflare-r2-active-storage) like a `cover` field for the `Event` we need to use the `add_other_files` hook to our pass model:

```ruby
module Passkit
  class EventTicket < BasePass
    ## Rest of the code

    def add_other_files(temporary_path)
      if @event.cover.attached?
        @event.cover.blob.open do |tempfile|
          FileUtils.cp(tempfile.path, temporary_path.join("strip.png"))
        end
      end
    end
  end
end
```

Here, we're fetching the event's cover and copying the file into the temporary directory that Passkit creates before packing the `.pkpass` file.

Of course, we could do the same for the `logo.png`, `thumbnail.png` or any other file the Apple Pass uses for each type of pass.

Now, if we purchase another ticket in our application, we'll see that it will include the event's cover as the `strip` image:

![Custom dynamic images with Apple Wallet Passes](/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6NDYxLCJwdXIiOiJibG9iX2lkIn19--57676609ed7c2f09a4612d4cf434f5735861a97e/exported-wallet-pass-with-custom-image.webp)

## Summary
In this tutorial, we explored how to integrate Apple Wallet Passes into Rails applications using the Passkit gem. 

We covered the complete process from understanding what Apple Wallet Passes are and their different types to setting up the necessary Apple Developer credentials and implementing them in a real application.

We learned that Apple Wallet Passes are dynamic digital representations of real-world items that can be very useful because of their unique features.

The five pass types (boarding Pass, coupon, event ticket, store card, and generic) each serve different use cases with their layout rules and field arrangements.

The Apple Developer setup required obtaining an Apple Intermediate Certificate, Team ID, Pass Type ID, and generating a properly formatted p12 certificate. We also addressed the common PKCS12 parsing error by re-encrypting certificates with OpenSSL.

To demonstrate pass creation, we built an `EventTicket` pass that handles custom colors, fields, barcodes, and even dynamic images from Active Storage uploads.

On top of pass creation, Passkit also provides features like URL generation, pass previews, and a dashboard to monitor created passes.

With this knowledge, you can create any type of Apple Wallet Passes for your applications and even allow users to create their own custom passes and integrate them into their applications.

I hope you enjoyed the article and that it can help you build your projects. 

Have a good one and happy coding!

