From 28a610e09b15d253690496c6ba775d19e874a64e Mon Sep 17 00:00:00 2001 From: "os-botify[bot]" <140437396+os-botify[bot]@users.noreply.github.com> Date: Mon, 25 Aug 2025 11:23:23 +0000 Subject: [PATCH 1/6] Update package.json version to 0.1.7 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ff7868d..be3191c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@expensify/react-native-wallet", - "version": "0.1.6", + "version": "0.1.7", "description": "A React Native module designed for seamless integration of Card Push Provisioning into Apple Wallet and Google Wallet, enabling easy and secure addition of payment cards directly from your application.", "source": "./src/index.tsx", "main": "lib/commonjs/index", From 518d4ed8b7f393cf0aca923b21643216a7becd13 Mon Sep 17 00:00:00 2001 From: Darwin Bracamonte Date: Fri, 29 Aug 2025 12:44:27 -0300 Subject: [PATCH 2/6] feat: add Google Wallet token management methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - add resumeAddCardToGoogleWallet() method for resuming card provisioning using existing token reference ID - add listTokens() method to retrieve all tokens stored in Google Wallet - add AndroidResumeCardData and TokenInfo types for new functionality - update README.md with documentation for new methods These methods provide better token lifecycle management and support for existing card tokens in Google Wallet integration. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- README.md | 6 +- .../java/com/expensify/wallet/WalletModule.kt | 68 +++++++++++++++++++ .../expensify/wallet/NativeWalletSpec.java | 12 ++++ src/NativeWallet.ts | 30 +++++++- src/index.tsx | 48 ++++++++++++- 5 files changed, 160 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 46c9994..31d12e4 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ Here you can find data elements used in the library, essential to work with Goog - **Ephemeral Public Key** - a key used by elliptic curve cryptography (ECC) (Base 64 encoded). # API Reference -The library offers five 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 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. ## Functions @@ -134,6 +134,8 @@ The library offers five functions for seamless integration and use of the Apple | **getCardStatusBySuffix** | Retrieves the current status of a card in the wallet. | `lastDigits: string`
(The last few digits of the card number) | `CardStatus` | âś… | âś… | | **getCardStatusByIdentifier** | Returns the state of a card based on a platform-specific identifier. On Android, it's `Token Reference ID` and on iOS, it's `Primary Account Identifier`. | `identifier: string`,
`tsp: string` | `CardStatus` | ✅ | ✅ | | **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` | ✅ | ❌ | @@ -143,11 +145,13 @@ The library offers five functions for seamless integration and use of the Apple |------|-------------|--------| | **AndroidWalletData** | Specific information for Android devices required for wallet transactions. | `deviceID: string`,
`walletAccountID: string` | | **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` | | **onCardActivatedPayload** | Data used by listener to notice when a card’s status changes. | `tokenId: string`,
`actionStatus: '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` | | **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. | `tokenReferenceId: string`,
`fpanLastFour: string`,
`tokenState: number` | ## Card Status diff --git a/android/src/main/java/com/expensify/wallet/WalletModule.kt b/android/src/main/java/com/expensify/wallet/WalletModule.kt index 76035c8..cd2ee21 100644 --- a/android/src/main/java/com/expensify/wallet/WalletModule.kt +++ b/android/src/main/java/com/expensify/wallet/WalletModule.kt @@ -208,6 +208,60 @@ class WalletModule internal constructor(context: ReactApplicationContext) : } } + @ReactMethod + override fun resumeAddCardToGoogleWallet(data: ReadableMap, promise: Promise) { + try { + val tokenReferenceId = data.getString("tokenReferenceId") + ?: return promise.reject(E_INVALID_DATA, "Missing tokenReferenceId") + + val network = data.getString("network") + ?: return promise.reject(E_INVALID_DATA, "Missing network") + + val cardNetwork = getCardNetwork(network) + val tokenServiceProvider = getTokenServiceProvider(network) + val displayName = getDisplayName(data, network) + + pendingPushTokenizePromise = promise + + tapAndPayClient.tokenize( + activity, + tokenReferenceId, + tokenServiceProvider, + displayName, + cardNetwork, + REQUEST_CODE_PUSH_TOKENIZE + ) + } catch (e: java.lang.Exception) { + promise.reject(e) + } + } + + @ReactMethod + override fun listTokens(promise: Promise) { + tapAndPayClient.listTokens() + .addOnCompleteListener { task -> + if (!task.isSuccessful || task.result == null) { + promise.resolve(Arguments.createArray()) + return@addOnCompleteListener + } + + val tokensArray = Arguments.createArray() + task.result.forEach { tokenInfo -> + val tokenData = Arguments.createMap().apply { + putString("tokenReferenceId", tokenInfo.issuerTokenId) + putString("fpanLastFour", tokenInfo.fpanLastFour) + putInt("tokenState", tokenInfo.tokenState) + } + tokensArray.pushMap(tokenData) + } + + promise.resolve(tokensArray) + } + .addOnFailureListener { e -> + promise.reject(E_OPERATION_FAILED, "listTokens: ${e.localizedMessage}") + } + } + private fun getWalletId(promise: Promise) { tapAndPayClient.activeWalletId.addOnCompleteListener { task -> if (task.isSuccessful) { @@ -273,6 +327,20 @@ class WalletModule internal constructor(context: ReactApplicationContext) : getHardwareId(promise) } + private fun getDisplayName(data: ReadableMap, network: String): String { + data.getString("cardHolderName")?.let { name -> + if (name.isNotEmpty()) return name + } + + data.getString("lastDigits")?.let { digits -> + if (digits.isNotEmpty()) { + return "${network.uppercase(Locale.getDefault())} Card *$digits" + } + } + + return "${network.uppercase(Locale.getDefault())} Card" + } + private fun sendEvent(reactContext: ReactContext, eventName: String, params: WritableMap?) { reactContext .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) diff --git a/android/src/paper/java/com/expensify/wallet/NativeWalletSpec.java b/android/src/paper/java/com/expensify/wallet/NativeWalletSpec.java index 4221a52..999f0cb 100644 --- a/android/src/paper/java/com/expensify/wallet/NativeWalletSpec.java +++ b/android/src/paper/java/com/expensify/wallet/NativeWalletSpec.java @@ -37,6 +37,10 @@ public NativeWalletSpec(ReactApplicationContext reactContext) { @DoNotStrip public abstract void checkWalletAvailability(Promise promise); + @ReactMethod + @DoNotStrip + public abstract void ensureGoogleWalletInitialized(Promise promise); + @ReactMethod @DoNotStrip public abstract void getSecureWalletInfo(Promise promise); @@ -53,6 +57,14 @@ public NativeWalletSpec(ReactApplicationContext reactContext) { @DoNotStrip public abstract void addCardToGoogleWallet(ReadableMap cardData, Promise promise); + @ReactMethod + @DoNotStrip + public abstract void resumeAddCardToGoogleWallet(ReadableMap cardData, Promise promise); + + @ReactMethod + @DoNotStrip + public abstract void listTokens(Promise promise); + @ReactMethod @DoNotStrip public abstract void IOSPresentAddPaymentPassView(ReadableMap cardData, Promise promise); diff --git a/src/NativeWallet.ts b/src/NativeWallet.ts index ae9bd7f..09aaad0 100644 --- a/src/NativeWallet.ts +++ b/src/NativeWallet.ts @@ -29,6 +29,13 @@ type AndroidCardData = { userAddress: UserAddress; }; +type AndroidResumeCardData = { + network: string; + tokenReferenceId: string; + cardHolderName?: string; + lastDigits?: string; +}; + type IOSCardData = { network: string; cardHolderName: string; @@ -56,6 +63,12 @@ type IOSEncryptPayload = { type TokenizationStatus = 'canceled' | 'success' | 'error'; +type TokenInfo = { + tokenReferenceId: string; + fpanLastFour: string; + tokenState: number; +}; + export interface Spec extends TurboModule { checkWalletAvailability(): Promise; ensureGoogleWalletInitialized(): Promise; @@ -63,6 +76,8 @@ export interface Spec extends TurboModule { getCardStatusBySuffix(last4Digits: string): Promise; getCardStatusByIdentifier(identifier: string, tsp: string): Promise; addCardToGoogleWallet(cardData: AndroidCardData): Promise; + resumeAddCardToGoogleWallet(cardData: AndroidResumeCardData): Promise; + listTokens(): Promise; IOSPresentAddPaymentPassView(cardData: IOSCardData): Promise; IOSHandleAddPaymentPassResponse(payload: IOSEncryptPayload): Promise; addListener: (eventType: string) => void; @@ -84,4 +99,17 @@ try { } export default Wallet; export {PACKAGE_NAME}; -export type {AndroidCardData, IOSCardData, AndroidWalletData, CardStatus, UserAddress, onCardActivatedPayload, Platform, IOSAddPaymentPassData, IOSEncryptPayload, TokenizationStatus}; +export type { + AndroidCardData, + AndroidResumeCardData, + IOSCardData, + AndroidWalletData, + CardStatus, + UserAddress, + onCardActivatedPayload, + Platform, + IOSAddPaymentPassData, + IOSEncryptPayload, + TokenizationStatus, + TokenInfo, +}; diff --git a/src/index.tsx b/src/index.tsx index 260e17e..418bc7b 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2,7 +2,18 @@ import {NativeEventEmitter, Platform} from 'react-native'; import type {EmitterSubscription} from 'react-native'; import Wallet, {PACKAGE_NAME} from './NativeWallet'; -import type {TokenizationStatus, AndroidCardData, CardStatus, IOSCardData, IOSEncryptPayload, AndroidWalletData, onCardActivatedPayload, IOSAddPaymentPassData} from './NativeWallet'; +import type { + TokenizationStatus, + AndroidCardData, + AndroidResumeCardData, + CardStatus, + IOSCardData, + IOSEncryptPayload, + AndroidWalletData, + onCardActivatedPayload, + IOSAddPaymentPassData, + TokenInfo, +} from './NativeWallet'; import {getCardState, getTokenizationStatus} from './utils'; import AddToWalletButton from './AddToWalletButton'; @@ -83,6 +94,37 @@ async function addCardToGoogleWallet(cardData: AndroidCardData): Promise { + if (Platform.OS === 'ios') { + throw new Error('resumeAddCardToGoogleWallet is not available on iOS'); + } + + if (!Wallet) { + return getModuleLinkingRejection(); + } + const isWalletInitialized = await Wallet.ensureGoogleWalletInitialized(); + if (!isWalletInitialized) { + throw new Error('Wallet could not be initialized'); + } + const tokenizationStatus = await Wallet.resumeAddCardToGoogleWallet(cardData); + return getTokenizationStatus(tokenizationStatus); +} + +async function listTokens(): Promise { + if (Platform.OS === 'ios') { + throw new Error('listTokens is not available on iOS'); + } + + if (!Wallet) { + return getModuleLinkingRejection(); + } + const isWalletInitialized = await Wallet.ensureGoogleWalletInitialized(); + if (!isWalletInitialized) { + throw new Error('Wallet could not be initialized'); + } + return Wallet.listTokens(); +} + async function addCardToAppleWallet( cardData: IOSCardData, issuerEncryptPayloadCallback: (nonce: string, nonceSignature: string, certificate: string[]) => Promise, @@ -110,7 +152,7 @@ async function addCardToAppleWallet( return getTokenizationStatus(status); } -export type {AndroidCardData, AndroidWalletData, CardStatus, IOSEncryptPayload, IOSCardData, IOSAddPaymentPassData, onCardActivatedPayload, TokenizationStatus}; +export type {AndroidCardData, AndroidWalletData, CardStatus, IOSEncryptPayload, IOSCardData, IOSAddPaymentPassData, onCardActivatedPayload, TokenizationStatus, TokenInfo}; export { AddToWalletButton, checkWalletAvailability, @@ -118,6 +160,8 @@ export { getCardStatusBySuffix, getCardStatusByIdentifier, addCardToGoogleWallet, + resumeAddCardToGoogleWallet, + listTokens, addCardToAppleWallet, addListener, removeListener, From 9392cbac0f452c009310d3608660d728ecfb80e8 Mon Sep 17 00:00:00 2001 From: Darwin Bracamonte Date: Fri, 29 Aug 2025 15:27:52 -0300 Subject: [PATCH 3/6] feat(android): add resumeAddCardToGoogleWallet example code --- example/src/App.tsx | 4 ++-- example/src/CONST.ts | 10 +++++++++- example/src/walletUtils.ts | 23 ++++++++++++++++++++++- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/example/src/App.tsx b/example/src/App.tsx index e8dad68..1c85f45 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -64,7 +64,7 @@ export default function App() { }, []); const handleAddCardToWallet = useCallback(() => { - addCardToWallet() + addCardToWallet(cardStatus) .then(status => { setAddCardStatus(status); }) @@ -72,7 +72,7 @@ export default function App() { console.error(e); setAddCardStatus('failed'); }); - }, []); + }, [cardStatus]); const walletSecureInfo = useMemo( () => getWalletInfoTextValue(walletData), diff --git a/example/src/CONST.ts b/example/src/CONST.ts index ffb1702..335836a 100644 --- a/example/src/CONST.ts +++ b/example/src/CONST.ts @@ -3,6 +3,7 @@ import type { UserAddress, IOSEncryptPayload, IOSCardData, + AndroidResumeCardData, } from '../../src/NativeWallet'; const dummyAddress: UserAddress = { @@ -24,6 +25,13 @@ const AndroidDummyCardData: AndroidCardData = { userAddress: dummyAddress, }; +const AndroidDummyResumeCardData: AndroidResumeCardData = { + network: 'VISA', + cardHolderName: 'John Doe', + lastDigits: '4321', + tokenReferenceId: '', +}; + const IOSDummyCardData: IOSCardData = { network: 'VISA', cardHolderName: 'John Doe', @@ -38,4 +46,4 @@ const IOSDummyEncryptPayload: IOSEncryptPayload = { ephemeralPublicKey: 'ZXBoZW1lcmFsUHVibGljS2V5MTIz', }; -export {AndroidDummyCardData, IOSDummyCardData, IOSDummyEncryptPayload}; +export {AndroidDummyCardData, AndroidDummyResumeCardData, IOSDummyCardData, IOSDummyEncryptPayload}; diff --git a/example/src/walletUtils.ts b/example/src/walletUtils.ts index a043761..3d0d5b9 100644 --- a/example/src/walletUtils.ts +++ b/example/src/walletUtils.ts @@ -1,6 +1,9 @@ import { addCardToAppleWallet, addCardToGoogleWallet, + listTokens, + resumeAddCardToGoogleWallet, + type CardStatus, } from '@expensify/react-native-wallet'; import * as CONST from './CONST'; import {Platform} from 'react-native'; @@ -15,8 +18,26 @@ function issuerEncryptPayloadCallback( return Promise.resolve(CONST.IOSDummyEncryptPayload); } -async function addCardToWallet() { +async function addCardToWallet(cardStatus?: CardStatus) { if (Platform.OS === 'android') { + if (cardStatus === 'requireActivation') { + const tokens = await listTokens(); + const existingToken = tokens.find( + token => + token.fpanLastFour === CONST.AndroidDummyResumeCardData.lastDigits, + ); + + if (!existingToken) { + throw new Error( + `No se encontrĂł el token para la tarjeta terminada en ${CONST.AndroidDummyResumeCardData.lastDigits}`, + ); + } + + return await resumeAddCardToGoogleWallet({ + ...CONST.AndroidDummyResumeCardData, + tokenReferenceId: existingToken.tokenReferenceId, + }); + } return addCardToGoogleWallet(CONST.AndroidDummyCardData); } else { return addCardToAppleWallet( From e29f876755f51edce950337b83a0ccce3f968d85 Mon Sep 17 00:00:00 2001 From: Darwin Bracamonte Date: Mon, 1 Sep 2025 12:20:23 -0300 Subject: [PATCH 4/6] feat(types): rename TokenInfo fields for cross-platform compatibility --- README.md | 4 ++-- .../src/main/java/com/expensify/wallet/WalletModule.kt | 10 +++++----- example/src/CONST.ts | 2 +- example/src/walletUtils.ts | 6 +++--- src/NativeWallet.ts | 6 +++--- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 31d12e4..f393fee 100644 --- a/README.md +++ b/README.md @@ -145,13 +145,13 @@ The library offers seven functions for seamless integration and use of the Apple |------|-------------|--------| | **AndroidWalletData** | Specific information for Android devices required for wallet transactions. | `deviceID: string`,
`walletAccountID: string` | | **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` | +| **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` | | **onCardActivatedPayload** | Data used by listener to notice when a card’s status changes. | `tokenId: string`,
`actionStatus: '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` | | **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. | `tokenReferenceId: string`,
`fpanLastFour: string`,
`tokenState: number` | +| **TokenInfo** | Information about a token stored in Google Wallet. | `identifier: string`,
`lastDigits: string`,
`tokenState: number` | ## Card Status diff --git a/android/src/main/java/com/expensify/wallet/WalletModule.kt b/android/src/main/java/com/expensify/wallet/WalletModule.kt index cd2ee21..c5db53f 100644 --- a/android/src/main/java/com/expensify/wallet/WalletModule.kt +++ b/android/src/main/java/com/expensify/wallet/WalletModule.kt @@ -211,8 +211,8 @@ class WalletModule internal constructor(context: ReactApplicationContext) : @ReactMethod override fun resumeAddCardToGoogleWallet(data: ReadableMap, promise: Promise) { try { - val tokenReferenceId = data.getString("tokenReferenceId") - ?: return promise.reject(E_INVALID_DATA, "Missing tokenReferenceId") + val tokenReferenceID = data.getString("tokenReferenceID") + ?: return promise.reject(E_INVALID_DATA, "Missing tokenReferenceID") val network = data.getString("network") ?: return promise.reject(E_INVALID_DATA, "Missing network") @@ -225,7 +225,7 @@ class WalletModule internal constructor(context: ReactApplicationContext) : tapAndPayClient.tokenize( activity, - tokenReferenceId, + tokenReferenceID, tokenServiceProvider, displayName, cardNetwork, @@ -248,8 +248,8 @@ class WalletModule internal constructor(context: ReactApplicationContext) : val tokensArray = Arguments.createArray() task.result.forEach { tokenInfo -> val tokenData = Arguments.createMap().apply { - putString("tokenReferenceId", tokenInfo.issuerTokenId) - putString("fpanLastFour", tokenInfo.fpanLastFour) + putString("identifier", tokenInfo.issuerTokenId) + putString("lastDigits", tokenInfo.fpanLastFour) putInt("tokenState", tokenInfo.tokenState) } tokensArray.pushMap(tokenData) diff --git a/example/src/CONST.ts b/example/src/CONST.ts index 335836a..34bb1a7 100644 --- a/example/src/CONST.ts +++ b/example/src/CONST.ts @@ -29,7 +29,7 @@ const AndroidDummyResumeCardData: AndroidResumeCardData = { network: 'VISA', cardHolderName: 'John Doe', lastDigits: '4321', - tokenReferenceId: '', + tokenReferenceID: '', }; const IOSDummyCardData: IOSCardData = { diff --git a/example/src/walletUtils.ts b/example/src/walletUtils.ts index 3d0d5b9..e0ce8e1 100644 --- a/example/src/walletUtils.ts +++ b/example/src/walletUtils.ts @@ -24,18 +24,18 @@ async function addCardToWallet(cardStatus?: CardStatus) { const tokens = await listTokens(); const existingToken = tokens.find( token => - token.fpanLastFour === CONST.AndroidDummyResumeCardData.lastDigits, + token.lastDigits === CONST.AndroidDummyResumeCardData.lastDigits, ); if (!existingToken) { throw new Error( - `No se encontrĂł el token para la tarjeta terminada en ${CONST.AndroidDummyResumeCardData.lastDigits}`, + `Token not found for card ending with ${CONST.AndroidDummyResumeCardData.lastDigits}`, ); } return await resumeAddCardToGoogleWallet({ ...CONST.AndroidDummyResumeCardData, - tokenReferenceId: existingToken.tokenReferenceId, + tokenReferenceID: existingToken.identifier, }); } return addCardToGoogleWallet(CONST.AndroidDummyCardData); diff --git a/src/NativeWallet.ts b/src/NativeWallet.ts index 09aaad0..cf8a47d 100644 --- a/src/NativeWallet.ts +++ b/src/NativeWallet.ts @@ -31,7 +31,7 @@ type AndroidCardData = { type AndroidResumeCardData = { network: string; - tokenReferenceId: string; + tokenReferenceID: string; cardHolderName?: string; lastDigits?: string; }; @@ -64,8 +64,8 @@ type IOSEncryptPayload = { type TokenizationStatus = 'canceled' | 'success' | 'error'; type TokenInfo = { - tokenReferenceId: string; - fpanLastFour: string; + identifier: string; + lastDigits: string; tokenState: number; }; From e0afe2666427613d982b3d2bada70fecf0cfd93d Mon Sep 17 00:00:00 2001 From: Darwin Bracamonte Date: Mon, 1 Sep 2025 12:41:36 -0300 Subject: [PATCH 5/6] feat: add stub for listTokens on iOS, returning an empty array --- src/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.tsx b/src/index.tsx index 418bc7b..1f94756 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -112,7 +112,7 @@ async function resumeAddCardToGoogleWallet(cardData: AndroidResumeCardData): Pro async function listTokens(): Promise { if (Platform.OS === 'ios') { - throw new Error('listTokens is not available on iOS'); + return Promise.resolve([]); } if (!Wallet) { From ec9a64b1a0cf2435e0ec2619829218368cb317cf Mon Sep 17 00:00:00 2001 From: Darwin Bracamonte Date: Thu, 18 Sep 2025 10:13:28 -0300 Subject: [PATCH 6/6] feat: add getDisplayName to addCardToGoogleWallet --- android/src/main/java/com/expensify/wallet/WalletModule.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/com/expensify/wallet/WalletModule.kt b/android/src/main/java/com/expensify/wallet/WalletModule.kt index c5db53f..1ff25aa 100644 --- a/android/src/main/java/com/expensify/wallet/WalletModule.kt +++ b/android/src/main/java/com/expensify/wallet/WalletModule.kt @@ -189,13 +189,14 @@ class WalletModule internal constructor(context: ReactApplicationContext) : val cardData = data.toCardData() ?: return promise.reject(E_INVALID_DATA, "Insufficient data") val cardNetwork = getCardNetwork(cardData.network) val tokenServiceProvider = getTokenServiceProvider(cardData.network) + val displayName = getDisplayName(data, cardData.network) pendingPushTokenizePromise = promise val pushTokenizeRequest = PushTokenizeRequest.Builder() .setOpaquePaymentCard(cardData.opaquePaymentCard.toByteArray(Charset.forName("UTF-8"))) .setNetwork(cardNetwork) .setTokenServiceProvider(tokenServiceProvider) - .setDisplayName(cardData.cardHolderName) + .setDisplayName(displayName) .setLastDigits(cardData.lastDigits) .setUserAddress(cardData.userAddress) .build() @@ -220,7 +221,6 @@ class WalletModule internal constructor(context: ReactApplicationContext) : val cardNetwork = getCardNetwork(network) val tokenServiceProvider = getTokenServiceProvider(network) val displayName = getDisplayName(data, network) - pendingPushTokenizePromise = promise tapAndPayClient.tokenize(