iOS push provisioning Invite Only

     

    Learn how to provide your users with an Add to Apple Wallet button

    Users can already add their Stripe Issuing cards to Apple wallet by going to the Wallet app and entering the card number manually.

    If you’re distributing an app, push provisioning allows you to display a single Add to Apple Wallet button, that guides them through adding their card to Apple Wallet without having to enter the card number.

    Request access from Apple

    Push provisioning requires a special entitlement from Apple called com.apple.developer.payment-pass-provisioning. You can request it by emailing apple-pay-provisioning@apple.com. Please cc push-provisioning-requests@stripe.com and mention that you’ll be using Stripe Issuing. In your email, include your app’s name, your developer team ID, and your app’s ADAM ID. (You can find your team ID at https://developer.apple.com/account/#/membership . Your ADAM ID is your app’s unique numeric ID. If the App Store link to your app is https://apps.apple.com/app/id123456789, your ADAM ID would be 123456789. You can also find it in App Store Connect).

    Important note on testing

    The com.apple.developer.payment-pass-provisioning entitlement required above only works with distribution provisioning profiles, which means that even after you obtain it, the only way to test the end-to-end push provisioning flow is by first distributing your app via TestFlight or the App Store. This can make testing your app awkward. To mitigate this, we’ve written a mock version of PKAddPaymentPassViewController (the iOS class responsible for managing push provisioning) called STPFakeAddPaymentPassViewController. This class is 100% API compatible with PKAddPaymentPassViewController, so you can write your integration against it, and when it’s time to go to production, simply replace any references to STPFakeAddPaymentPassViewController with PKAddPaymentPassViewController and be confident that your app will work. STPFakeAddPaymentPassViewController also contains code that will help you diagnose common integration issues.

    Update your app

    First, make sure you’ve integrated the latest version of the Stripe iOS SDK with your app, per the instructions at our Getting Started page.

    Next, determine if the user’s device is eligible to use push provisioning by calling PKAddPaymentPassViewController.canAddPaymentPass(). You might use that to show or hide a PKAddPassButton, for example. When the user presses that button (or otherwise indicates they’d like to begin push provisioning), you’ll want to 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> // Inside a 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]; }

    You’ll notice that PKAddPaymentPassViewController’s initializer takes a delegate that you’ll 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

    Lastly, you’ll notice STPPushProvisioningContext’s initializer expects a keyProvider. This should be an instance of a class that implements the STPIssuingCardEphemeralKeyProvider protocol (again, it’s fine if this is just your view controller). 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, as described in “Backend Changes” below, 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 Alamofire.request("https://myapi.com/ephemeral_keys", method: .post, parameters: ["api_version": apiVersion]) .responseJSON { response in completion(response.result.value, response.result.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

    That’s it! You can now go through the push provisioning flow, with STPPushProvisioningContext doing the heavy lifting of communicating with the Stripe API.

    Backend changes

    The push provisioning implementation exposes methods that expect you to communicate with your own server 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 just a single instance of an Issuing Card object. To ensure that the object returned by the Stripe API is compatible with the version of the iOS/Android SDK you’re using, our SDK will tell you what API version it prefers, and then you must explicitly pass this API version to our API when creating the key on your backend. Your app is responsible for determining which Card object to use. Typically this would involve finding the Issuing Cardholder object that is associated with your logged-in user and returning their first active Card.

    # Sinatra post path do stripe_version = params['api_version'] # issuingCardID should be 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: stripe_version} ) key.to_json end
    # Flask from flask import Flask, session, jsonify, request @app.route(path, methods=['POST']) def issue_key(): api_version = request.args['api_version'] # issuing_card_id should be the ID of the Issuing Card you'd like to use for push provisioning. key = stripe.EphemeralKey.create(issuing_card=issuing_card_id, api_version=api_version) return jsonify(key)
    if (!isset($_POST['api_version'])) { exit(http_response_code(400)); } try { // $issuingCardID should be the ID of the Issuing Card you'd like to use for push provisioning. $key = \Stripe\EphemeralKey::create( ["issuing_card" => $issuingCardID], ["stripe_version" => $_POST['api_version']] ); header('Content-Type: application/json'); exit(json_encode($key)); } catch (Exception $e) { exit(http_response_code(500)); }
    // Express app.post(path, (req, res) => { const stripe_version = req.query.api_version; if (!stripe_version) { res.status(400).end(); return; } // issuingCardID should be the ID of the Issuing Card you'd like to use for push provisioning. stripe.ephemeralKeys.create( {issuing_card: issuingCardID}, {stripe_version: stripe_version} ).then((key) => { res.status(200).json(key); }).catch((err) => { res.status(500).end(); }); });
    // Using Spark framework (http://sparkjava.com) post(new Route(path) { @Override public Object handle(final Request request, final Response response) { String apiVersion = request.queryParams("api_version"); RequestOptions requestOptions = (new RequestOptions.RequestOptionsBuilder()) .setStripeVersion(apiVersion) .build(); try { // issuingCardID should be the ID of the Issuing Card you'd like to use for push provisioning. Map<String, Object> options = new HashMap<String, Object>(); options.put("issuing_card", issuingCardID); EphemeralKey key = EphemeralKey.create(options, requestOptions); return key.getRawJson(); } catch (StripeException e) { response.status(500); return e; } } });
    // net/http // The issuingCardID parameter should be the ID of the Issuing Card you'd like to use for push provisioning. func issueKeyHandler(w http.ResponseWriter, r *http.Request, issuingCardID string) { r.ParseForm() stripeVersion := r.Form.Get("api_version") if stripeVersion == "" { log.Printf("Stripe-Version not found\n") w.WriteHeader(400) return } params := &stripe.EphemeralKeyParams{ IssuingCard: stripe.String(issuingCardID), StripeVersion: stripe.String(stripeVersion), } key, err := ephemeralkey.New(params) if err != nil { log.Printf("Stripe bindings call failed, %v\n", err) w.WriteHeader(500) return } w.Write(key.RawJSON) }

    Was this page helpful?

    Thank you for helping improve Stripe's documentation. If you need help or have any questions, please consider contacting support.

    On this page