diff --git a/README.md b/README.md
index 4033659..a6e7324 100644
--- a/README.md
+++ b/README.md
@@ -187,7 +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[]` | ✅ | ❌ |
-| **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. | `data`: `IOSCardData`,
`issuerEncrypt-`
`PayloadCallback: IOSIssuerCallback` | `void` | ✅ | ❌ |
+| **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 af84cf7..0ee2329 100644
--- a/android/src/main/java/com/expensify/wallet/WalletModule.kt
+++ b/android/src/main/java/com/expensify/wallet/WalletModule.kt
@@ -596,6 +596,10 @@ class WalletModule internal constructor(context: ReactApplicationContext) :
// no-op
}
+ override fun IOSFailAddPaymentPass(promise: Promise?) {
+ promise?.resolve(null)
+ }
+
override fun canAddCardToAppleWallet(primaryAccountIdentifier: String, lastDigits: String, promise: Promise) {
promise.resolve(false)
}
diff --git a/android/src/paper/java/com/expensify/wallet/NativeWalletSpec.java b/android/src/paper/java/com/expensify/wallet/NativeWalletSpec.java
index 07eb60e..2b1196b 100644
--- a/android/src/paper/java/com/expensify/wallet/NativeWalletSpec.java
+++ b/android/src/paper/java/com/expensify/wallet/NativeWalletSpec.java
@@ -85,6 +85,8 @@ public NativeWalletSpec(ReactApplicationContext reactContext) {
@DoNotStrip
public abstract void IOSHandleAddPaymentPassResponse(ReadableMap payload, Promise promise);
+ public abstract void IOSFailAddPaymentPass(Promise promise);
+
public abstract void activateAppleWalletPass(ReadableMap passData, Promise promise);
@ReactMethod
diff --git a/ios/RNWallet.mm b/ios/RNWallet.mm
index a4d4302..59bc0b6 100644
--- a/ios/RNWallet.mm
+++ b/ios/RNWallet.mm
@@ -114,6 +114,16 @@ - (instancetype)init {
}
}
+RCT_REMAP_METHOD(IOSFailAddPaymentPass,
+ IOSFailAddPaymentPass:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject)
+{
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [self->walletManager IOSFailAddPaymentPass];
+ resolve(nil);
+ });
+}
+
RCT_REMAP_METHOD(getCardStatusBySuffix,
getCardStatusBySuffix:(NSString *)last4Digits
resolve:(RCTPromiseResolveBlock)resolve
diff --git a/ios/WalletManager.swift b/ios/WalletManager.swift
index 6e5c8c7..ea3bf40 100644
--- a/ios/WalletManager.swift
+++ b/ios/WalletManager.swift
@@ -199,6 +199,16 @@ open class WalletManager: UIViewController {
self.addPassHandler?(addPaymentPassRequest)
self.addPassHandler = nil
}
+
+ /// Closes the Add Payment Pass sheet when the issuer encrypt call fails.
+ /// The PassKit completion handler is dropped: there is no payload to give it.
+ @objc
+ public func IOSFailAddPaymentPass() {
+ self.addPassHandler = nil
+ self.addPaymentPassCompletionHandler = nil
+ self.addPassPrimaryAccountIdentifier = nil
+ hideModal()
+ }
private func getPassActivationState(matching condition: (PKSecureElementPass) -> Bool) -> NSNumber {
let paymentPasses = passLibrary.passes(of: .payment)
diff --git a/ios/stucts/AddPassResponse.swift b/ios/stucts/AddPassResponse.swift
index 3512e45..886928b 100644
--- a/ios/stucts/AddPassResponse.swift
+++ b/ios/stucts/AddPassResponse.swift
@@ -23,13 +23,9 @@ struct AddPassResponse {
func toNSDictionary() -> NSDictionary {
if status.rawValue > 0 {
- var response: [String: Any] = [
+ return [
"status": status.rawValue,
- ]
- if let deviceType = deviceType {
- response["deviceType"] = deviceType.rawValue
- }
- return response as NSDictionary
+ ] as NSDictionary
}
guard let nonce = nonce,
diff --git a/src/NativeWallet.ts b/src/NativeWallet.ts
index 9522e9f..eb2bc87 100644
--- a/src/NativeWallet.ts
+++ b/src/NativeWallet.ts
@@ -128,6 +128,7 @@ export interface Spec extends TurboModule {
listAppleWalletPasses(): Promise;
IOSPresentAddPaymentPassView(cardData: IOSCardData): Promise;
IOSHandleAddPaymentPassResponse(payload: IOSEncryptPayload): Promise;
+ IOSFailAddPaymentPass(): Promise;
activateAppleWalletPass(passData: IOSPassActivationData): Promise;
canAddCardToAppleWallet(primaryAccountIdentifier: string, lastDigits: string): Promise;
addListener: (eventType: string) => void;
diff --git a/src/index.tsx b/src/index.tsx
index a08b373..92f9ae6 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -244,7 +244,17 @@ async function addCardToAppleWallet(
}
async function addPaymentPassToWallet(paymentPassData: IOSAddPaymentPassData): Promise {
- const responseData = await issuerEncryptPayloadCallback(paymentPassData.nonce, paymentPassData.nonceSignature, paymentPassData.certificates, toIOSDeviceType(paymentPassData.deviceType));
+ let responseData: IOSEncryptPayload;
+ try {
+ responseData = await issuerEncryptPayloadCallback(paymentPassData.nonce, paymentPassData.nonceSignature, paymentPassData.certificates, toIOSDeviceType(paymentPassData.deviceType));
+ } catch (error) {
+ try {
+ await wallet.IOSFailAddPaymentPass();
+ } catch {
+ // Closing the sheet is best-effort. The issuer error is what the user sees.
+ }
+ throw error;
+ }
const response = await wallet.IOSHandleAddPaymentPassResponse(responseData);
// Response is null when a pass is successfully added to the wallet or the user cancels the process
// In case the user presses the `Try again` option, new pass data is returned, and it should reenter the function