WalletPasses

Apple Wallet and Google Wallet pass generation, management, and remote updates for Elixir.

Features

Installation

Add wallet_passes to your list of dependencies in mix.exs:

def deps do
  [
    {:wallet_passes, "~> 0.1.0"},
  ]
end

Generate and run the database migrations:

$ mix wallet_passes.gen.migration
$ mix ecto.migrate

Configuration

# config/config.exs
config :wallet_passes,
  repo: MyApp.Repo,
  pass_data_provider: MyApp.WalletPassProvider,
  apple_pass_type_id: "pass.com.example.mypass",
  apple_web_service_url: "https://yourdomain.com/passes/apple"

# config/runtime.exs
config :wallet_passes,
  apple_team_id: System.get_env("APPLE_TEAM_ID"),
  apple_pass_type_cert: System.get_env("APPLE_PASS_TYPE_CERT"),
  apple_pass_type_key: System.get_env("APPLE_PASS_TYPE_KEY"),
  apple_wwdr_cert: System.get_env("APPLE_WWDR_CERT"),
  google_issuer_id: System.get_env("GOOGLE_WALLET_ISSUER_ID"),
  google_service_account_json: System.get_env("GOOGLE_WALLET_SERVICE_ACCOUNT_JSON")

Certificate/key values accept file paths, PEM strings, or base64-encoded values.

Quick Start

1. Implement the PassDataProvider

The library needs to look up pass data autonomously (e.g., when Apple requests an updated pass). Implement the behaviour:

defmodule MyApp.WalletPassProvider do
  @behaviour WalletPasses.PassDataProvider

  @impl true
  def build_pass_data(serial_number) do
    case MyApp.find_by_serial(serial_number) do
      nil -> {:error, :not_found}
      record ->
        {:ok, %{
          pass_data: %WalletPasses.PassData{
            serial_number: serial_number,
            event_name: record.event_name,
            holder_name: record.holder_name,
            primary_fields: [{"name", "Name", record.holder_name}],
            # ... more fields
          },
          apple: %WalletPasses.Apple.Visual{
            background_color: "#1A1A1A",
            foreground_color: "#FFFFFF",
            label_color: "#D4A843",
            icon_path: "/path/to/icon.png",
          },
          google: %WalletPasses.Google.Visual{
            background_color: "#1A1A1A",
            logo_uri: "https://example.com/logo.png",
          },
        }}
    end
  end
end

2. Generate passes

# Build an Apple .pkpass
{:ok, pkpass_binary} = WalletPasses.build_apple_pass(pass_data, apple_visual)

# Get a Google Wallet save URL
{:ok, url} = WalletPasses.google_save_url(pass_data, google_visual)

3. Mount the Apple callback router

Apple devices call back to your server to register for updates and fetch the latest pass. Mount the router in your Phoenix app:

# router.ex
forward "/passes/apple", WalletPasses.Apple.Router

4. Send push updates

WalletPasses.notify_apple_devices("SERIAL-NUMBER")

Theme Helper

Use the Theme struct to share colors across platforms:

theme = %WalletPasses.Theme{
  background_color: "#1A1A1A",
  foreground_color: "#FFFFFF",
  label_color: "#D4A843",
  logo_text: "My Event",
}

apple_visual = theme
  |> WalletPasses.Theme.to_apple_visual()
  |> struct!(icon_path: "/path/to/icon.png", strip_image_path: "/path/to/strip.png")

google_visual = theme
  |> WalletPasses.Theme.to_google_visual()
  |> struct!(logo_uri: "https://example.com/logo.png", hero_image_uri: "https://example.com/hero.png")

Optional Add-ons

Preview Components (Phoenix LiveView)

Add {:phoenix_live_view, "~> 1.0"} to your deps, then:

import WalletPasses.Preview.Components

<.apple_pass_preview pass_json={@apple_json} qr_svg={@qr_svg} />
<.google_pass_preview pass_object={@google_obj} qr_svg={@qr_svg} />

Background Sync (Oban)

Add {:oban, "~> 2.18"} to your deps, then:

# Sync specific passes
WalletPasses.Sync.sync(["SERIAL-1", "SERIAL-2"])

# Sync all passes in the database
WalletPasses.Sync.sync_all()

Why not passbook?

The passbook (ex_passbook) package is the only other Elixir library for .pkpass generation. Both it and this library shell out to openssl smime for PKCS#7 signing -- the signing approach is equivalent. However:

Pure-Erlang signing (eliminating the OpenSSL dependency) is a future goal. Contributing fixes upstream to passbook is also under consideration.

System Requirements

License

MIT -- see LICENSE for details.