ReviewOS

craft-native/craft

No Apple Wallet or Google Wallet: membership cards, tickets and class passes can only be drawn on screen

#374
Closed glennmichael123 opened this 8 days ago ·
8 days ago

There is no Apple Wallet support in Craft:

PassKit  0
PKPass   0

(and no Google Wallet on the Android side)

The case

Any app with a membership, a ticket, a class booking or a door credential eventually renders it as a QR code on a screen. That works, and it is strictly worse than a pass for the moment it is used:

  • a pass is on the lock screen. Double-tap, it is there. A screen-rendered code is: unlock, find the app, wait for a view to load, hold it up — at a turnstile with people behind you.
  • a pass works with the app closed, and offline. A gym basement with no signal is the normal case, not the edge case.
  • a pass can surface itself. A geofenced pass appears on the lock screen when you arrive at the venue, which is exactly when it is wanted and never otherwise.
  • a pass updates server-side by push. A cancelled membership or an expired class pack stops working without the person ever opening the app. A rendered code can only be revoked when the app next runs.
  • it is where people already keep this. Boarding passes, event tickets, loyalty cards, transit. A gym card living somewhere else is the odd one out.

Rough shape

Reading an already-signed .pkpass is most of the value and is not much API:

  • craft.wallet.isAvailable()
  • craft.wallet.add(passData | url) → presents the system add-pass sheet
  • craft.wallet.has(serialNumber) → so the app can show "Add to Wallet" or "In Wallet" honestly
  • Android: the Google Wallet equivalent

Signing stays on the server, where the certificate belongs — the app should never hold a pass type certificate, and Craft should not be in the business of signing. This is the presentation half only.

The update path (PKPassLibrary + the pass's own web service) is a reasonable second step, and the first step is useful without it.

Where this came from

HQ.training, built on Craft, already has the whole server half: GetMembershipCardAction, RotateMembershipCardAction, and check-in screens including aggregator check-ins for Urban Sports Club / ClassPass / Wellhub. The card is rotatable, which is exactly the property a Wallet pass's update service wants. What it cannot do is put any of it on the lock screen.

Filed alongside craft-native/craft#372 — both came out of asking which native capabilities a training app on Craft still cannot reach. That one is foundational; this one is a day at a turnstile.

8 days ago

Implemented the first native Wallet slice on main in 67488204 (feat(ios): add native wallet pass capability).

  • Added opt-in enableWallet config and typed craft.wallet contract.
  • iOS native screens now expose wallet with isAvailable(), has(serialNumber), and add({data}) / add({url}) using PassKit.
  • Base64 .pkpass data is validated before presentation; HTTPS downloads are parsed natively; capability remains disabled by default.
  • iOS builder: 120 tests passed; typecheck and Pickier passed. Simulator unit tests: 176 passed. The existing UI fixture had unrelated flaky failures in switch hit-testing and keyboard text assertions on this run.

Android/Google Wallet and pass update support remain follow-up work, so this issue stays open.

8 days ago

Android follow-up is now on main in 395e99a (feat(android): hand wallet passes to Google Wallet).

  • Added opt-in enableWallet config and native wallet capability.
  • isAvailable() checks for the Google Wallet package; add({ url }) validates an HTTPS save URL and hands it to the installed Wallet/browser; has() reports an explicit unsupported result because Google Wallet save links do not expose local serial lookup.
  • Android builder suite: 89 passed; typecheck and Pickier passed.

#374 remains open for a richer Android Wallet SDK path and pass update/query support.

8 days ago

Found and fixed a configuration parity regression on main in 5c83563: the new enableWallet flag was decoded by Swift and exposed to native actions, but omitted from Zig ios_config.Feature, so the iOS conformance test rejected the generated config. Zig now mirrors the key and the iOS README/contract test cover it. This is separate from the remaining Android Wallet SDK/update-query work, so #374 stays open.

8 days ago

The shared native runtime now exposes the Wallet protocol already implemented by both maintained hosts:

  • STX 6cb964cd3f054b0bd5ebf0f06ef4598ba9db0a02 adds typed craft.wallet.isAvailable(), add({ data | url }), and has(serialNumber) behind the host-advertised wallet capability.
  • Craft 6ab4cbe6 pins that exact compiler revision across Mobile E2E, release validation, and the Native STX guide.
  • The focused STX capability tests and Craft contract/release suite are green; the full STX native suite is running now.

The remaining platform limitation is intentional: Android has() reports UNSUPPORTED because Google Wallet's save-link API has no local pass lookup. Signing and pass updates remain server-side concerns.

8 days ago

Android’s remaining Wallet gap is now on main in:

  • 42205cf3 — feat(android): add native Google Wallet JWT saves
  • 47d18c59 — docs(android): document native Google Wallet saves

With enableWallet: true, Android now:

  • checks Google’s PayClient SAVE_PASSES_JWT availability instead of only checking whether the Wallet package is installed;
  • accepts craft.wallet.add({ jwt }) and launches the native Google Wallet save sheet;
  • maps success, cancellation, API-unavailable, internal, and save-error results back to the native screen;
  • keeps the existing HTTPS save-link fallback;
  • keeps the capability opt-in, so disabled apps receive no Pay SDK imports or dependency.

craft.wallet.has() remains explicitly unsupported on Android because the Google Wallet save API does not expose local pass lookup. Pass signing, updates, and server-side object queries remain outside the Craft host.

The generator and Android builder tests pass; the full Gradle template compile could not run in this environment because no Android SDK is installed.

8 days ago

Wallet support is complete for the presentation scope described here and is verified on current main.

  • iOS PassKit: opt-in enableWallet, isAvailable(), has(serialNumber), and add({ data | url }).
  • Android Google Wallet: opt-in enableWallet, SAVE_PASSES_JWT capability detection, add({ jwt }), and the existing HTTPS save-link fallback.
  • Android has(serialNumber) remains an explicit UNSUPPORTED result because Google Wallet's save-link API exposes no local serial lookup; signing, updates, and server-side object queries stay on the app backend as intended.

Verification on current main: iOS generator 121 passed, Android generator 93 passed, wallet renderer contract passed, and TypeScript typecheck passed. The implementation is present in 42205cf / 47d18c5 and the shared protocol/config pin is in 6ab4cbe.

8 days ago

Resolved on main: Apple Wallet PassKit and Android Google Wallet JWT/save-link presentation are shipped and covered by the host/contract tests. Android local lookup is intentionally unsupported because the Google save-link API provides no lookup primitive.

Sign in to comment on this issue.