# Use digital wallets with Issuing
Learn how to use Issuing to add cards to digital wallets.
Issuing allows users to add cards to digital wallets like Apple Pay and Google Pay.
You can’t test this feature in a sandbox, because digital wallet tokens are only available in live mode. To test using digital wallet tokens, you must be approved for live use cases and use real cards. Stripe supports the addition of cards through two methods:
1. **Manual Provisioning:** cardholders enter their card details into a phone’s wallet application to add it to their digital wallets.
2. **Push Provisioning:** mobile applications allow users to add cards to their digital wallets straight from the app.
When a card is added to a digital wallet, a tokenized representation of that card is created. Network tokens are managed separately from cards. For more information about network tokens and how they work, see [Token Management](https://docs.stripe.com/issuing/controls/token-management.md).
## Manual Provisioning
Cardholders can add Stripe Issuing [virtual cards](https://docs.stripe.com/issuing/cards/virtual.md) and [physical cards](https://docs.stripe.com/issuing/cards/physical.md) to their Apple Pay, Google Pay, and Samsung Pay wallets through manual provisioning.
To do so, cardholders open the wallet app on their phone and enter their card details. Stripe then sends a 6-digit verification code to the `phone_number` or `email` of the cardholder associated with the card.
A `card not supported` error displays if neither field is set on the cardholder when the card was provisioned.
No code is required to implement manual provisioning, but the process to set it up can vary depending on the digital wallet provider and the country you’re based in:
### US
Apple Pay wallets require approval from Apple. Check your [digital wallets settings](https://dashboard.stripe.com/settings/issuing/digital-wallets) to view the status of Apple Pay in your account. You might need to submit an application before using Apple Pay. After the application is submitted, approval can take 1-2 weeks.
Google Pay and Samsung Pay have no additional required steps.
### EU and UK
Digital wallet integrations require additional approval from the Stripe partnership team. Get in touch with your account representative or [contact Stripe](https://stripe.com/contact/embedded-finance) for more information.
Apple Pay wallets require additional approval. Check your [digital wallets settings](https://dashboard.stripe.com/settings/issuing/digital-wallets) to view the status of Apple Pay in your account. You might need to submit an application before using Apple Pay.
## Push provisioning
Push provisioning allows cardholders to add a Stripe Issuing cards to their digital wallets directly from your app by pressing an “add to wallet” button like the ones shown below.
Users must first complete manual provisioning steps to enable push provisioning in the US. In addition to manual provisioning approval, push provisioning requires you to integrate with the Stripe SDK.
This requires both approval processes through Stripe and code integration with the Stripe SDK for each platform you wish to support push provisioning on. Platform approvals cascade down to all of their connected accounts.
Samsung Pay push provisioning isn’t supported with our SDKs.
# React Native


## Request access
You must get access to [manual provisioning](https://docs.stripe.com/issuing/cards/digital-wallets.md?platform=ios#manual-provisioning) before you can request push provisioning.
### Requesting access for iOS
Push provisioning requires a special entitlement from Apple called `com.apple.developer.payment-pass-provisioning`. You can request it by emailing [support-issuing@stripe.com](mailto:support-issuing@stripe.com). In your email, include your:
- **Card network**: Visa or MasterCard.
- **Card name**: The name of the card displayed in the wallet.
- **App name**: Your app’s name.
- **Developer team ID**: Found in your Apple Developer account settings under [membership](https://developer.apple.com/account/#/membership).
- **ADAM ID**: Your app’s unique numeric ID. Found in [App Store Connect](https://appstoreconnect.apple.com), or in the App Store link to your app (for example, `https://apps.apple.com/app/id123456789`).
- **Bundle ID**: Your app’s bundle identifier, also found in App Store Connect (for example, `com.example.yourapp`).
### Requesting access for Android
> This guide reflects Google Wallet’s Unified Push Provisioning (UPP) flow. This flow lets cardholders provision cards directly to wearable devices from a mobile device. It also lets cardholders save card information to a Google Wallet account for use on other Google devices and applications, such as Google Chrome.
Stripe provides an SDK wrapper around a private Google library for push provisioning. To distribute your app on the Google Pay Store with push provisioning you need to:
1. [Set up a Google Issuer Console account](https://pay.google.com/business/console?business_type=financial_institution). Select **Financial Institution** as the business type.
2. In the Google Issuer Console dashboard, go to the **Push Provisioning API** tab and complete your business profile. If you don’t know the Visa BID or Mastercard CID, set it to `Unknown`. Google reviews your issuer details within 24 to 48 hours and emails you an NDA and call to action to complete.
3. Don’t exchange keys with Google. Instead, complete the [Unified Push Provisioning API Intake Request](https://support.google.com/googlepay/contact/upp_api_onboarding) and select **Aggregator/Program Manager linking** to link your managed issuer account to Stripe’s program manager account. Stripe’s Aggregator Merchant ID is `BCR2DN7TWDCZDZJC`.
> Google allows each Issuer Console account to link to only one program manager. If you have multiple program managers, contact [support-issuing@stripe.com](mailto:support-issuing@stripe.com) for guidance.
4. Request access to [Google’s push provisioning documentation](https://developers.google.com/pay/issuers/apis/push-provisioning/android) and download the [private TapAndPay SDK](https://developers.google.com/pay/issuers/apis/push-provisioning/android/releases). The most recently tested version, and the minimum required version, is `18.8.0`.
5. Update your client app and server backend by following the guidance in the [following sections](https://docs.stripe.com/issuing/cards/digital-wallets.md#update-your-app).
6. Submit screenshots of your app’s user flow in the Google Issuer Console. Follow [Google’s brand guidelines](https://developers.google.com/pay/issuers/apis/push-provisioning/android/branding-guidelines).
7. Submit your app ID and fingerprint in the Google Issuer Console to gain access to Google’s Push Provisioning API. Before you complete this step, **Add to Google Wallet** returns an error. For more information about allowlisting your app, see [Google’s documentation](https://developers.google.com/pay/issuers/apis/push-provisioning/android/allowlist).
8. [Contact support-issuing@stripe.com](mailto:support-issuing@stripe.com) with your application name, application ID, card network, and card name.
9. Use the [Testing section](https://docs.stripe.com/issuing/cards/digital-wallets.md#testing) to get final approval from Google before you go live.
## Setup your app [Client-side]
The [React Native SDK](https://github.com/stripe/stripe-react-native) is open source and fully documented. Internally, it uses the [native iOS](https://github.com/stripe/stripe-ios) and [Android](https://github.com/stripe/stripe-android) SDKs. To install Stripe’s React Native SDK, run one of the following commands in your project’s directory (depending on which package manager you use):
#### yarn
```bash
yarn add @stripe/stripe-react-native
```
#### npm
```bash
npm install @stripe/stripe-react-native
```
Next, install some other necessary dependencies:
- For iOS, go to the **ios** directory and run `pod install` to ensure that you also install the required native dependencies.
- For Android, there are no more dependencies to install.
> We recommend following the [official TypeScript guide](https://reactnative.dev/docs/typescript#adding-typescript-to-an-existing-project) to add TypeScript support.
### Stripe initialization
To initialize Stripe in your React Native app, either wrap your payment screen with the `StripeProvider` component, or use the `initStripe` initialization method. Only the API [publishable key](https://docs.stripe.com/keys.md#obtain-api-keys) in `publishableKey` is required. The following example shows how to initialize Stripe using the `StripeProvider` component.
```jsx
import { useState, useEffect } from 'react';
import { StripeProvider } from '@stripe/stripe-react-native';
function App() {
const [publishableKey, setPublishableKey] = useState('');
const fetchPublishableKey = async () => {
const key = await fetchKey(); // fetch key from your server here
setPublishableKey(key);
};
useEffect(() => {
fetchPublishableKey();
}, []);
return (
{/* Your app code here */}
);
}
```
> Use your API [test keys](https://docs.stripe.com/keys.md#obtain-api-keys) while you test and develop, and your [live mode](https://docs.stripe.com/keys.md#test-live-modes) keys when you publish your app.
### Android-specific setup
To enable push provisioning on Android, after you receive access to the TapAndPay SDK (see above), you need to [include it in your native Android project](https://developers.google.com/pay/issuers/apis/push-provisioning/android/setup). The minimum version required is version `18.8.0`.
Then, you need to import Stripe’s native Android push provisioning library by adding the following to your `android/app/build.gradle` file:
#### Groovy
```groovy
dependencies {
// ...
implementation 'com.stripe:stripe-android-issuing-push-provisioning:1.3.0'
}
```
### iOS-specific setup
To enable push provisioning on iOS, after Stripe confirms the entitlement has been granted, you need to [add the capability to your provisioning profile on app store connect](https://developer.apple.com/account/resources/profiles/list).
Then, you need to add the new entitlement to your `ios/app.config.js` file:
```
"entitlements": {
"com.apple.developer.payment-pass-provisioning": true
}
```
## Update your backend [Server-side]
The push provisioning implementation exposes methods that expect you to communicate with your own backend to create a Stripe Ephemeral Key and return a JSON of it to your app. This key is a short-lived API credential that you can use to retrieve the encrypted card details for a single instance of a card object.
To make sure that the object returned by the Stripe API is compatible with the version of the SDK you’re using, you must explicitly pass the API version exported by the React Native SDK to our API when creating the key.
#### curl
```bash
curl https://api.stripe.com/v1/ephemeral_keys \
-u <>: \
-d "issuing_card"="{{ISSUING_CARD_ID}}" \
-H "Stripe-Version: {{API_VERSION}}"
```
```json
{
"id": "ephkey_1G4V6eEEs6YsaMZ2P1diLWdj",
"object": "ephemeral_key",
"associated_objects": [
{
"id": "{{CARD_ID}}",
"type": "issuing.card"
}
],
"created": 1586556828,
"expires": 1586560428,
"livemode": false,
"secret": "ek_test_YWNjdF8xRmdlTjZFRHelWWxwWVo5LEtLWFk0amJ2N0JOa0htU1JzEZkd2RpYkpJdnM_00z2ftxCGG"
}
```
You should also create an endpoint to retrieve issuing card details that you must pass to the `` component:
```curl
curl https://api.stripe.com/v1/issuing/cards/ISSUING_CARD_ID \
-u "<>:"
```
## Update your app [Client-side]
First, determine if the device is eligible to use push provisioning by checking that the value of `wallets.apple_pay.eligible` in the issued card (retrieved from the second endpoint you created above in step 3) is `true`. If it’s, save the card details to use later in our component, and proceed. If `wallets.apple_pay.eligible` is `false`, don’t show the `` on iOS, or App Review might reject your app. The same applies to `wallets.google_pay.eligible` for Android.
```javascript
import React, {useEffect, useState} from 'react';
import {Constants} from '@stripe/stripe-react-native';
import {View} from 'react-native';
export default function MyScreen() {
const [key, setKey] = useState(null);
const [card, setCard] = useState(null);
useEffect(() => {
fetchEphemeralKey();
fetchIssuingCard();
}, []);
const fetchIssuingCard = async () => {
const response = await fetch(`${API_URL}/issuing-card`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
ISSUING_CARD_ID: '{{ISSUING_CARD_ID}}',
}),
});
const card = await response.json();
if (!card.wallets.apple_pay.eligible) {
// Do not show component on iOS. See card.wallets.apple_pay.ineligible_reason for details
} else if (!card.wallets.google_pay.eligible) {
// Do not show component on Android. See card.wallets.google_pay.ineligible_reason for details
} else {
setCard(card);
}
};
const fetchEphemeralKey = async () => {
// See above
};
return ;
}
```
Next, fetch your ephemeral key from the first endpoint you created in Step 3 above, and save it.
```javascript
import React, {useEffect, useState} from 'react';
import {Constants} from '@stripe/stripe-react-native';
import {View} from 'react-native';
export default function MyScreen() {
const [key, setKey] = useState(null);
useEffect(() => {
fetchEphemeralKey();
}, []);
const fetchEphemeralKey = async () => {
const response = await fetch(`${API_URL}/ephemeral-key`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
ISSUING_CARD_ID: '{{ISSUING_CARD_ID}}',
API_VERSION: Constants.API_VERSIONS.ISSUING,
}),
});
const myKey = await response.json();
setKey(myKey);
};
return ;
}
```
You don’t need any more server communication. Next, you must determine if the card *can* be added to the wallet. You can check for this with the `canAddCardToWallet` method, which returns an object containing a boolean field `canAddCard`. If `canAddCard` is `false`, don’t render the `AddToWalletButton`, otherwise your app might be rejected by Apple or Google.
On Android, the card might be in the wallet already, but stuck in a bad state. You can add logic to handle this case by checking for a `token` in the object returned from `canAddCardToWallet`. If that response is non-null, and the `token.status` is `"TOKEN_STATE_NEEDS_IDENTITY_VERIFICATION"`, pass that `token` into ``'s props.
> For Android, provide your application ID to Stripe before you start internal testing. Setup can take more than 1 week, and an incomplete setup can cause inconsistent responses from these methods. The tokens returned by `canAddCardToWallet` include only cards added after Stripe completes the setup.
```javascript
import React, {useEffect, useState} from 'react';
import {Constants, canAddCardToWallet, GooglePayCardToken} from '@stripe/stripe-react-native';
import {View} from 'react-native';
export default function MyScreen() {
const [key, setKey] = useState(null);
const [card, setCard] = useState(null);
const [showAddToWalletButton, setShowAddToWalletButton] = useState(false);
const [androidCardToken, setAndroidCardToken] = useState(null);
useEffect(() => {
fetchEphemeralKey();
fetchIssuingCard();
}, []);
const checkIfCanAddCard = async () => {
const { canAddCard, details, error } = await canAddCardToWallet({
primaryAccountIdentifier: card?.wallets?.primary_account_identifier,
cardLastFour: card.last4,
cardBrand: card.brand,
hasPairedAppleWatch: // Pass a boolean indicating whether or not the device has a paired Apple Watch. iOS only.
});
if (error) {
Alert.alert(error.code, error.message);
} else {
setShowAddToWalletButton(canAddCard);
if (details?.token?.status === 'TOKEN_STATE_NEEDS_IDENTITY_VERIFICATION') {
setAndroidCardToken(details.token);
}
}
};
const fetchIssuingCard = async () => {
// See above
await checkIfCanAddCard();
};
const fetchEphemeralKey = async () => {
// See above
};
return ;
}
```
Now we have all the information we need to show the button:
```javascript
import React, {useEffect, useState} from 'react';
import {
Constants,
canAddCardToWallet,
AddToWalletButton,
GooglePayCardToken,
} from '@stripe/stripe-react-native';
import {View, Image, Alert, StyleSheet} from 'react-native';
import AddToGooglePayPNG from '../assets/Add-to-Google-Pay-Button-dark-no-shadow.png';
export default function MyScreen() {
const [key, setKey] = useState(null);
const [card, setCard] = useState(null);
const [showAddToWalletButton, setShowAddToWalletButton] = useState(false);
const [androidCardToken, setAndroidCardToken] = useState(null);
useEffect(() => {
fetchEphemeralKey();
fetchIssuingCard();
}, []);
const canAddCard = async () => {
// See above
};
const fetchIssuingCard = async () => {
// See above
};
const fetchEphemeralKey = async () => {
// See above
};
return (
{showAddToWalletButton && (
{
Alert.alert(
error ? error.code : 'Success',
error
? error.message
: 'Card was successfully added to the wallet.',
);
}}
/>
)}
);
}
const styles = StyleSheet.create({
payButton: {
// You may add custom styles to your button, but make sure it complies
// with the relevant platform guidelines:
// iOS : https://developer.apple.com/wallet/add-to-apple-wallet-guidelines/
// Android : https://developers.google.com/pay/issuers/apis/push-provisioning/android/branding-guidelines
},
});
```
When a user taps the button, the UI to add the card to the wallet opens. Implement the callback in your `onComplete` prop. If the `error` field is non-null, an error occurred and the card wasn’t added to the wallet. If `error` is `null`, the card was provisioned successfully. After provisioning succeeds, display a success message to the user. Google reviews this screen as part of the user flow review.
If you support Bounce Provisioning, you can dynamically set `isBounceProvisioned` on `AddToWalletButton`. See [Google’s documentation](https://developers.google.com/pay/issuers/apis/push-provisioning/android/bounce_provisioning) for more information about how to support Bounce Provisioning within your app.
### Button style
On iOS, the button style is determined by the `iOSButtonStyle` prop. Set this prop to:
- `onLightBackground` when you show the button on top of a light or white background.
- `onDarkBackground` when you show the button on top of a dark or black background.
On Android, you must pass in the actual image asset to the `androidAssetSource` prop. You can download all the possible asset options [directly from Google](https://developers.google.com/static/pay/issuers/apis/push-provisioning/android/downloads/add-to-wallet-png.zip). Follow [Google’s branding guidelines](https://developers.google.com/pay/issuers/apis/push-provisioning/android/branding-guidelines#style) when implementing your button.
To pass your chosen PNG to the `AddToWalletButton` component, add it to your project, import it like you would any other asset, and then resolve the source with `Image.resolveAssetSource`:
```javascript
import {Image} from 'react-native';
import AddToGooglePayPNG from '../assets/Add-to-Google-Pay-Button-dark-no-shadow.png';
...
```
## Testing
### iOS
On iOS, you can test push provisioning in development, on simulators, and with test cards as long as you pass `testEnv={true}` to the `AddToWalletButton` component. Be aware that if the `testEnv` prop is set to `true`, cards won’t actually be added to the device’s wallet. In testing environments, you don’t need the `com.apple.developer.payment-pass-provisioning` entitlement.
### Android
On Android, the `testEnv` prop has no effect. All testing must be done in live mode, with live issuing cards, and on physical devices.
Submit videos through the Issuer Console that show successful completion of Google’s [test cases](https://developers.google.com/pay/issuers/apis/push-provisioning/android/test-cases). Before you go live, also complete [field testing](https://developers.google.com/pay/issuers/apis/push-provisioning/android/launch-process#field_testing). For more information, see [Google’s launch process](https://developers.google.com/pay/issuers/apis/push-provisioning/android/launch-process#app_review).
Make sure to provide your application ID to Stripe before starting internal testing.