diff --git a/README.md b/README.md
index a6e7324..98ca8f1 100644
--- a/README.md
+++ b/README.md
@@ -187,6 +187,7 @@ The library offers functions for seamless integration and use of the Apple Walle
| **resumeAddCardToGoogleWallet** | Resumes the Push Provisioning flow for adding a card to the Google Wallet using existing token reference ID. | `data`: `AndroidResumeCardData` | `TokenizationStatus` | ❌ | ✅ |
| **listTokens** | Lists all tokens currently stored in the Google Wallet. | None | `TokenInfo[]` | ❌ | ✅ |
| **listAppleWalletPasses** | Lists Apple Wallet Secure Element passes available to the app, including activation state and whether the pass is remote (for example on Apple Watch). Filter the result for `activationState === 'requireActivation'` or `activationState === 'pending'` to build a pending-cards UI. | None | `IOSWalletPass[]` | ✅ | ❌ |
+| **presentAppleWalletPass** | Opens the installed iPhone pass for these last digits, preferring one that still needs verification. Returns `false` when `lastDigits` is empty, when no pass matches, when more than one card ends in those digits, and on Android. | `lastDigits: string` | `boolean` | ✅ | ✅ |
| **addCardToAppleWallet** | Initiates native Push Provisioning flow for adding a card to the Apple Wallet. When `IOSCardData.primaryAccountIdentifier` is set, the device picker only lists devices that do not already have the card. If PassKit cannot present the flow because the card is already on every device, the call fails with a clear error. If the issuer callback throws, the Add Payment Pass sheet is closed and the promise rejects with that error. | `data`: `IOSCardData`,
`issuerEncrypt-`
`PayloadCallback: IOSIssuerCallback` | `void` | ✅ | ❌ |
| **canAddCardToAppleWallet** | Checks whether the card can still be added to the iPhone or a paired Apple Watch. Pass `lastDigits` to use the same id lookup as `addCardToAppleWallet`. Returns `false` on Android. | `primaryAccountIdentifier: string`,
`lastDigits?: string` | `boolean` | ✅ | ✅ |
| **activateAppleWalletPass** | Activates an existing Secure Element pass in Apple Wallet using activation data from your issuer backend. Resolves `true` when the pass is already active. For `requireActivation`, returns Apple's result, which can be `false`. Rejects for any other state (`pending`, `suspended`), a missing pass, unavailable activation, or invalid or non-base64 activation data. | `data`: `IOSPassActivationData` | `boolean` | ✅ | ❌ |
diff --git a/android/src/main/java/com/expensify/wallet/WalletModule.kt b/android/src/main/java/com/expensify/wallet/WalletModule.kt
index 0ee2329..b08bfaa 100644
--- a/android/src/main/java/com/expensify/wallet/WalletModule.kt
+++ b/android/src/main/java/com/expensify/wallet/WalletModule.kt
@@ -580,6 +580,10 @@ class WalletModule internal constructor(context: ReactApplicationContext) :
.emit(eventName, params)
}
+ override fun presentAppleWalletPass(lastDigits: String, promise: Promise?) {
+ promise?.resolve(false)
+ }
+
override fun IOSPresentAddPaymentPassView(cardData: ReadableMap?, promise: Promise?) {
// no-op
}
diff --git a/android/src/paper/java/com/expensify/wallet/NativeWalletSpec.java b/android/src/paper/java/com/expensify/wallet/NativeWalletSpec.java
index 2b1196b..d8600f6 100644
--- a/android/src/paper/java/com/expensify/wallet/NativeWalletSpec.java
+++ b/android/src/paper/java/com/expensify/wallet/NativeWalletSpec.java
@@ -77,6 +77,10 @@ public NativeWalletSpec(ReactApplicationContext reactContext) {
@DoNotStrip
public abstract void listAppleWalletPasses(Promise promise);
+ @ReactMethod
+ @DoNotStrip
+ public abstract void presentAppleWalletPass(String lastDigits, Promise promise);
+
@ReactMethod
@DoNotStrip
public abstract void IOSPresentAddPaymentPassView(ReadableMap cardData, Promise promise);
diff --git a/ios/RNWallet.mm b/ios/RNWallet.mm
index 59bc0b6..fff31e3 100644
--- a/ios/RNWallet.mm
+++ b/ios/RNWallet.mm
@@ -132,6 +132,16 @@ - (instancetype)init {
resolve([walletManager getCardStatusBySuffixWithLast4Digits:last4Digits]);
}
+RCT_REMAP_METHOD(presentAppleWalletPass,
+ presentAppleWalletPass:(NSString *)lastDigits
+ resolve:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject)
+{
+ dispatch_async(dispatch_get_main_queue(), ^{
+ resolve(@([self->walletManager presentAppleWalletPassWithLastDigits:lastDigits]));
+ });
+}
+
RCT_REMAP_METHOD(getCardStatusByIdentifier,
getCardStatusByIdentifier:(NSString *)identifier
tsp:(NSString *)tsp
diff --git a/ios/WalletManager.swift b/ios/WalletManager.swift
index ea3bf40..465209d 100644
--- a/ios/WalletManager.swift
+++ b/ios/WalletManager.swift
@@ -335,6 +335,27 @@ open class WalletManager: UIViewController {
}
}
+ @objc public func presentAppleWalletPass(lastDigits: NSString) -> Bool {
+ let suffix = lastDigits as String
+ guard !suffix.isEmpty else {
+ return false
+ }
+
+ let candidates = passLibrary.passes(of: .secureElement)
+ .filter { !$0.isRemotePass }
+ .compactMap(\.secureElementPass)
+ .filter {
+ $0.primaryAccountNumberSuffix.hasSuffix(suffix) &&
+ $0.passActivationState != .deactivated
+ }
+ guard Set(candidates.map(\.primaryAccountIdentifier)).count == 1,
+ let pass = candidates.first(where: { $0.passActivationState == .requiresActivation }) ?? candidates.first else {
+ return false
+ }
+ passLibrary.present(pass)
+ return true
+ }
+
@objc public func canAddCardToAppleWallet(primaryAccountIdentifier: NSString, lastDigits: NSString) -> Bool {
let suffix = lastDigits as String
let resolved = suffix.isEmpty
diff --git a/src/NativeWallet.ts b/src/NativeWallet.ts
index eb2bc87..6b761e7 100644
--- a/src/NativeWallet.ts
+++ b/src/NativeWallet.ts
@@ -126,6 +126,7 @@ export interface Spec extends TurboModule {
resumeAddCardToGoogleWallet(cardData: AndroidResumeCardData): Promise;
listTokens(): Promise;
listAppleWalletPasses(): Promise;
+ presentAppleWalletPass(lastDigits: string): Promise;
IOSPresentAddPaymentPassView(cardData: IOSCardData): Promise;
IOSHandleAddPaymentPassResponse(payload: IOSEncryptPayload): Promise;
IOSFailAddPaymentPass(): Promise;
diff --git a/src/index.tsx b/src/index.tsx
index 92f9ae6..c2f26b5 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -279,6 +279,18 @@ async function activateAppleWalletPass(passData: IOSPassActivationData): Promise
return Wallet.activateAppleWalletPass(passData);
}
+async function presentAppleWalletPass(lastDigits: string): Promise {
+ if (Platform.OS === 'android') {
+ return false;
+ }
+
+ if (!Wallet) {
+ return getModuleLinkingRejection();
+ }
+
+ return Wallet.presentAppleWalletPass(lastDigits);
+}
+
export type {
AndroidCardData,
AndroidWalletData,
@@ -306,6 +318,7 @@ export {
resumeAddCardToGoogleWallet,
listTokens,
listAppleWalletPasses,
+ presentAppleWalletPass,
canAddCardToAppleWallet,
addCardToAppleWallet,
activateAppleWalletPass,