npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@bhut-it/expo-apple-wallet

v1.0.4

Published

A module to call apple wallet using Passkit API

Readme

expo-apple-wallet (ideal para projetos Expo gerenciados)

npm version npm downloads

História

Criei essa biblioteca para simplificar a integração do encarteiramento da Apple Wallet em projetos que utilizam o Expo Managed Flow, onde não há possibilidade de mexer em código nativo. Usando a Expo Modules API, deu pra embutir a integração com o PassKit como um módulo Swift comum, que funciona com prebuild/EAS Build sem precisar de eject nem manter uma pasta ios/ própria no projeto consumidor.

  • Somente iOS. O módulo Android é um stub sem nenhuma função (existe só pra não quebrar o autolinking). Sempre proteja as chamadas com Platform.OS === "ios".

Compatibilidade

| Versão da lib | Testada com Expo SDK | Observações | |---|---|---| | 3.3.2+ | 57+ | Exige o fix de MainActor (ver Troubleshooting) — o expo-modules-core 57 mudou como o currentViewController() precisa ser chamado. | | 3.3.1 e anteriores | até 56 | Funciona sem o MainActor. |

Se você atualizar o SDK do Expo e o botão de "adicionar à wallet" parar de funcionar silenciosamente (ou o app fechar sem erro no JS), olha a seção Troubleshooting antes de sair refazendo tudo do zero.

Instalação

npm install expo-apple-wallet
# ou
yarn add expo-apple-wallet

Configuração

1. Painel da Apple Developer

Nas capabilities do seu identifier:

Wallet capability In-App Provisioning capability

  1. Manda um e-mail pra Apple pedindo a liberação da capability "In-App Provisioning" pro seu identifier (não é self-service, a Apple aprova manualmente).
  2. Cria um provisioning profile do tipo "App Store Provisioning Profile".

2. Entitlements no app.config.js / app.config.ts

ios: {
  entitlements: {
    "com.apple.developer.payment-pass-provisioning": true,
    "com.apple.developer.pass-type-identifiers": ["$(TeamIdentifierPrefix)*"],
  },
},

Referência da API

Todos os métodos ficam no export default: import ExpoAppleWallet from "expo-apple-wallet".

| Método | Assinatura | Descrição | |---|---|---| | isAvailable | () => Promise<boolean> | Se o provisioning do PassKit está disponível no device. Sempre retorna false no Simulador — teste em device real. | | isCardAlreadyAdded | (panTokenSuffix: string) => Promise<boolean> | Se já existe um cartão com esse sufixo de PAN na wallet do usuário. | | initEnrollProcess | (panTokenSuffix: string, holder: string) => Promise<{ nonce: string; nonceSignature: string; certificates: string } \| undefined> | Apresenta a tela nativa de adicionar à wallet e retorna o payload de criptografia que seu back-end precisa pra chamar o serviço web da Apple. | | completeEnrollment | (activationData: string, ephemeralPublicKey: string, encryptedPassData: string) => void | Chame depois que seu back-end devolver os dados criptografados do pass, pra concluir o provisioning. A ordem dos parâmetros importa — ver exemplo abaixo. | | inAppVerification | (serialNumber: string, passTypeIdentifier: string, action: string) => Promise<Record<string, string>> | Busca um pass já existente na wallet (local ou remoto) pra fluxos de ativação/verificação de cartão dentro do app. Retorna { error, message } se o pass não for encontrado ou não estiver em um estado ativável. |

O que é cada parâmetro

  • panTokenSuffix: os últimos dígitos (sufixo) do PAN/token do cartão que seu processador de pagamentos te dá — não é o número completo do cartão. A Apple usa isso só pra identificar/exibir qual cartão é, e pra checar se ele já foi adicionado antes (isCardAlreadyAdded).
  • holder: o nome do titular do cartão, exatamente como deve aparecer na tela de "Adicionar à Wallet" (campo "Nome" da tela nativa).
  • serialNumber / passTypeIdentifier: identificam um pass que já está instalado na Wallet do usuário (vêm do próprio pass, não são gerados por você). Usados só no fluxo de inAppVerification, pra achar esse pass e continuar a ativação dele.
  • activationData / ephemeralPublicKey / encryptedPassData: os três valores que voltam do seu backend, depois que ele chama o web service da Apple usando o nonce/nonceSignature/certificates que o initEnrollProcess te devolveu. São eles que fecham o ciclo de criptografia — sem os três, completeEnrollment não tem o que fazer.

