WalletPasses (wallet_passes v0.8.0)

Copy Markdown View Source

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

This module provides convenience functions that orchestrate both platforms. For platform-specific control, use WalletPasses.Apple.Builder, WalletPasses.Google.Api, etc. directly.

Pass Lifecycle

Passes have a lifecycle status: :active (default), :voided, :expired, :completed. Use void_pass/1, expire_pass/1, complete_pass/1, and reactivate_pass/1 to transition. Each function:

  • Updates the status column on whichever Apple/Google rows exist for the serial (transactional — the two rows can't diverge from a DB error).
  • Patches the Google object's state field via the Wallet API (ACTIVE/INACTIVE/EXPIRED/COMPLETED).
  • Triggers Apple device push so devices fetch the updated pass content.

Returns {:ok, %{status: status, apple: :ok | :not_found, google: :ok | :not_found | {:error, _}}} even when remote calls fail — the DB write is the source of truth, and remote failures are surfaced for caller-driven retry rather than silently rolled back. Returns {:error, :not_found} only when neither an Apple nor a Google row exists.

reactivate_pass/1 lets a non-active pass return to :active. The library does not enforce transition validity; consumers wanting a state machine should wrap these calls in their own policy layer.

For Apple devices to render lifecycle states differently, the PassDataProvider.build_pass_data/1 implementation should inspect WalletPasses.Schema.get_pass_status/1 (or call WalletPasses.PassDataProvider.apply_status_decoration/2) before returning the pass.

The :expired status overlaps with Apple's native expirationDate field in the .pkpass. Use expire_pass/1 for issuer-driven expiry events (refunded ticket, revoked subscription); use expirationDate for date-based expiry that the OS handles automatically.

Localization

Pass content can be translated for both platforms via a single :translations map option:

translations = %{
  "fr" => %{"Gate" => "Porte", "Section" => "Section"},
  "es" => %{"Gate" => "Puerta"}
}

WalletPasses.build_apple_pass(pass_data, visual, translations: translations)
WalletPasses.google_save_url(pass_data, visual, translations: translations)

Apple receives .lproj/pass.strings entries inside the .pkpass ZIP; Google receives LocalizedString translatedValues on the object and class JSON. The same map drives both. See the Localization Guide for the full reference.

Summary

Functions

Generates an Apple .pkpass binary for a pass.

Marks a pass as completed (used/redeemed) on all platforms. See void_pass/1.

Marks a pass as expired on all platforms. See void_pass/1 for semantics.

Generates a "Save to Google Wallet" URL for a pass.

Sends Apple push notifications for a pass to trigger refresh on devices.

Reactivates a non-active pass. See void_pass/1 for semantics.

Updates a Google Wallet pass object on Google's servers.

Voids a pass. Updates the DB status for whichever Apple/Google rows exist for the serial, patches Google's object state to INACTIVE, and pushes Apple devices so they refetch the pass.

Returns wallet presence for a pass across both platforms.

Types

lifecycle_result()

@type lifecycle_result() :: %{
  status: :active | :voided | :expired | :completed,
  apple: :ok | :not_found,
  google: :ok | :not_found | {:error, term()}
}

Functions

build_apple_pass(pass_data, visual, opts \\ [])

Generates an Apple .pkpass binary for a pass.

Creates or retrieves the Apple pass record, then builds the .pkpass bundle.

Options

  • :translations — Passed through to Apple.Builder.build_pkpass/4. See the Localization guide.
  • :localized_images — Passed through to Apple.Builder.build_pkpass/4.

complete_pass(serial_number)

@spec complete_pass(String.t()) :: {:ok, lifecycle_result()} | {:error, :not_found}

Marks a pass as completed (used/redeemed) on all platforms. See void_pass/1.

expire_pass(serial_number)

@spec expire_pass(String.t()) :: {:ok, lifecycle_result()} | {:error, :not_found}

Marks a pass as expired on all platforms. See void_pass/1 for semantics.

google_save_url(pass_data, visual, opts \\ [])

Generates a "Save to Google Wallet" URL for a pass.

Creates or retrieves the Google pass record, creates/updates the object on Google's servers, and returns the save URL.

Options

  • :class_id — Class ID suffix to use for the object (defaults to the pass type's standard suffix).
  • :class_config — Map of class configuration. When provided, ensures the class exists on Google's servers (idempotent — only created once per VM lifetime per class). The map's :id key defaults to the :class_id opt or to the pass type's standard suffix.
  • :origins — Passed through to Google.SaveUrl.
  • :translations — Passed through to Google.Api.build_pass_object/3 for object-level localization. To localize class-level fields, include :translations inside the :class_config map. See the Localization guide.

notify_apple_devices(serial_number)

Sends Apple push notifications for a pass to trigger refresh on devices.

reactivate_pass(serial_number)

@spec reactivate_pass(String.t()) :: {:ok, lifecycle_result()} | {:error, :not_found}

Reactivates a non-active pass. See void_pass/1 for semantics.

update_google_pass(pass_data, visual, opts \\ [])

Updates a Google Wallet pass object on Google's servers.

Options

  • :class_id — Pass-through to Google.Api.update_object.
  • :class_config — Map of class configuration. When provided, ensures the class exists on Google's servers (idempotent).
  • :translations — Passed through to Google.Api.build_pass_object/3. See the Localization guide.

void_pass(serial_number)

@spec void_pass(String.t()) :: {:ok, lifecycle_result()} | {:error, :not_found}

Voids a pass. Updates the DB status for whichever Apple/Google rows exist for the serial, patches Google's object state to INACTIVE, and pushes Apple devices so they refetch the pass.

Returns {:ok, lifecycle_result()} even when remote calls fail — the DB write is the source of truth and is never rolled back. Per-platform outcomes are reported in the result map. Returns {:error, :not_found} only when neither an Apple nor a Google row exists for the serial.

Idempotent.

wallet_presence(serial_number)

@spec wallet_presence(String.t()) :: %{apple: boolean(), google: boolean() | nil}

Returns wallet presence for a pass across both platforms.

  • :appletrue when the pass has at least one device registration. Note: this is a "reachable for push" signal, not a "user has the pass" signal — see WalletPasses.EventHandler.on_pass_removed/3 for the caveats around iOS push-token rotation and app uninstall.

  • :googletrue when the latest recorded callback for the pass is save, false if del, and nil if no callback has been received (either the pass was never saved, or the consumer doesn't have :google_callback_url configured).