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
statuscolumn 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
statefield 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
@type lifecycle_result() :: %{ status: :active | :voided | :expired | :completed, apple: :ok | :not_found, google: :ok | :not_found | {:error, term()} }
Functions
Generates an Apple .pkpass binary for a pass.
Creates or retrieves the Apple pass record, then builds the .pkpass bundle.
Options
:translations— Passed through toApple.Builder.build_pkpass/4. See the Localization guide.:localized_images— Passed through toApple.Builder.build_pkpass/4.
@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.
@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.
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:idkey defaults to the:class_idopt or to the pass type's standard suffix.:origins— Passed through toGoogle.SaveUrl.:translations— Passed through toGoogle.Api.build_pass_object/3for object-level localization. To localize class-level fields, include:translationsinside the:class_configmap. See the Localization guide.
Sends Apple push notifications for a pass to trigger refresh on devices.
@spec reactivate_pass(String.t()) :: {:ok, lifecycle_result()} | {:error, :not_found}
Reactivates a non-active pass. See void_pass/1 for semantics.
Updates a Google Wallet pass object on Google's servers.
Options
:class_id— Pass-through toGoogle.Api.update_object.:class_config— Map of class configuration. When provided, ensures the class exists on Google's servers (idempotent).:translations— Passed through toGoogle.Api.build_pass_object/3. See the Localization guide.
@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.
Returns wallet presence for a pass across both platforms.
:apple—truewhen the pass has at least one device registration. Note: this is a "reachable for push" signal, not a "user has the pass" signal — seeWalletPasses.EventHandler.on_pass_removed/3for the caveats around iOS push-token rotation and app uninstall.:google—truewhen the latest recorded callback for the pass issave,falseifdel, andnilif no callback has been received (either the pass was never saved, or the consumer doesn't have:google_callback_urlconfigured).