@bhut-it/expo-apple-wallet 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintrc.js ADDED
@@ -0,0 +1,5 @@
1
+ module.exports = {
2
+ root: true,
3
+ extends: ['universe/native', 'universe/web'],
4
+ ignorePatterns: ['build'],
5
+ };
package/README.md ADDED
@@ -0,0 +1,136 @@
1
+ # expo-apple-wallet (ideal para projetos Expo gerenciados)
2
+
3
+ [![npm version](https://img.shields.io/npm/v/expo-apple-wallet.svg)](https://www.npmjs.com/package/expo-apple-wallet)
4
+ [![npm downloads](https://img.shields.io/npm/dm/expo-apple-wallet.svg)](https://www.npmjs.com/package/expo-apple-wallet)
5
+
6
+ ## História
7
+
8
+ 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.
9
+
10
+ - **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"`.
11
+
12
+ ## Compatibilidade
13
+
14
+ | Versão da lib | Testada com Expo SDK | Observações |
15
+ |---|---|---|
16
+ | 3.3.2+ | 57+ | Exige o fix de `MainActor` (ver [Troubleshooting](#troubleshooting)) — o `expo-modules-core` 57 mudou como o `currentViewController()` precisa ser chamado. |
17
+ | 3.3.1 e anteriores | até 56 | Funciona sem o `MainActor`. |
18
+
19
+ 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](#troubleshooting) antes de sair refazendo tudo do zero.
20
+
21
+ ## Instalação
22
+
23
+ ```bash
24
+ npm install expo-apple-wallet
25
+ # ou
26
+ yarn add expo-apple-wallet
27
+ ```
28
+
29
+ ## Configuração
30
+
31
+ ### 1. Painel da Apple Developer
32
+
33
+ Nas capabilities do seu identifier:
34
+
35
+ ![Wallet capability](/assets/wallet.png)
36
+ ![In-App Provisioning capability](/assets/provisioning.png)
37
+
38
+ 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).
39
+ 2. Cria um provisioning profile do tipo **"App Store Provisioning Profile"**.
40
+
41
+ ### 2. Entitlements no `app.config.js` / `app.config.ts`
42
+
43
+ ```js
44
+ ios: {
45
+ entitlements: {
46
+ "com.apple.developer.payment-pass-provisioning": true,
47
+ "com.apple.developer.pass-type-identifiers": ["$(TeamIdentifierPrefix)*"],
48
+ },
49
+ },
50
+ ```
51
+
52
+ ## Referência da API
53
+
54
+ Todos os métodos ficam no export default: `import ExpoAppleWallet from "expo-apple-wallet"`.
55
+
56
+ | Método | Assinatura | Descrição |
57
+ |---|---|---|
58
+ | `isAvailable` | `() => Promise<boolean>` | Se o provisioning do PassKit está disponível no device. Sempre retorna `false` no Simulador — teste em device real. |
59
+ | `isCardAlreadyAdded` | `(panTokenSuffix: string) => Promise<boolean>` | Se já existe um cartão com esse sufixo de PAN na wallet do usuário. |
60
+ | `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. |
61
+ | `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. |
62
+ | `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. |
63
+
64
+ ### O que é cada parâmetro
65
+
66
+ - **`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`).
67
+ - **`holder`**: o nome do titular do cartão, exatamente como deve aparecer na tela de "Adicionar à Wallet" (campo "Nome" da tela nativa).
68
+ - **`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.
69
+ - **`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.
70
+
71
+ ## Uso
72
+
73
+ ### Checando disponibilidade
74
+
75
+ ```ts
76
+ const [isAvailable, setIsAvailable] = useState(false)
77
+
78
+ useEffect(() => {
79
+ ExpoAppleWallet.isAvailable().then(setIsAvailable)
80
+ }, [])
81
+ ```
82
+
83
+ ### Adicionando um cartão
84
+
85
+ ```ts
86
+ const addCard = async () => {
87
+ const result = await ExpoAppleWallet.initEnrollProcess(panTokenSuffix, holder)
88
+
89
+ if (result) {
90
+ const { nonce, nonceSignature, certificates } = result
91
+
92
+ // Envia nonce/nonceSignature/certificates pro seu back-end,
93
+ // que chama o serviço web da Apple e devolve:
94
+ // { activationData, ephemeralPublicKey, encryptedPassData }
95
+ const { activationData, ephemeralPublicKey, encryptedPassData } =
96
+ await yourBackend.encryptNonce({ nonce, nonceSignature, certificates })
97
+
98
+ await ExpoAppleWallet.completeEnrollment(
99
+ activationData,
100
+ ephemeralPublicKey,
101
+ encryptedPassData,
102
+ )
103
+ }
104
+ }
105
+ ```
106
+
107
+ ## Resultado final
108
+
109
+ 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:
110
+
111
+ <img src="/assets/add-to-wallet-result.png" alt="Tela nativa de adicionar cartão ao Apple Pay" width="320" />
112
+
113
+ ## Testando mudanças nessa lib
114
+
115
+ 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:
116
+
117
+ 1. Faça a mudança em `ios/ExpoAppleWalletModule.swift`.
118
+ 2. Copia o arquivo pra dentro do app consumidor: `cp ios/ExpoAppleWalletModule.swift <app>/node_modules/expo-apple-wallet/ios/ExpoAppleWalletModule.swift`.
119
+ 3. No app consumidor, roda `npx expo prebuild --clean` pra regenerar o projeto nativo com o código atualizado.
120
+ 4. Abre `ios/*.xcworkspace` no Xcode e roda num **device real** (o Simulador não testa PassKit — `canAddPaymentPass()` sempre retorna `false`).
121
+ 5. Testa o fluxo completo de adicionar um cartão real à wallet.
122
+ 6. Só depois de passar nesse teste, leve a mudança de volta pra esse repo e faça o commit.
123
+
124
+ ## Troubleshooting
125
+
126
+ ### App fecha silenciosamente (sem erro no JS, nada no log do Metro) ao adicionar um cartão — depois de atualizar o Expo
127
+
128
+ **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.
129
+
130
+ **Como confirmar:** um `try/catch` no JS **não** vai mostrar nada. Você precisa do stack trace nativo:
131
+ 1. Conecta o device, abre `ios/*.xcworkspace` no Xcode, roda com o debugger conectado.
132
+ 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.
133
+
134
+ **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.
135
+
136
+ **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.
@@ -0,0 +1,43 @@
1
+ apply plugin: 'com.android.library'
2
+
3
+ group = 'expo.modules.applewallet'
4
+ version = '0.1.0'
5
+
6
+ def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
7
+ apply from: expoModulesCorePlugin
8
+ applyKotlinExpoModulesCorePlugin()
9
+ useCoreDependencies()
10
+ useExpoPublishing()
11
+
12
+ // If you want to use the managed Android SDK versions from expo-modules-core, set this to true.
13
+ // The Android SDK versions will be bumped from time to time in SDK releases and may introduce breaking changes in your module code.
14
+ // Most of the time, you may like to manage the Android SDK versions yourself.
15
+ def useManagedAndroidSdkVersions = false
16
+ if (useManagedAndroidSdkVersions) {
17
+ useDefaultAndroidSdkVersions()
18
+ } else {
19
+ buildscript {
20
+ // Simple helper that allows the root project to override versions declared by this library.
21
+ ext.safeExtGet = { prop, fallback ->
22
+ rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
23
+ }
24
+ }
25
+ project.android {
26
+ compileSdkVersion safeExtGet("compileSdkVersion", 34)
27
+ defaultConfig {
28
+ minSdkVersion safeExtGet("minSdkVersion", 21)
29
+ targetSdkVersion safeExtGet("targetSdkVersion", 34)
30
+ }
31
+ }
32
+ }
33
+
34
+ android {
35
+ namespace "expo.modules.applewallet"
36
+ defaultConfig {
37
+ versionCode 1
38
+ versionName "0.1.0"
39
+ }
40
+ lintOptions {
41
+ abortOnError false
42
+ }
43
+ }
@@ -0,0 +1,2 @@
1
+ <manifest>
2
+ </manifest>
@@ -0,0 +1,11 @@
1
+ package expo.modules.applewallet
2
+
3
+ import expo.modules.kotlin.modules.Module
4
+ import expo.modules.kotlin.modules.ModuleDefinition
5
+
6
+ class ExpoAppleWalletModule : Module() {
7
+ override fun definition() = ModuleDefinition {
8
+ Name("ExpoAppleWallet")
9
+
10
+ }
11
+ }
Binary file
Binary file
Binary file
@@ -0,0 +1,3 @@
1
+ declare const ExpoAppleWalletModule: any;
2
+ export default ExpoAppleWalletModule;
3
+ //# sourceMappingURL=ExpoAppleWalletModule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpoAppleWalletModule.d.ts","sourceRoot":"","sources":["../src/ExpoAppleWalletModule.ts"],"names":[],"mappings":"AAEA,QAAA,MAAM,qBAAqB,KAAyC,CAAC;AAErE,eAAe,qBAAqB,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+ const ExpoAppleWalletModule = requireNativeModule('ExpoAppleWallet');
3
+ export default ExpoAppleWalletModule;
4
+ //# sourceMappingURL=ExpoAppleWalletModule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpoAppleWalletModule.js","sourceRoot":"","sources":["../src/ExpoAppleWalletModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,MAAM,qBAAqB,GAAG,mBAAmB,CAAC,iBAAiB,CAAC,CAAC;AAErE,eAAe,qBAAqB,CAAC","sourcesContent":["import { requireNativeModule } from 'expo-modules-core';\n\nconst ExpoAppleWalletModule = requireNativeModule('ExpoAppleWallet');\n\nexport default ExpoAppleWalletModule;\n"]}
@@ -0,0 +1,2 @@
1
+ export { default } from './ExpoAppleWalletModule';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC"}
package/build/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { default } from './ExpoAppleWalletModule';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC","sourcesContent":["export { default } from './ExpoAppleWalletModule';"]}
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "expo-apple-wallet",
3
+ "platforms": ["android", "ios"],
4
+ "apple": {
5
+ "modules": [
6
+ "ExpoAppleWalletModule"
7
+ ]
8
+ },
9
+ "android": {
10
+ "modules": [
11
+ "expo.modules.applewallet.ExpoAppleWalletModule"
12
+ ]
13
+ }
14
+ }
@@ -0,0 +1,31 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'ExpoAppleWallet'
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.description = package['description']
10
+ s.license = package['license']
11
+ s.author = package['author']
12
+ s.homepage = package['homepage']
13
+ s.platforms = {
14
+ :ios => '12.0',
15
+ :tvos => '12.0'
16
+ }
17
+ s.swift_version = '5.4'
18
+
19
+ s.source = {
20
+ :http => "https://registry.npmjs.org/@bhut-it/expo-apple-wallet/-/expo-apple-wallet-#{s.version}.tgz"
21
+ }
22
+
23
+ s.dependency 'ExpoModulesCore'
24
+
25
+ # Swift/Objective-C compatibility
26
+ s.pod_target_xcconfig = {
27
+ 'DEFINES_MODULE' => 'YES',
28
+ }
29
+
30
+ s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}"
31
+ end
@@ -0,0 +1,240 @@
1
+ import ExpoModulesCore
2
+ import UIKit
3
+ import PassKit
4
+
5
+ private struct Card {
6
+ let panTokenSuffix: String
7
+ let holder: String
8
+ }
9
+
10
+ struct NonceResult {
11
+ let nonce: String
12
+ let nonceSignature: String
13
+ let certificates: [String]
14
+ }
15
+
16
+ private func isPassKitAvailable() -> Bool {
17
+ return PKAddPaymentPassViewController.canAddPaymentPass()
18
+ }
19
+
20
+ private func cardInformation(panTokenSuffix: String, holder: String) -> Card {
21
+ return Card(panTokenSuffix: panTokenSuffix, holder: holder)
22
+ }
23
+
24
+ class HandleDelegate: NSObject, PKAddPaymentPassViewControllerDelegate {
25
+ private let configuration: PKAddPaymentPassRequestConfiguration
26
+ private unowned let module: ExpoAppleWalletModule
27
+ private var completionHandler: ((PKAddPaymentPassRequest) -> Void)?
28
+ private var nonceContinuation: CheckedContinuation<NonceResult, Error>?
29
+
30
+ init(configuration: PKAddPaymentPassRequestConfiguration, module: ExpoAppleWalletModule) {
31
+ self.configuration = configuration
32
+ self.module = module
33
+ }
34
+
35
+ func addPaymentPassViewController(
36
+ _ controller: PKAddPaymentPassViewController,
37
+ generateRequestWithCertificateChain certificates: [Data],
38
+ nonce: Data, nonceSignature: Data,
39
+ completionHandler handler: @escaping (PKAddPaymentPassRequest) -> Void
40
+ ) {
41
+ self.completionHandler = handler
42
+
43
+ let nonceBase64 = nonce.base64EncodedString()
44
+ let nonceSignatureBase64 = nonceSignature.base64EncodedString()
45
+ let certificatesBase64 = certificates.map { $0.base64EncodedString() }
46
+
47
+ if let continuation = self.nonceContinuation {
48
+ continuation.resume(returning: NonceResult(
49
+ nonce: nonceBase64,
50
+ nonceSignature: nonceSignatureBase64,
51
+ certificates: certificatesBase64
52
+ ))
53
+ self.nonceContinuation = nil
54
+ } else {
55
+ print("Nenhuma continuation esperando pelo nonce")
56
+ }
57
+ }
58
+
59
+ func addPaymentPassViewController(
60
+ _ controller: PKAddPaymentPassViewController,
61
+ didFinishAdding pass: PKPaymentPass?,
62
+ error: Error?
63
+ ) {
64
+ if let error = error {
65
+ print("Erro ao adicionar cartão: \(error.localizedDescription)")
66
+ } else {
67
+ print("Cartão adicionado com sucesso!")
68
+ }
69
+
70
+ DispatchQueue.main.async {
71
+ controller.dismiss(animated: true, completion: nil)
72
+ }
73
+ }
74
+
75
+ func createEnrollViewController() -> PKAddPaymentPassViewController? {
76
+ return PKAddPaymentPassViewController(requestConfiguration: configuration, delegate: self)
77
+ }
78
+
79
+ func continueEnrollment(
80
+ activationData: Data,
81
+ ephemeralPublicKey: Data,
82
+ encryptedPassData: Data
83
+ ) {
84
+ guard let handler = self.completionHandler else {
85
+ print("Handler não encontrado")
86
+ return
87
+ }
88
+
89
+ let request = PKAddPaymentPassRequest()
90
+
91
+ request.activationData = activationData
92
+ request.ephemeralPublicKey = ephemeralPublicKey
93
+ request.encryptedPassData = encryptedPassData
94
+
95
+ handler(request)
96
+
97
+ self.completionHandler = nil
98
+ }
99
+
100
+ func waitForNonce() async throws -> NonceResult {
101
+ return try await withCheckedThrowingContinuation { continuation in
102
+ self.nonceContinuation = continuation
103
+ }
104
+ }
105
+ }
106
+
107
+ public class ExpoAppleWalletModule: Module {
108
+ static var shared: ExpoAppleWalletModule?
109
+ var activeDelegate: HandleDelegate?
110
+
111
+ public func definition() -> ModuleDefinition {
112
+ Name("ExpoAppleWallet")
113
+
114
+ Function("isAvailable") { () -> Bool in
115
+ return isPassKitAvailable()
116
+ }
117
+
118
+ AsyncFunction("isCardAlreadyAdded") { (panTokenSuffix: String) -> Bool in
119
+ let passLibrary = PKPassLibrary()
120
+ let passes = passLibrary.passes()
121
+
122
+ for pass in passes {
123
+ if let paymentPass = pass as? PKPaymentPass {
124
+ if paymentPass.primaryAccountNumberSuffix == panTokenSuffix {
125
+ return true
126
+ }
127
+ }
128
+ }
129
+
130
+ return false
131
+ }
132
+
133
+ AsyncFunction("initEnrollProcess") { (panTokenSuffix: String, holder: String) async throws -> [String: String] in
134
+ let card = cardInformation(panTokenSuffix: panTokenSuffix, holder: holder)
135
+
136
+ guard let configuration = PKAddPaymentPassRequestConfiguration(encryptionScheme: .ECC_V2) else {
137
+ throw NSError(domain: "Configuração inválida", code: 0)
138
+ }
139
+
140
+ configuration.cardholderName = card.holder
141
+ configuration.primaryAccountSuffix = card.panTokenSuffix
142
+ configuration.paymentNetwork = PKPaymentNetwork.visa
143
+
144
+ let delegate = HandleDelegate(configuration: configuration, module: self)
145
+ self.activeDelegate = delegate
146
+
147
+ try await MainActor.run {
148
+ guard PKAddPaymentPassViewController.canAddPaymentPass() else {
149
+ throw NSError(domain: "PassKit indisponível", code: 0)
150
+ }
151
+
152
+ guard let enrollViewController = delegate.createEnrollViewController() else {
153
+ throw NSError(domain: "Erro ao criar controlador", code: 0)
154
+ }
155
+
156
+ guard let currentVC = appContext?.utilities?.currentViewController() else {
157
+ throw NSError(domain: "Sem controlador atual", code: 0)
158
+ }
159
+
160
+ currentVC.present(enrollViewController, animated: true, completion: nil)
161
+ }
162
+
163
+ let result = try await delegate.waitForNonce()
164
+
165
+ return [
166
+ "nonce": result.nonce,
167
+ "nonceSignature": result.nonceSignature,
168
+ "certificates": result.certificates.joined(separator: ",")
169
+ ]
170
+ }
171
+
172
+ Function("completeEnrollment") {
173
+ (
174
+ activationDataBase64: String,
175
+ ephemeralPublicKeyBase64: String,
176
+ encryptedPassDataBase64: String
177
+ ) in
178
+
179
+ guard let delegate = self.activeDelegate else {
180
+ print("Delegate não encontrado")
181
+ return
182
+ }
183
+
184
+ guard let activationData = Data(base64Encoded: activationDataBase64),
185
+ let ephemeralPublicKey = Data(base64Encoded: ephemeralPublicKeyBase64),
186
+ let encryptedPassData = Data(base64Encoded: encryptedPassDataBase64) else {
187
+ print("Dados inválidos")
188
+ return
189
+ }
190
+
191
+ delegate.continueEnrollment(
192
+ activationData: activationData,
193
+ ephemeralPublicKey: ephemeralPublicKey,
194
+ encryptedPassData: encryptedPassData
195
+ )
196
+ }
197
+
198
+ AsyncFunction("inAppVerification") { (serialNumber: String, passTypeIdentifier: String, action: String) in
199
+ print("inAppVerification: \(serialNumber) \(passTypeIdentifier) \(action)")
200
+
201
+ let passLibrary = PKPassLibrary()
202
+
203
+ var foundPass: PKSecureElementPass? = passLibrary.pass(withPassTypeIdentifier: passTypeIdentifier, serialNumber: serialNumber) as? PKSecureElementPass
204
+
205
+ if foundPass == nil {
206
+ let remotePasses = PKPassLibrary().remoteSecureElementPasses
207
+ foundPass = remotePasses.first(where: {
208
+ $0.passTypeIdentifier == passTypeIdentifier && $0.serialNumber == serialNumber
209
+ })
210
+ }
211
+
212
+ guard let securePass = foundPass else {
213
+ return [
214
+ "error": "CARD_NOT_FOUND",
215
+ "message": "Cartão não encontrado no dispositivo"
216
+ ]
217
+ }
218
+
219
+ guard securePass.passActivationState == .requiresActivation else {
220
+ return [
221
+ "error": "INVALID_CARD_STATE",
222
+ "message": "Cartão em estado inválido para ativação"
223
+ ]
224
+ }
225
+
226
+ var result: [String: String] = [
227
+ "deviceAccountNumberSuffix": securePass.deviceAccountNumberSuffix,
228
+ "primaryAccountIdentifier": securePass.primaryAccountIdentifier,
229
+ "activationState": "requiresActivation",
230
+ "serialNumber": securePass.serialNumber,
231
+ "passTypeIdentifier": securePass.passTypeIdentifier
232
+ ]
233
+
234
+ let deviceName = UIDevice.current.name
235
+ result["deviceName"] = deviceName
236
+
237
+ return result
238
+ }
239
+ }
240
+ }
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@bhut-it/expo-apple-wallet",
3
+ "version": "1.0.2",
4
+ "description": "A module to call apple wallet using Passkit API",
5
+ "main": "build/index.js",
6
+ "types": "build/index.d.ts",
7
+ "scripts": {
8
+ "build": "expo-module build",
9
+ "clean": "expo-module clean",
10
+ "lint": "expo-module lint",
11
+ "test": "expo-module test",
12
+ "prepare": "expo-module prepare",
13
+ "prepublishOnly": "expo-module prepublishOnly",
14
+ "expo-module": "expo-module",
15
+ "open:ios": "xed example/ios",
16
+ "open:android": "open -a \"Android Studio\" example/android"
17
+ },
18
+ "keywords": [
19
+ "react-native",
20
+ "expo",
21
+ "expo-react-native-wallet",
22
+ "ExpoAppleWallet"
23
+ ],
24
+ "author": "BHUT <contato@bhut.com.br>",
25
+ "license": "MIT",
26
+ "devDependencies": {
27
+ "@types/react": "~18.3.12",
28
+ "expo-module-scripts": "^4.0.3",
29
+ "expo": "~52.0.0",
30
+ "react-native": "0.76.0"
31
+ },
32
+ "peerDependencies": {
33
+ "expo": "*",
34
+ "react": "*",
35
+ "react-native": "*"
36
+ },
37
+ "directories": {
38
+ "example": "example"
39
+ }
40
+ }
@@ -0,0 +1,5 @@
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+
3
+ const ExpoAppleWalletModule = requireNativeModule('ExpoAppleWallet');
4
+
5
+ export default ExpoAppleWalletModule;
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export { default } from './ExpoAppleWalletModule';
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ // @generated by expo-module-scripts
2
+ {
3
+ "extends": "expo-module-scripts/tsconfig.base",
4
+ "compilerOptions": {
5
+ "outDir": "./build"
6
+ },
7
+ "include": ["./src"],
8
+ "exclude": ["**/__mocks__/*", "**/__tests__/*", "**/__rsc_tests__/*"]
9
+ }