Issuing
Digital wallets

Digital wallets Invite only

Learn how to add cards to digital wallets.

Add to wallet directly

Users can add Stripe Issuing cards to their Apple Pay, Google Pay, and Samsung Pay wallets by opening the wallet app on their phone and entering their card number.

Stripe sends a 6-digit verification code to the phone_number or email of the cardholder associated with the card.

If neither field is set on the cardholder, a “card not supported” error will be displayed.

Add to wallet with an app

1 Request Access

Push provisioning requires a special entitlement from Apple called com.apple.developer.payment-pass-provisioning. You can request it by emailing push-provisioning-requests@stripe.com. In your email, include your:

2 Update your app Client-side

Make sure you’ve integrated the latest version of the Stripe iOS SDK with your app.

Determine if the device is eligible to use push provisioning by calling PKAddPaymentPassViewController.canAddPaymentPass(). You can use this to show or hide a PKAddPassButton. When the user taps the button, create and present a PKAddPaymentPassViewController, which contains Apple’s UI for the push provisioning flow.

import Stripe class MyViewController: UIViewController { // ... func beginPushProvisioning() { let config = STPPushProvisioningContext.requestConfiguration( withName: "Jenny Rosen", // the cardholder's name description: "RocketRides Card", // optional; a description of your card last4: "4242", // optional; the last 4 digits of the card brand: .visa // optional; the brand of the card ) let controller = PKAddPaymentPassViewController(requestConfiguration: config, delegate: self) self.present(controller!, animated: true, completion: nil) } }
#import <Stripe/Stripe.h> @implementation ViewController - (void)beginPushProvisioning { PKAddPaymentPassRequestConfiguration *config = [STPPushProvisioningContext requestConfigurationWithName:@"Jenny Rosen" description:@"RocketRides Card" last4:@"4242" brand:STPCardBrandVisa]; PKAddPaymentPassViewController *controller = [[PKAddPaymentPassViewController alloc] initWithRequestConfiguration:config delegate:self]; [self presentViewController:controller animated:YES completion:nil]; } @end

The PKAddPaymentPassViewController’s initializer takes a delegate that you need to implement – typically this can just be the view controller from which you’re presenting it. We provide a class called STPPushProvisioningContext that is designed to help you implement these methods.

class MyViewController: UIViewController { var pushProvisioningContext: STPPushProvisioningContext? = nil // ... } extension MyViewController: PKAddPaymentPassViewControllerDelegate { func addPaymentPassViewController(_ controller: PKAddPaymentPassViewController, generateRequestWithCertificateChain certificates: [Data], nonce: Data, nonceSignature: Data, completionHandler handler: @escaping (PKAddPaymentPassRequest) -> Void) { self.pushProvisioningContext = STPPushProvisioningContext(keyProvider: self) // STPPushProvisioningContext implements this delegate method for you, by retrieving encrypted card details from the Stripe API. self.pushProvisioningContext?.addPaymentPassViewController(controller, generateRequestWithCertificateChain: certificates, nonce: nonce, nonceSignature: nonceSignature, completionHandler: handler); } func addPaymentPassViewController(_ controller: PKAddPaymentPassViewController, didFinishAdding pass: PKPaymentPass?, error: Error?) { // Depending on if `error` is present, show a success or failure screen. self.dismiss(animated: true, completion: nil) } }
@interface ViewController () <PKAddPaymentPassViewControllerDelegate> @property STPPushProvisioningContext *pushProvisioningContext; @end @implementation ViewController - (void)addPaymentPassViewController:(PKAddPaymentPassViewController *)controller generateRequestWithCertificateChain:(NSArray<NSData *> *)certificates nonce:(NSData *)nonce nonceSignature:(NSData *)nonceSignature completionHandler:(void (^)(PKAddPaymentPassRequest * _Nonnull))handler { self.pushProvisioningContext = [[STPPushProvisioningContext alloc] initWithKeyProvider:self]; // STPPushProvisioningContext implements this delegate method for you, by retrieving encrypted card details from the Stripe API. [self.pushProvisioningContext addPaymentPassViewController:controller generateRequestWithCertificateChain:certificates nonce:nonce nonceSignature:nonceSignature completionHandler:handler]; } - (void)addPaymentPassViewController:(PKAddPaymentPassViewController *)controller didFinishAddingPaymentPass:(PKPaymentPass *)pass error:(NSError *)error { // Depending on if `error` is present, show a success or failure screen. [self dismissViewControllerAnimated:YES completion:nil]; } @end

Last, you’ll notice STPPushProvisioningContext’s initializer expects a keyProvider. This should be an instance of a class that implements the STPIssuingCardEphemeralKeyProvider protocol.

This protocol defines a single required method, createIssuingCardKeyWithAPIVersion:completion. To implement this method, make an API call to your backend. Your backend creates an Ephemeral Key object using the Stripe API, and returns it to your app. Your app then calls the provided completion handler with your backend’s API response.

extension MyViewController: STPIssuingCardEphemeralKeyProvider { func createIssuingCardKey(withAPIVersion apiVersion: String, completion: @escaping STPJSONResponseCompletionBlock) { // This example uses Alamofire for brevity, but you can make the request however you want AF.request("https://myapi.com/ephemeral_keys", method: .post, parameters: ["api_version": apiVersion]) .responseJSON { response in switch response.result { case .success: if let data = response.data { do { let obj = try JSONSerialization.jsonObject(with: data, options: []) as! [AnyHashable: Any] completion(obj, nil) } catch { completion(nil, error) } case .failure(let error): completion(nil, error) } } }
@interface ViewController () <STPIssuingCardEphemeralKeyProvider> @end @implementation ViewController - (void)createIssuingCardKeyWithAPIVersion:(NSString *)apiVersion completion:(STPJSONResponseCompletionBlock)completion { NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; NSURL *url = [NSURL URLWithString:@"https://myapi.com/ephemeral_keys"]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; request.HTTPMethod = @"POST"; NSString *postBody = [@"api_version=" stringByAppendingString:apiVersion]; request.HTTPBody = [postBody dataUsingEncoding:NSUTF8StringEncoding]; [[session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSDictionary *parsed = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; completion(parsed, error); }] resume]; } @end

3 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 it to your app. This key is a short-lived API credential that can be used to retrieve the encrypted card details for a single instance of a card object.

To ensure that the object returned by the Stripe API is compatible with the version of iOS/Android SDK you are using, the Stripe SDK will tell you what API version it prefers. You must explicitly pass this API version to our API when creating the key.

curl https://api.stripe.com/v1/ephemeral_keys \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -d issuing_card="{{ISSUING_CARD_ID}}" \ -H "Stripe-Version: {{API_VERSION}}"
# ISSUING_CARD_ID is the ID of the Issuing Card you'd like to use for # push provisioning. key = Stripe::EphemeralKey.create( {issuing_card: '{{ISSUING_CARD_ID}}'}, {stripe_version: '{{API_VERSION}}'} )
# ISSUING_CARD_ID is the ID of the Issuing Card you'd like to use for # push provisioning. key = stripe.EphemeralKey.create( issuing_card='{{ISSUING_CARD_ID}}', stripe_version='{{API_VERSION}}', )
// ISSUING_CARD_ID is the ID of the Issuing Card you'd like to use for // push provisioning. $key = \Stripe\EphemeralKey::create( ['issuing_card' => '{{ISSUING_CARD_ID}}'], ['stripe_version' => '{{API_VERSION}}'] );
// ISSUING_CARD_ID is the ID of the Issuing Card you'd like to use for // push provisioning. let key = await stripe.ephemeralKeys.create( {issuing_card: '{{ISSUING_CARD_ID}}'}, {stripe_version: '{{API_VERSION}}'} );
// ISSUING_CARD_ID is the ID of the Issuing Card you'd like to use for // push provisioning. RequestOptions requestOptions = (new RequestOptions.RequestOptionsBuilder()) .setStripeVersion("{{API_VERSION}}") .build(); Map<String, Object> options = new HashMap<String, Object>(); options.put("issuing_card", "{{ISSUING_CARD_ID}}"); EphemeralKey key = EphemeralKey.create(options, requestOptions);
// ISSUING_CARD_ID is the ID of the Issuing Card you'd like to use for // push provisioning. params := &stripe.EphemeralKeyParams{ IssuingCard: stripe.String("{{ISSUING_CARD_ID}}"), StripeVersion: stripe.String("{{API_VERSION}}"), } key, err := ephemeralkey.New(params)
// ISSUING_CARD_ID is the ID of the Issuing Card you'd like to use for // push provisioning. var options = new EphemeralKeyCreateOptions { IssuingCard = "{{ISSUING_CARD_ID}}", StripeVersion = "{{API_VERSION}}" }; var service = new EphemeralKeyService(); service.Create(options);
{ "id": "ephkey_1G4V6eEEs6YsaMZ2P1diLWdj", "object": "ephemeral_key", "associated_objects": [ { "id": "ic_1GWQp6EESaYspYZ9uSEZOcq9", "type": "issuing.card" } ], "created": 1586556828, "expires": 1586560428, "livemode": false, "secret": "ek_test_YWNjdF8xRmdlTjZFRHelWWxwWVo5LEtLWFk0amJ2N0JOa0htU1JzEZkd2RpYkpJdnM_00z2ftxCGG" }

4 Testing

The com.apple.developer.payment-pass-provisioning entitlement only works with distribution provisioning profiles, meaning even after you obtain it, the only way to test the end-to-end push provisioning flow is by distributing your app with TestFlight or the App Store.

To make testing easier, we provide a mock version of PKAddPaymentPassViewController called STPFakeAddPaymentPassViewController that can be used interchangeably during testing.

import Stripe class MyViewController: UIViewController { // ... func beginPushProvisioning() { let config = STPPushProvisioningContext.requestConfiguration( withName: "Jenny Rosen", // the cardholder's name description: "RocketRides Card", // optional; a description of your card last4: "4242", // optional; the last 4 digits of the card brand: .visa // optional; the brand of the card ) let controller = STPFakeAddPaymentPassViewController(requestConfiguration: config, delegate: self) self.present(controller!, animated: true, completion: nil) } }
#import <Stripe/Stripe.h> @implementation ViewController - (void)beginPushProvisioning { PKAddPaymentPassRequestConfiguration *config = [STPPushProvisioningContext requestConfigurationWithName:@"Jenny Rosen" description:@"RocketRides Card" last4:@"4242" brand:STPCardBrandVisa]; PKAddPaymentPassViewController *controller = [[STPFakeAddPaymentPassViewController alloc] initWithRequestConfiguration:config delegate:self]; [self presentViewController:controller animated:YES completion:nil]; } @end

1 Request Access

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:

2 Update your app Client-side

  • Import Google’s private SDK.
  • Import Stripe’s SDK.
dependencies { [... your dependencies] implementation 'com.stripe:stripe-android-issuing-push-provisioning:1.0.3' }
  • Prepare your backend to create ephemeral keys for your cards. See section below.
  • Create an EphemeralKeyProvider that extends PushProvisioningEphemeralKeyProvider. As the ephemeral key provider will be passed to another activity, it also needs to implement Parcelable (see Parcelable).
  • Implement the Add to Google Pay button according to Google’s specifications.
  • When a user taps the button, launch Stripe’s PushProvisioningActivity using the PushProvisioningActivityStarter.
new PushProvisioningActivityStarter( this, // The Activity or Fragment you are initiating the push provisioning from new PushProvisioningActivityStarter.Args( "Stripe Card", // The name that will appear on the push provisioning UI ephemeralKeyProvider, // Your instance of EphemeralKeyProvider false // If you want to enable logs or not )).startForResult();
  • This prepares the push provisioning and launches the UI to add the card to the wallet. Implement the callback in your onActivityResult.
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { if (requestCode == PushProvisioningActivityStarter.REQUEST_CODE) { if (resultCode == PushProvisioningActivity.RESULT_OK) { PushProvisioningActivityStarter.Result success = PushProvisioningActivityStarter.Result.fromIntent(data); } else if (resultCode == PushProvisioningActivity.RESULT_ERROR) { PushProvisioningActivityStarter.Error error = PushProvisioningActivityStarter.Error.fromIntent(data); } } }

If the provisioning was successful, you’ll receive a PushProvisioningActivityStarter.Result containing a cardTokenId which is Google’s ID for the card in the active wallet. You can use the rest of the wallet functions with this ID.

If the provisioning encountered an error, a PushProvisioningActivityStarter.Error will be returned with a code and a message. The message is a developer-friendly text explaining the error. The code can have the following values:

Enum Meaning
USER_CANCELED The user canceled the provisioning.
CARD_CANCELED The card has been canceled or is lost or stolen and cannot be provisioned.
EPHEMERAL_KEY_ERROR There was an error retrieving the ephemeral key.
TAP_AND_PAY_UNAVAILABLE The TapAndPay library can’t be used, most likely because the app is not whitelisted.
NO_STABLE_HARDWARE_ID This can happen in the development emulator. The app can’t retrieve the stable hardware ID.
NO_ACTIVE_WALLET_FOUND No active wallet available. Note that emulators generally don’t have Google Pay.
PUSH_PROVISIONING_ENCRYPTED_PAYLOAD_ERROR There was an error contacting Stripe’s servers to get the encrypted payload for push provisioning.
UNKNOWN_ERROR An unexpected error occurred. The message will have additional information.

3 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 it to your app. This key is a short-lived API credential that can be used to retrieve the encrypted card details for a single instance of a card object.

To ensure that the object returned by the Stripe API is compatible with the version of iOS/Android SDK you are using, the Stripe SDK will tell you what API version it prefers. You must explicitly pass this API version to our API when creating the key.

curl https://api.stripe.com/v1/ephemeral_keys \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -d issuing_card="{{ISSUING_CARD_ID}}" \ -H "Stripe-Version: {{API_VERSION}}"
# ISSUING_CARD_ID is the ID of the Issuing Card you'd like to use for # push provisioning. key = Stripe::EphemeralKey.create( {issuing_card: '{{ISSUING_CARD_ID}}'}, {stripe_version: '{{API_VERSION}}'} )
# ISSUING_CARD_ID is the ID of the Issuing Card you'd like to use for # push provisioning. key = stripe.EphemeralKey.create( issuing_card='{{ISSUING_CARD_ID}}', stripe_version='{{API_VERSION}}', )
// ISSUING_CARD_ID is the ID of the Issuing Card you'd like to use for // push provisioning. $key = \Stripe\EphemeralKey::create( ['issuing_card' => '{{ISSUING_CARD_ID}}'], ['stripe_version' => '{{API_VERSION}}'] );
// ISSUING_CARD_ID is the ID of the Issuing Card you'd like to use for // push provisioning. let key = await stripe.ephemeralKeys.create( {issuing_card: '{{ISSUING_CARD_ID}}'}, {stripe_version: '{{API_VERSION}}'} );
// ISSUING_CARD_ID is the ID of the Issuing Card you'd like to use for // push provisioning. RequestOptions requestOptions = (new RequestOptions.RequestOptionsBuilder()) .setStripeVersion("{{API_VERSION}}") .build(); Map<String, Object> options = new HashMap<String, Object>(); options.put("issuing_card", "{{ISSUING_CARD_ID}}"); EphemeralKey key = EphemeralKey.create(options, requestOptions);
// ISSUING_CARD_ID is the ID of the Issuing Card you'd like to use for // push provisioning. params := &stripe.EphemeralKeyParams{ IssuingCard: stripe.String("{{ISSUING_CARD_ID}}"), StripeVersion: stripe.String("{{API_VERSION}}"), } key, err := ephemeralkey.New(params)
// ISSUING_CARD_ID is the ID of the Issuing Card you'd like to use for // push provisioning. var options = new EphemeralKeyCreateOptions { IssuingCard = "{{ISSUING_CARD_ID}}", StripeVersion = "{{API_VERSION}}" }; var service = new EphemeralKeyService(); service.Create(options);
{ "id": "ephkey_1G4V6eEEs6YsaMZ2P1diLWdj", "object": "ephemeral_key", "associated_objects": [ { "id": "ic_1GWQp6EESaYspYZ9uSEZOcq9", "type": "issuing.card" } ], "created": 1586556828, "expires": 1586560428, "livemode": false, "secret": "ek_test_YWNjdF8xRmdlTjZFRHelWWxwWVo5LEtLWFk0amJ2N0JOa0htU1JzEZkd2RpYkpJdnM_00z2ftxCGG" }
Was this page helpful?
Questions? Contact us.
Developer tutorials on YouTube.