Using Android Standard UI Components

    Learn how to build an integration that uses the built-in user interface components in Stripe’s Android SDK.

    This guide demonstrates how to build your app’s customer payment management flow using PaymentSession, a class designed to make building your app’s checkout flow as easy as possible. It handles collecting, saving, and reusing your user’s payment details.

    Prepare your API

    Our prebuilt UI elements operate on the assumption that you have a single Customer object for each of your users. We strongly recommend you create this Customer at the same time you create your user on your own backend, so that every user is guaranteed to have an associated Customer. (This is fine even if you don’t collect your user’s payment information when they sign up—it’s totally OK to have a Customer without any attached cards.)

    Create ephemeral keys

    Our prebuilt UI elements require an ephemeral key, a short-lived API key with restricted API access. You can think of an ephemeral key as a session, authorizing the SDK to retrieve and update a specific Customer object for the duration of the session. To provide an ephemeral key to the SDK, you’ll need to expose a new API endpoint on your backend. This endpoint should create an ephemeral key for the current Stripe customer, and return the key’s unmodified response as JSON.

    When the SDK requests an ephemeral key, it specifies the version of the Stripe API that it expects the response to come from. Your endpoint should accept an api_version parameter, and use the specified API version when creating the ephemeral key. This ensures that the SDK always receives the correct ephemeral key response from your backend. You can consult our Example Backend to see this in practice.

    # Sinatra
    post path do
      stripe_version = params['api_version']
      customer_id = session['customer_id']
      key = Stripe::EphemeralKey.create(
        {customer: customer_id},
        {stripe_version: stripe_version}
      )
      key.to_json
    end
    # Flask
    from flask import Flask, session, jsonify, request
    # This function assumes that the session handling has stored the customerId
    @app.route(path, methods=['POST'])
    def issue_key():
        stripe_api_version = request.args['api_version']
        customerId = session['customerId']
        key = stripe.EphemeralKey.create(customer=customerId, api_version=stripe_api_version)
        return jsonify(key)
    // This assumes that $customerId has been set appropriately from session data
    parse_str($_SERVER['QUERY_STRING'], $params);
    if (!isset($params['api_version']))
    {
        exit(http_response_code(400));
    }
    try {
        $key = \Stripe\EphemeralKey::create(
          ["customer" => $customerId],
          ["stripe_version" => $params['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;
      }
      // This function assumes that some previous middleware has determined the
      // correct customerId for the session and saved it on the request object.
      stripe.ephemeralKeys.create(
        {customer: req.customerId},
        {stripe_version: stripe_version}
      ).then((key) => {
        res.status(200).json(key);
      }).catch((err) => {
        res.status(500).end();
      });
    });
    // net/http
    // The customerId parameter should be the ID of the Customer object associated
    // with the session the request was made on.
    func issueKeyHandler(w http.ResponseWriter, r *http.Request, customerId 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{
            Customer: stripe.String(customerId),
            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)
    }

    After you’ve added an ephemeral key endpoint to your backend, you’ll need a way for your Android app to communicate with this endpoint. In your app, you should make your API client class implement the EphemeralKeyProvider interface, which defines a single method, createEphemeralKey(). When implementing this method, be sure to pass the apiVersion parameter along to your ephemeral keys endpoint. You can consult our Example App to see this in practice.

    public class ExampleEphemeralKeyProvider implements EphemeralKeyProvider {
        private final BackendApi mBackendApi =
            RetrofitFactory.instance.create(BackendApi.class);
        private final CompositeDisposable mCompositeDisposable =
            new CompositeDisposable();
    
        @Override
        public void createEphemeralKey(
                @NonNull @Size(min = 4) String apiVersion,
                @NonNull final EphemeralKeyUpdateListener keyUpdateListener) {
            final Map<String, String> apiParamMap = new HashMap<>();
            apiParamMap.put("api_version", apiVersion);
    
            // Using RxJava2 for handling asynchronous responses
            mCompositeDisposable.add(mBackendApi.createEphemeralKey(apiParamMap)
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(
                            response -> {
                                try {
                                    final String rawKey = response.string();
                                    keyUpdateListener.onKeyUpdate(rawKey);
                                } catch (IOException ignored) {
                                }
                            }));
        }
    }
    
    class ExampleEphemeralKeyProvider : EphemeralKeyProvider {
    
        private val compositeDisposable: CompositeDisposable = CompositeDisposable()
        private val backendApi: BackendApi =
                RetrofitFactory.instance.create(BackendApi::class.java)
    
        override fun createEphemeralKey(
            @Size(min = 4) apiVersion: String,
            keyUpdateListener: EphemeralKeyUpdateListener
        ) {
            compositeDisposable.add(
                backendApi.createEphemeralKey(hashMapOf("api_version" to apiVersion))
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe { responseBody ->
                        try {
                            val ephemeralKeyJson = responseBody.string()
                            keyUpdateListener.onKeyUpdate(ephemeralKeyJson)
                        } catch (e: IOException) {
                            keyUpdateListener
                                .onKeyUpdateFailure(0, e.message ?: "")
                        }
                    })
        }
    }
    

    After creating an EphemeralKeyProvider, initialize a CustomerSession. Think of CustomerSession as a Stripe communication manager for a single logged-in session of a customer. A CustomerSession talks to your backend to retrieve an ephemeral key (via its key provider), and uses that key to manage retrieving and updating its Stripe customer on your behalf.

    public class StoreActivity extends Activity {
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            CustomerSession.initCustomerSession(this,
                    new ExampleEphemeralKeyProvider());
        }
    }
    
    class StoreActivity : Activity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            CustomerSession.initCustomerSession(this, ExampleEphemeralKeyProvider())
        }
    }
    

    CustomerSession automatically prefetches its customer once its key provider has been set. If you’d like to take advantage of preloading your customer’s information, initialize your CustomerSession instance earlier, before your user enters your payment flow. If your current user logs out of the app, you should clear the current CustomerSession singleton using the clearInstance method. When a new user logs in, you can re-initialize the instance. On your backend, be sure to create and return a new ephemeral key for the customer object associated with the new user.

    Implement your app’s payment flow

    Our SDK provides a class called PaymentSession, which is designed to make building your app’s checkout flow as easy as possible. It handles collecting, saving, and reusing your user’s payment details, and can also be used to collect shipping info. Think of it as the data source for your checkout activity—it handles asynchronously retrieving the data you need, and notifies its listener when its UI should change.

    Setting the listener, configuration, and host activity

    To work with PaymentSession, you’ll need to write a class that implements PaymentSessionListener, and pass a PaymentSessionConfig object to PaymentSession#init(). (Note, the code samples in this section are simply examples – your own implementation may differ depending on the structure of your app).

    The PaymentSessionConfig object is created using a Builder pattern, and has the following fields, all of which are optional.

    setShippingInfoRequired(boolean required)

    This method sets whether or not the PaymentSession requires the user to have a valid shipping address. The default value is true.

    setShippingMethodsRequired(boolean required)

    This method sets whether or not the PaymentSession requires the user to select a shipping method (Overnight, Standard Shipping, etc). The default value is true. You must set these values later.

    setPrepopulatedShippingInfo(ShippingInformation shippingInfo)

    Sets a default shipping address for your customer, which you can get from your CustomerSession or your own records. The Shipping information screen will start with this information already filled out.

    setHiddenShippingInfoFields(List<String> hiddenFields)

    Sets which address fields should be hidden in the shipping information screen. Values must be from among the labels listed in ShippingInfoWidget.. All fields will be shown if this list is empty. Note that not all fields can be hidden, such as country or name.

    setOptionalShippingInfoFields(List<String> hiddenFields)

    Sets which address fields should be listed as optional in the shipping information screen. Values must be from among the labels listed in ShippingInfoWidget.. All fields will be required if this list is empty. Note that not all fields can be optional, such as country or name.

    Having created the PaymentSessionConfig, you’ll need to create your PaymentSessionListener, which has 3 required methods:

    void onPaymentSessionDataChanged(@NonNull PaymentSessionData data)

    This method is called whenever the PaymentSession’s data changes, e.g., when the user selects a new PaymentMethod or enters shipping info. This is a good place to update your UI:

    public class MyPaymentSessionListener
            implements PaymentSession.PaymentSessionListener {
        @Override
        public void onPaymentSessionDataChanged(@NonNull PaymentSessionData data) {
            final PaymentMethod selectedPaymentMethod = data.getPaymentMethod();
            final PaymentMethod.Card card = selectedPaymentMethod.card;
            // Display information about the selected card
    
            // Update your UI here with other data
            if (data.isPaymentReadyToCharge()) {
                // Use the data to complete your charge - see below.
            }
        }
    }
    
    class MyPaymentSessionListener : PaymentSession.PaymentSessionListener {
        override fun onPaymentSessionDataChanged(data: PaymentSessionData ) {
            val selectedPaymentMethod = data.getPaymentMethod();
            val card = selectedPaymentMethod.card
            // Display information about the selected card
    
            // Update your UI here with other data
            if (data.isPaymentReadyToCharge()) {
                // Use the data to complete your charge - see below.
            }
        }
    }
    

    This method should also check for whether or not the payment data is complete, according to the PaymentSessionConfig specified. If you receive an update for which PaymentSessionData#isPaymentReadyToCharge() returns true, you can immediately send a message to your server to complete the charge.

    void onCommunicatingStateChanged(boolean isCommunicating)

    This method is called whenever the network communication state has changed. We recommend showing a spinner or infinite progress bar when it is set to true

    public class MyPaymentSessionListener
            implements PaymentSession.PaymentSessionListener {
        @Override
        public void onCommunicatingStateChanged(boolean isCommunicating) {
            mHostActivity.getProgressBar()
                .setVisibility(isCommunicating ? View.VISIBLE : View.GONE);
        }
    }
    
    class MyPaymentSessionListener : PaymentSession.PaymentSessionListener {
        override fun onCommunicatingStateChanged(isCommunicating: Boolean) {
            mHostActivity.getProgressBar().setVisibility(
                if (isCommunicating) {
                    View.VISIBLE
                } else {
                    View.GONE
                }
            )
        }
    }
    

    void onError(int errorCode, @Nullable String errorMessage)

    This method is called whenever an error occurs when connecting to the Stripe API. The error messages should be user-surfaceable, so displaying them in an alert dialog is recommended.

    Using the PaymentSession to collect data

    Having created your PaymentSessionConfig and PaymentSessionListener, you can now initialize the PaymentSession. In the below example, we use anonymous classes to create our listener and config for simplicity.

    public class HostActivity extends Activity {
        private void setupPaymentSession() {
            mPaymentSession = new PaymentSession(this);
            final boolean paymentSessionInitialized = mPaymentSession.init(
                new PaymentSession.PaymentSessionListener() {
                    @Override
                    public void onCommunicatingStateChanged(boolean isCommunicating) {
                        mProgressBar.setVisibility(
                            isCommunicating ? View.VISIBLE : View.INVISIBLE
                        );
                    }
    
                    @Override
                    public void onError(int errorCode, @Nullable String errorMessage) {
                        mErrorDialogHandler.showError(errorMessage);
                    }
    
                    @Override
                    public void onPaymentSessionDataChanged(
                            @NonNull PaymentSessionData data) {
                        mResultTitleTextView.setVisibility(View.VISIBLE);
                        mResultTextView.setText(
                            formatStringResults(
                                mPaymentSession.getPaymentSessionData()
                            )
                        );
                    }
                },
                new PaymentSessionConfig.Builder()
                    .setPrepopulatedShippingInfo(getExampleShippingInfo())
                    .build()
            );
            if (paymentSessionInitialized) {
                mStartPaymentFlowButton.setEnabled(true);
            }
        }
    }
    

    Note that PaymentSession#init() returns a boolean value that indicates whether or not the initialization was successful. The only reason for failure is if you have not already set up a CustomerSession, which must come first.

    Once the PaymentSession has been initialized, you can use it to make the following calls.

    void presentPaymentMethodSelection()

    This method starts the PaymentMethodsActivity to allow the user to choose a stored payment method or to add new payment methods and select from among those. The payment method list will automatically be populated with existing payment methods belonging to the Customer.

    void presentShippingFlow()

    This method presents the PaymentFlowActivity to allow the user to enter shipping information, if such information is required according to your PaymentSessionConfig. Note that in order to validate the input shipping address, you’ll need to register a local BroadcastReceiver.

    public class HostActivity extends Activity {
    
        @Override
        protected void onCreate(Bundle savedInstance) {
            // Create the BroadcastReceiver in onCreate
            mBroadcastReceiver = new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    final ShippingInformation shippingInformation =
                        intent.getParcelableExtra(EXTRA_SHIPPING_INFO_DATA);
                    final Intent shippingInfoProcessedIntent =
                        new Intent(EVENT_SHIPPING_INFO_PROCESSED);
                    if (shippingInformation.getAddress() == null ||
                        !shippingInformation.getAddress().getCountry()
                            .equals(Locale.US.getCountry())) {
                        // In this example, we check to see that the country is US.
                        // Your logic will probably be more complicated.
                        shippingInfoProcessedIntent
                            .putExtra(EXTRA_IS_SHIPPING_INFO_VALID, false);
                    } else {
                        final ArrayList<ShippingMethod> shippingMethods =
                            createSampleShippingMethods();
                        shippingInfoProcessedIntent
                            .putExtra(EXTRA_IS_SHIPPING_INFO_VALID, true);
                        shippingInfoProcessedIntent
                            .putParcelableArrayListExtra(EXTRA_VALID_SHIPPING_METHODS,
                                shippingMethods);
                        shippingInfoProcessedIntent
                            .putExtra(EXTRA_DEFAULT_SHIPPING_METHOD,
                                shippingMethods.get(1));
                    }
                    LocalBroadcastManager.getInstance(PaymentSessionActivity.this)
                        .sendBroadcast(shippingInfoProcessedIntent);
                }
            };
            // Register in onCreate
            LocalBroadcastManager.getInstance(this)
                .registerReceiver(mBroadcastReceiver,
                    new IntentFilter(EVENT_SHIPPING_INFO_SUBMITTED));
        }
    
          @Override
          public void onDestroy() {
            // Don't forget to unregister in onDestroy!
            LocalBroadcastManager.getInstance(this)
                .unregisterReceiver(mBroadcastReceiver);
          }
    }
    

    When you broadcast your response to the shipping validation request, you can then send down your (potentially) dynamically created shipping methods. This allows you to validate whether or not a customer is eligible for expedited shipping, for instance, based on the input shipping address. This is also the place to filter out locations that you cannot ship to, for instance if you are restricted to domestic customers.

    Completing the purchase

    Once PaymentSession#isPaymentReadyToCharge() returns true, charge the PaymentMethod with the data in the PaymentSessionData object using a PaymentIntent. After the payment is captured, call PaymentSession#onCompleted().

    Managing PaymentSession in a host Activity

    In order to get updates for the PaymentSessionData object and to handle state during Activity lifecycle, you’ll need to hook up your PaymentSession instance to a few key parts of your host Activity lifecycle. The first is in onActivityResult()

    public class HostActivity extends Activity {
        @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
            mPaymentSession.handlePaymentData(requestCode, resultCode, data);
        }
    }
    
    class HostActivity : Activity() {
        override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
            super.onActivityResult(requestCode, resultCode, data)
            mPaymentSession.handlePaymentData(requestCode, resultCode, data)
        }
    }
    

    This is all you need to do to get updates from the various activities launched by PaymentSession. Any updates to the data will be reported to the PaymentSessionListener argument to PaymentSession#init().

    To handle lifecycle events in your activity that require the saving and restoring of state, the session can be attached and re-initialized as follows. If saving state during activity lifecycle events is important, be sure to pass the savedInstanceState to PaymentSession#init().

    public class HostActivity extends Activity {
        @Override
        public void onSaveInstanceState(Bundle outState) {
            super.onSaveInstanceState(outState);
            mPaymentSession.savePaymentSessionInstanceState(outState);
        }
    
        // Can also be re-initialized in onRestoreInstanceState
        @Override
        public void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            // other onCreate logic
            mPaymentSession = new PaymentSession(this);
            // Create your listener and your configuration
            // ...
            // It's fine if the savedInstanceState is null.
            mPaymentSession.init(mPaymentSessionListener, mPaymentSessionConfig,
                savedInstanceState);
        }
    }
    

    Finally, don’t forget to destroy the PaymentSession when your activity is destroyed. This clears out any listeners and references that you may have given the PaymentSession object that could have been put in different threads.

    public class HostActivity extends Activity {
        @Override
        protected void onDestroy() {
            mPaymentSession.onDestroy();
            super.onDestroy();
        }
    }
    
    class HostActivity : Activity() {
        override fun onDestroy() {
            mPaymentSession.onDestroy()
            super.onDestroy()
        }
    }
    

    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