Uso

Checando disponibilidade

const [isAvailable, setIsAvailable] = useState(false)

useEffect(() => {
  ExpoAppleWallet.isAvailable().then(setIsAvailable)
}, [])

Adicionando um cartão

const addCard = async () => {
  const result = await ExpoAppleWallet.initEnrollProcess(panTokenSuffix, holder)

  if (result) {
    const { nonce, nonceSignature, certificates } = result

    // Envia nonce/nonceSignature/certificates pro seu back-end,
    // que chama o serviço web da Apple e devolve:
    // { activationData, ephemeralPublicKey, encryptedPassData }
    const { activationData, ephemeralPublicKey, encryptedPassData } =
      await yourBackend.encryptNonce({ nonce, nonceSignature, certificates })

    await ExpoAppleWallet.completeEnrollment(
      activationData,
      ephemeralPublicKey,
      encryptedPassData,
    )
  }
}

Resultado final

Depois de chamar initEnrollProcess e completar o fluxo com completeEnrollment, o usuário vê a tela nativa da Apple pra confirmar a adição do cartão à Wallet:

Testando mudanças nessa lib

Você não consegue validar uma mudança nativa completamente dentro do example/ desse repo — a entitlement de In-App Provisioning da Apple é vinculada a um Team ID/merchant e provisioning profile específicos, que o app de exemplo não tem. Teste contra um app consumidor real que já tenha a capability aprovada:

  1. Faça a mudança em ios/ExpoAppleWalletModule.swift.
  2. Copia o arquivo pra dentro do app consumidor: cp ios/ExpoAppleWalletModule.swift <app>/node_modules/expo-apple-wallet/ios/ExpoAppleWalletModule.swift.
  3. No app consumidor, roda npx expo prebuild --clean pra regenerar o projeto nativo com o código atualizado.
  4. Abre ios/*.xcworkspace no Xcode e roda num device real (o Simulador não testa PassKit — canAddPaymentPass() sempre retorna false).
  5. Testa o fluxo completo de adicionar um cartão real à wallet.
  6. Só depois de passar nesse teste, leve a mudança de volta pra esse repo e faça o commit.

Troubleshooting

App fecha silenciosamente (sem erro no JS, nada no log do Metro) ao adicionar um cartão — depois de atualizar o Expo

Causa: o expo-modules-core mudou como o Utilities.currentViewController() resolve a key window entre o Expo SDK 56 e o 57. Os closures de AsyncFunction não necessariamente rodam na main thread, e mexer em UIKit (resolver/apresentar um view controller) fora da main thread pode derrubar o processo diretamente — antes mesmo de chegar num try/catch do JS ou numa rejeição de Promise. É por isso que parece "silencioso": o crash é nativo, não é uma exceção do JS.

Como confirmar: um try/catch no JS não vai mostrar nada. Você precisa do stack trace nativo:

  1. Conecta o device, abre ios/*.xcworkspace no Xcode, roda com o debugger conectado.
  2. Reproduz o crash. Olha o stack trace no debugger — se algum frame apontar pra dentro de Utilities.currentViewController() (ou outra chamada de UIKit) dentro do expo-modules-core, é a mesma classe de bug.

Fix: envolva qualquer código que mexa em appContext?.utilities?.currentViewController(), PKAddPaymentPassViewController.canAddPaymentPass() ou .present(...) num try await MainActor.run { ... }, ao invés de chamar direto ou usar um DispatchQueue.main.async fire-and-forget (que além disso engole silenciosamente qualquer erro lançado dentro dele). Veja o initEnrollProcess em ios/ExpoAppleWalletModule.swift pra ver a implementação atual.

Regra geral pra futuras atualizações de SDK: qualquer coisa nesse módulo que mexa em UIKit (UIApplication, UIWindow, UIViewController, PKAddPaymentPassViewController, etc.) precisa rodar dentro de MainActor.run, nunca assumir que já está na main thread.