diff --git a/README.md b/README.md
index ee3b7fdc..fe28f02f 100644
--- a/README.md
+++ b/README.md
@@ -166,9 +166,11 @@ Here you can find data elements used in the library, essential to work with Goog
- **Certificates** - an array of NSData objects. Each object contains a DER encoded X.509 certificate, with the leaf first and root last. You must download the root CA to validate the entire chain.
- **Encrypted Pass Data**- an encrypted JSON file containing the sensitive information needed to add a card to Apple Pay (Base 64 encoded). [More details here.](https://developer.apple.com/documentation/passkit/pkaddpaymentpassrequest/encryptedpassdata)
- **Ephemeral Public Key** - a key used by elliptic curve cryptography (ECC) (Base 64 encoded).
+- **Primary Account Identifier** - an opaque issuer id (also called the FPAN ID) that PassKit uses to recognize the same card across reissues and devices. Pass it in `IOSCardData` and to `canAddCardToAppleWallet`. On iOS, `getCardStatusByIdentifier` uses this same value. If no pass matches the given id exactly, the library uses the id of the only pass with the same last digits.
+- **Device type** - the device the card is being provisioned to: `phone` or `watch`. PassKit does not report the device the user selected, so the library infers it from where the card is already provisioned. When it cannot tell, the value is `unknown`.
# API Reference
-The library offers seven functions for seamless integration and use of the Apple Wallet and Google Wallet APIs. Additionally, it includes one listener that informs when the added card has been activated. Below, these functions are described along with the data types involved.
+The library offers nine functions for seamless integration and use of the Apple Wallet and Google Wallet APIs. Additionally, it includes one listener that informs when the added card has been activated. Below, these functions are described along with the data types involved.
## Functions
@@ -181,7 +183,8 @@ The library offers seven functions for seamless integration and use of the Apple
| **addCardToGoogleWallet** | Initiates native Push Provisioning flow for adding a card to the Google Wallet. | `data`: `AndroidCardData` | `TokenizationStatus` | ❌ | ✅ |
| **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[]` | ❌ | ✅ |
-| **addCardToAppleWallet** | Initiates native Push Provisioning flow for adding a card to the Apple Wallet. | `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. | `data`: `IOSCardData`,
`issuerEncrypt-`
`PayloadCallback: IOSIssuerCallback` | `void` | ✅ | ❌ |
+| **canAddCardToAppleWallet** | Checks whether the card can still be added to any of the user's devices (iPhone or paired Apple Watch). | `primaryAccountIdentifier: string` | `boolean` | ✅ | ❌ |
## Data Types
@@ -192,9 +195,11 @@ The library offers seven functions for seamless integration and use of the Apple
| **AndroidCardData** | Data related to a card that is to be added on Android platform wallets. | `network: string`,
`opaquePaymentCard: string`,
`cardHolderName: string`,
`lastDigits: string`,
`userAddress: UserAddress` |
| **AndroidResumeCardData** | Simplified data structure for resuming card addition to Google Wallet using existing token reference ID. | `network: string`,
`tokenReferenceID: string`,
`cardHolderName?: string`,
`lastDigits?: string` |
| **UserAddress** | Structured address used for cardholder verification. | `name: string`,
`addressOne: string`,
`addressTwo: string`,
`city: string`,
`administrativeArea: string`,
`countryCode: string`,
`postalCode: string`,
`phoneNumber: string` |
-| **IOSCardData** | Data related to a card that is to be added on iOS platform. | `network: string`,
`activationData: string`,
`encryptedPassData: string`,
`ephemeralPublicKey: string`,
`cardHolderTitle: string`,
`cardHolderName: string`,
`lastDigits: string`,
`cardDescription: string`,
`cardDescriptionComment: string` |
+| **IOSCardData** | Data related to a card that is to be added on iOS. Set `primaryAccountIdentifier` after the first provisioning so the next attempt targets devices that do not already have the card. | `network: string`,
`activationData: string`,
`encryptedPassData: string`,
`ephemeralPublicKey: string`,
`cardHolderTitle: string`,
`cardHolderName: string`,
`lastDigits: string`,
`cardDescription: string`,
`cardDescriptionComment: string`,
`primaryAccountIdentifier?: string` |
| **onCardActivatedPayload** | Data used by listener to notice when a card's status changes. | `tokenId: string`,
`status: 'activated' \| 'canceled'`
|
-| **IOSIssuerCallback** | This callback is invoked with a nonce, its signature, and a certificate array obtained from Apple. It is expected that you will forward these details to your server or the card issuer's API to securely encrypt the payload required for adding cards to the Apple Wallet. | `(nonce: string, nonceSignature: string, certificate: string[]) => IOSEncryptPayload` |
+| **IOSIssuerCallback** | This callback is invoked with a nonce, its signature, a certificate array obtained from Apple, and the device the card is being added to. Forward these details to your server or the card issuer's API to securely encrypt the payload required for adding cards to the Apple Wallet. `deviceType` is a best-effort value; see `IOSDeviceType`. | `(nonce: string, nonceSignature: string, certificate: string[], deviceType: IOSDeviceType) => IOSEncryptPayload` |
+| **IOSDeviceType** | Best-effort device the card is being provisioned to. PassKit does not report the selected device, so this is inferred from existing passes when `primaryAccountIdentifier` is set. If no pass matches that id exactly, the library uses the id of the only pass with the same last digits. Passes on a paired Apple Watch are never counted as iPhone passes. `phone` means the card is already on the paired Apple Watch only. `watch` means it is already on the iPhone only. `unknown` means the card is on neither device, or no identifier was provided, so the user may still choose either device. Passes in the `deactivated` state count as not present. | `'phone' \| 'watch' \| 'unknown'` |
+| **IOSAddPaymentPassData** | Data Apple returns when the user continues the provisioning flow, including the device the card is being added to. | `status: number`,
`nonce: string`,
`nonceSignature: string`,
`certificates: string[]`,
`deviceType?: string` |
| **IOSEncryptPayload** | An object containing the necessary elements to complete the addition of a card to Apple Wallet. | `encryptedPassData: string`,
`activationData: string`,
`ephemeralPublicKey: string` |
| **TokenInfo** | Information about a token stored in Google Wallet. | `identifier: string`,
`lastDigits: string`,
`tokenState: number` |
@@ -267,6 +272,7 @@ const base64ToHex = (base64: string): string => Buffer.from(base64, "base64").to
nonce: string,
nonceSignature: string,
certificate: string[],
+ deviceType: IOSDeviceType,
): Promise => {
try {
const payload = {
@@ -275,6 +281,7 @@ const base64ToHex = (base64: string): string => Buffer.from(base64, "base64").to
nonceSignature: base64ToHex(nonceSignature),
wspCertificates: certificate,
},
+ deviceType,
serialNumber,
wspId: "APPLE" as const,
}
@@ -301,7 +308,7 @@ The app will need to be reviewed by Google. During this process, it will need to
### iOS
When implementing the In-App Push Provisioning feature in your App make sure that your app follows Apple's [branding guidelines connected to Apple Wallet](https://developer.apple.com/wallet/add-to-apple-wallet-guidelines/). Remember that you must not create your own buttons or your app could be rejected at revision. You can use the[ AddWalletButton component](#components) instead!
-When the pass is already provisioned, make sure to hide this button and replace it with text like `Added to Apple Wallet`. The card is fully provisioned once it added to your main device (user's iPhone) and all linked devices (for example Apple Watch).
+Keep the Add to Apple Wallet button visible while `canAddCardToAppleWallet(primaryAccountIdentifier)` returns `true`, including after the card is added to the iPhone but not yet to a paired Apple Watch. Show text like `Added to Apple Wallet` only once that call returns `false`. Pass `primaryAccountIdentifier` in `IOSCardData` so the next attempt targets the remaining device. This matches [Apple's in-app provisioning guidance](https://applepaydemo.apple.com/in-app-provisioning#3.3).
Next to branding guidelines, please follow the instructions and best practices from [the In-App provisioning documentation]((#ios)) provided by Apple.
diff --git a/android/src/main/java/com/expensify/wallet/WalletModule.kt b/android/src/main/java/com/expensify/wallet/WalletModule.kt
index dfa1a398..152ab5e8 100644
--- a/android/src/main/java/com/expensify/wallet/WalletModule.kt
+++ b/android/src/main/java/com/expensify/wallet/WalletModule.kt
@@ -365,6 +365,10 @@ class WalletModule internal constructor(context: ReactApplicationContext) :
// no-op
}
+ override fun canAddCardToAppleWallet(primaryAccountIdentifier: String, promise: Promise) {
+ // no-op
+ }
+
override fun addListener(eventType: String?) {
// 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 999f0cb8..773b336f 100644
--- a/android/src/paper/java/com/expensify/wallet/NativeWalletSpec.java
+++ b/android/src/paper/java/com/expensify/wallet/NativeWalletSpec.java
@@ -73,6 +73,10 @@ public NativeWalletSpec(ReactApplicationContext reactContext) {
@DoNotStrip
public abstract void IOSHandleAddPaymentPassResponse(ReadableMap payload, Promise promise);
+ @ReactMethod
+ @DoNotStrip
+ public abstract void canAddCardToAppleWallet(String primaryAccountIdentifier, Promise promise);
+
@ReactMethod
@DoNotStrip
public abstract void addListener(String eventType);
diff --git a/ios/RNWallet.mm b/ios/RNWallet.mm
index 4981b78a..bca330ef 100644
--- a/ios/RNWallet.mm
+++ b/ios/RNWallet.mm
@@ -42,24 +42,32 @@ - (instancetype)init {
reject:(RCTPromiseRejectBlock)reject)
{
@try {
- NSDictionary *cardDataDict = nil;
+ NSMutableDictionary *cardDataDict = nil;
+ NSString *primaryAccountIdentifier = nil;
#ifdef RCT_NEW_ARCH_ENABLED
- cardDataDict = @{
+ cardDataDict = [@{
@"network": [self safeString:cardData.network()],
@"cardHolderName": [self safeString:cardData.cardHolderName()],
@"lastDigits": [self safeString:cardData.lastDigits()],
@"cardDescription": [self safeString:cardData.cardDescription()],
- };
+ } mutableCopy];
+ primaryAccountIdentifier = [self safeString:cardData.primaryAccountIdentifier()];
#else
- cardDataDict = @{
+ cardDataDict = [@{
@"network": [self safeString:cardData[@"network"]],
@"cardHolderName": [self safeString:cardData[@"cardHolderName"]],
@"lastDigits": [self safeString:cardData[@"lastDigits"]],
@"cardDescription": [self safeString:cardData[@"cardDescription"]],
- };
+ } mutableCopy];
+ id rawIdentifier = cardData[@"primaryAccountIdentifier"];
+ primaryAccountIdentifier = [self safeString:[rawIdentifier isKindOfClass:[NSString class]] ? rawIdentifier : nil];
#endif
+ if (primaryAccountIdentifier.length > 0) {
+ cardDataDict[@"primaryAccountIdentifier"] = primaryAccountIdentifier;
+ }
+
dispatch_async(dispatch_get_main_queue(), ^{
[self->walletManager IOSPresentAddPaymentPassViewWithCardData:cardDataDict completion:^(OperationResult result, NSDictionary* data) {
[self handleWalletResponse:result data:data completedBlock:resolve errorPrefix:@"present_payment_pass_view_failed" defaultErrorMessage:@"Failed to present the payment pass view" rejecter:reject];
@@ -123,6 +131,14 @@ - (instancetype)init {
resolve([walletManager getCardStatusByIdentifierWithIdentifier:identifier]);
}
+RCT_REMAP_METHOD(canAddCardToAppleWallet,
+ canAddCardToAppleWallet:(NSString *)primaryAccountIdentifier
+ resolve:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject)
+{
+ resolve(@([walletManager canAddCardToAppleWalletWithPrimaryAccountIdentifier:primaryAccountIdentifier]));
+}
+
- (void)addCardToGoogleWallet:
#ifdef RCT_NEW_ARCH_ENABLED
(JS::NativeWallet::AndroidCardData &)cardData
diff --git a/ios/WalletManager.swift b/ios/WalletManager.swift
index 6b3e4c35..d5134a94 100644
--- a/ios/WalletManager.swift
+++ b/ios/WalletManager.swift
@@ -21,6 +21,8 @@ open class WalletManager: UIViewController {
private var addPaymentPassCompletionHandler: (CompletionHandler)?
private var addPassHandler: ((PKAddPaymentPassRequest) -> Void)?
+
+ private var addPassTargetDevice: String?
@objc public var packageName = "react-native-wallet"
@@ -111,10 +113,23 @@ open class WalletManager: UIViewController {
configuration.cardholderName = card.cardHolderName
configuration.primaryAccountSuffix = card.lastDigits
configuration.localizedDescription = String(card.cardDescription)
+ let primaryAccountIdentifier = card.primaryAccountIdentifier.map {
+ resolvedPrimaryAccountIdentifier($0, lastDigits: card.lastDigits)
+ }
+ if let primaryAccountIdentifier = primaryAccountIdentifier {
+ configuration.primaryAccountIdentifier = primaryAccountIdentifier
+ }
+
+ let targetDevice = provisioningTargetDevice(for: primaryAccountIdentifier)
guard let enrollViewController = PKAddPaymentPassViewController(requestConfiguration: configuration, delegate: self) else {
+ let alreadyOnEveryDevice = primaryAccountIdentifier.map {
+ !passLibrary.canAddSecureElementPass(primaryAccountIdentifier: $0)
+ } ?? false
completion(.error, [
- "errorMessage": "InApp enrollment controller configuration fails"
+ "errorMessage": alreadyOnEveryDevice
+ ? "This card has already been added to all available devices."
+ : "InApp enrollment controller configuration fails"
])
return
}
@@ -134,6 +149,7 @@ open class WalletManager: UIViewController {
self.presentAddPaymentPassCompletionHandler = nil
self.addPaymentPassCompletionHandler = nil
self.addPassHandler = nil
+ self.addPassTargetDevice = nil
let errorResponse: NSDictionary = [
"errorMessage": "EnrollViewController is no longer presented."
@@ -150,6 +166,7 @@ open class WalletManager: UIViewController {
self.presentAddPaymentPassCompletionHandler = completion
self.addPassViewController = enrollViewController
+ self.addPassTargetDevice = targetDevice
presentingViewController.present(enrollViewController, animated: true, completion: nil)
}
}
@@ -212,6 +229,65 @@ open class WalletManager: UIViewController {
return pass.primaryAccountIdentifier == identifier as String
}
}
+
+ @objc public func canAddCardToAppleWallet(primaryAccountIdentifier: NSString) -> Bool {
+ return passLibrary.canAddSecureElementPass(primaryAccountIdentifier: primaryAccountIdentifier as String)
+ }
+
+ private func provisioningTargetDevice(for primaryAccountIdentifier: String?) -> String {
+ guard let primaryAccountIdentifier = primaryAccountIdentifier else {
+ return "unknown"
+ }
+
+ let onPhone = hasProvisionedPass(identifier: primaryAccountIdentifier, in: passLibrary.passes(of: .secureElement))
+ let onWatch = hasProvisionedPass(identifier: primaryAccountIdentifier, in: passLibrary.remoteSecureElementPasses)
+
+ if onPhone && !onWatch {
+ return "watch"
+ }
+ if onWatch && !onPhone {
+ return "phone"
+ }
+ return "unknown"
+ }
+
+ private func resolvedPrimaryAccountIdentifier(_ identifier: String, lastDigits: String) -> String {
+ let passes = allSecureElementPasses()
+ if passes.contains(where: { $0.primaryAccountIdentifier == identifier }) {
+ return identifier
+ }
+
+ let suffixMatches = passes.filter { $0.primaryAccountNumberSuffix.hasSuffix(lastDigits) }
+ let identifiers = Set(suffixMatches.map(\.primaryAccountIdentifier))
+ if identifiers.count == 1, let resolvedIdentifier = identifiers.first {
+ return resolvedIdentifier
+ }
+ return identifier
+ }
+
+ private func allSecureElementPasses() -> [PKSecureElementPass] {
+ let localPasses = passLibrary.passes(of: .secureElement)
+ .filter { !$0.isRemotePass }
+ .compactMap(\.secureElementPass)
+ return localPasses + passLibrary.remoteSecureElementPasses
+ }
+
+ private func hasProvisionedPass(identifier: String, in passes: [PKPass]) -> Bool {
+ passes.contains { pass in
+ guard !pass.isRemotePass, let securePass = pass.secureElementPass else {
+ return false
+ }
+ return isProvisioned(securePass, identifier: identifier)
+ }
+ }
+
+ private func hasProvisionedPass(identifier: String, in passes: [PKSecureElementPass]) -> Bool {
+ passes.contains { isProvisioned($0, identifier: identifier) }
+ }
+
+ private func isProvisioned(_ pass: PKSecureElementPass, identifier: String) -> Bool {
+ pass.primaryAccountIdentifier == identifier && pass.passActivationState != .deactivated
+ }
private func isPassKitAvailable() -> Bool {
return PKAddPaymentPassViewController.canAddPaymentPass()
@@ -261,7 +337,7 @@ extension WalletManager: PKAddPaymentPassViewControllerDelegate {
let stringCertificates = certificates.map {
$0.base64EncodedString() as NSString
}
- let reqestCardData = AddPassResponse(status: .completed, nonce: stringNonce, nonceSignature: stringNonceSignature, certificates: stringCertificates)
+ let reqestCardData = AddPassResponse(status: .completed, nonce: stringNonce, nonceSignature: stringNonceSignature, certificates: stringCertificates, deviceType: addPassTargetDevice as NSString?)
self.addPassHandler = handler
// Retry the JS issuer callback if the user tries again to add a payment pass
@@ -293,6 +369,7 @@ extension WalletManager: PKAddPaymentPassViewControllerDelegate {
presentAddPaymentPassCompletionHandler = nil
addPaymentPassCompletionHandler = nil
addPassHandler = nil
+ addPassTargetDevice = nil
let errorMessage = error?.localizedDescription ?? ""
diff --git a/ios/stucts/AddPassResponse.swift b/ios/stucts/AddPassResponse.swift
index 1df45b4b..327afea8 100644
--- a/ios/stucts/AddPassResponse.swift
+++ b/ios/stucts/AddPassResponse.swift
@@ -5,12 +5,25 @@ struct AddPassResponse {
let nonce: NSString?
let nonceSignature: NSString?
let certificates: [NSString]?
+ let deviceType: NSString?
+
+ init(status: OperationResult, nonce: NSString?, nonceSignature: NSString?, certificates: [NSString]?, deviceType: NSString? = nil) {
+ self.status = status
+ self.nonce = nonce
+ self.nonceSignature = nonceSignature
+ self.certificates = certificates
+ self.deviceType = deviceType
+ }
func toNSDictionary() -> NSDictionary {
if status.rawValue > 0 {
- return [
+ var response: [String: Any] = [
"status": status.rawValue,
]
+ if let deviceType = deviceType {
+ response["deviceType"] = deviceType
+ }
+ return response as NSDictionary
}
guard let nonce = nonce,
@@ -19,12 +32,16 @@ struct AddPassResponse {
return [:]
}
- return [
+ var response: [String: Any] = [
"status": status.rawValue,
"nonce": nonce,
"nonceSignature": nonceSignature,
- "certificates": certificates
+ "certificates": certificates,
]
+ if let deviceType = deviceType {
+ response["deviceType"] = deviceType
+ }
+ return response as NSDictionary
}
}
diff --git a/ios/stucts/CardInfo.swift b/ios/stucts/CardInfo.swift
index c02a3c7c..13499153 100644
--- a/ios/stucts/CardInfo.swift
+++ b/ios/stucts/CardInfo.swift
@@ -5,6 +5,7 @@ struct CardInfo {
let cardHolderName: String
let lastDigits: String
let cardDescription: String
+ let primaryAccountIdentifier: String?
init(cardData: NSDictionary) throws {
guard let networkString = cardData["network"] as? String, !networkString.isEmpty,
@@ -19,6 +20,11 @@ struct CardInfo {
self.cardHolderName = cardHolderName
self.lastDigits = lastDigits
self.cardDescription = cardDescription
+ if let identifier = cardData["primaryAccountIdentifier"] as? String, !identifier.isEmpty {
+ self.primaryAccountIdentifier = identifier
+ } else {
+ self.primaryAccountIdentifier = nil
+ }
}
private static func getNetwork(from identifier: String) -> PKPaymentNetwork? {
diff --git a/src/NativeWallet.ts b/src/NativeWallet.ts
index f15df6e9..bc2abffc 100644
--- a/src/NativeWallet.ts
+++ b/src/NativeWallet.ts
@@ -41,6 +41,7 @@ type IOSCardData = {
cardHolderName: string;
lastDigits: string;
cardDescription: string;
+ primaryAccountIdentifier?: string;
};
type onCardActivatedPayload = {
@@ -53,8 +54,11 @@ type IOSAddPaymentPassData = {
nonce: string;
nonceSignature: string;
certificates: string[];
+ deviceType?: string;
};
+type IOSDeviceType = 'phone' | 'watch' | 'unknown';
+
type IOSEncryptPayload = {
encryptedPassData: string;
activationData: string;
@@ -80,6 +84,7 @@ export interface Spec extends TurboModule {
listTokens(): Promise;
IOSPresentAddPaymentPassView(cardData: IOSCardData): Promise;
IOSHandleAddPaymentPassResponse(payload: IOSEncryptPayload): Promise;
+ canAddCardToAppleWallet(primaryAccountIdentifier: string): Promise;
addListener: (eventType: string) => void;
removeListeners: (count: number) => void;
}
@@ -109,6 +114,7 @@ export type {
onCardActivatedPayload,
Platform,
IOSAddPaymentPassData,
+ IOSDeviceType,
IOSEncryptPayload,
TokenizationStatus,
TokenInfo,
diff --git a/src/index.tsx b/src/index.tsx
index d1801ffc..2cc3605d 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -12,6 +12,7 @@ import type {
AndroidWalletData,
onCardActivatedPayload,
IOSAddPaymentPassData,
+ IOSDeviceType,
TokenInfo,
} from './NativeWallet';
import {getCardState, getTokenizationStatus} from './utils';
@@ -131,9 +132,32 @@ async function listTokens(): Promise {
return Wallet.listTokens();
}
+function toIOSDeviceType(deviceType: string | undefined): IOSDeviceType {
+ switch (deviceType) {
+ case 'phone':
+ case 'watch':
+ case 'unknown':
+ return deviceType;
+ default:
+ return 'unknown';
+ }
+}
+
+async function canAddCardToAppleWallet(primaryAccountIdentifier: string): Promise {
+ if (Platform.OS === 'android') {
+ throw new Error('canAddCardToAppleWallet is not available on Android');
+ }
+
+ if (!Wallet) {
+ return getModuleLinkingRejection();
+ }
+
+ return Wallet.canAddCardToAppleWallet(primaryAccountIdentifier);
+}
+
async function addCardToAppleWallet(
cardData: IOSCardData,
- issuerEncryptPayloadCallback: (nonce: string, nonceSignature: string, certificate: string[]) => Promise,
+ issuerEncryptPayloadCallback: (nonce: string, nonceSignature: string, certificate: string[], deviceType: IOSDeviceType) => Promise,
): Promise {
if (Platform.OS === 'android') {
throw new Error('addCardToAppleWallet is not available on Android');
@@ -151,7 +175,7 @@ async function addCardToAppleWallet(
}
async function addPaymentPassToWallet(paymentPassData: IOSAddPaymentPassData): Promise {
- const responseData = await issuerEncryptPayloadCallback(paymentPassData.nonce, paymentPassData.nonceSignature, paymentPassData.certificates);
+ const responseData = await issuerEncryptPayloadCallback(paymentPassData.nonce, paymentPassData.nonceSignature, paymentPassData.certificates, toIOSDeviceType(paymentPassData.deviceType));
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
@@ -164,7 +188,7 @@ async function addCardToAppleWallet(
return getTokenizationStatus(status);
}
-export type {AndroidCardData, AndroidWalletData, CardStatus, IOSEncryptPayload, IOSCardData, IOSAddPaymentPassData, onCardActivatedPayload, TokenizationStatus, TokenInfo};
+export type {AndroidCardData, AndroidWalletData, CardStatus, IOSEncryptPayload, IOSCardData, IOSAddPaymentPassData, IOSDeviceType, onCardActivatedPayload, TokenizationStatus, TokenInfo};
export {
AddToWalletButton,
checkWalletAvailability,
@@ -174,6 +198,7 @@ export {
addCardToGoogleWallet,
resumeAddCardToGoogleWallet,
listTokens,
+ canAddCardToAppleWallet,
addCardToAppleWallet,
addListener,
removeListener,