Using Android basic integration

    Accept cards with the Android SDK’s prebuilt UI.

    Use this integration if you want a prebuilt UI that:

    • Accepts credit cards
    • Saves and displays cards for reuse
    • Can be customized to fit your app’s look and feel using an Android theme
    • Launches full-screen activities to collect payment details, shipping address, and shipping method:

    These activities can also be used individually.

    This integration requires both server and client-side steps to implement.

    1 Set up Stripe Client-side Server-side

    First, you need a Stripe account. Register now.

    Server-side

    This integration requires endpoints on your server that talk to the Stripe API. Use our official libraries for access to the Stripe API from your server:

    Available as a gem:

    sudo gem install stripe

    If you use bundler, you can use this line:

    gem 'stripe', :git => 'https://github.com/stripe/stripe-ruby'

    Available through pip:

    pip install --upgrade stripe

    Alternatively, you can also use easy_install:

    easy_install --upgrade stripe

    The PHP library can be installed via Composer:

    composer require stripe/stripe-php

    Alternatively, you can download the source directly.

    For Gradle, add the following dependency to your build.gradle:

    implementation "com.stripe:stripe-java:{VERSION}"
    (Replace {VERSION} with the actual version number you want to use. You can find the most recent version on Maven Repository or GitHub.)

    For Maven, add the following dependency to your POM:

    <dependency>
      <groupId>com.stripe</groupId>
      <artifactId>stripe-java</artifactId>
      <version>{VERSION}</version>
    </dependency>
    (Replace {VERSION} with the actual version number you want to use. You can find the most recent version on Maven Repository or GitHub.)

    In other environments, manually install the following JARs:

    Install via npm:

    npm install stripe

    Install via go:

    go get github.com/stripe/stripe-go

    Then import the package:

    import (
      "github.com/stripe/stripe-go"
    )

    Install via dotnet:

    dotnet add package Stripe.net
    dotnet restore

    Or using NuGet:

    PM> Install-Package Stripe.net

    Client-side

    The Android SDK is open source and fully documented.

    To install the SDK, add stripe-android to the dependencies block of your app/build.gradle file:

    apply plugin: 'com.android.application'
    
    android { ... }
    
    dependencies {
      // ...
    
      // Stripe Android SDK
      implementation 'com.stripe:stripe-android:12.4.0'
    }
    

    Configure the SDK with your Stripe publishable key so that it can make requests to the Stripe API, such as in your Application subclass:

    import com.stripe.android.PaymentConfiguration
    
    class MyApp: Application() {
        override fun onCreate() {
            super.onCreate()
            PaymentConfiguration.init(applicationContext, "pk_test_TYooMQauvdEDq54NiTphI7jx")
        }
    }
    
    import com.stripe.android.PaymentConfiguration;
    
    public class MyApp extends Application {
        @Override
        public void onCreate() {
            super.onCreate();
            PaymentConfiguration.init(getApplicationContext(), "pk_test_TYooMQauvdEDq54NiTphI7jx");
        }
    }
    

    2 Set up an ephemeral key Client-side Server-side

    In order for the SDK to save and retrieve credit cards for later use, create a single Stripe Customer object for each of your users. When you create a new user or account on your server, create a corresponding Customer object at the same time, even if you don’t collect payment information from your users when they sign up. This ensures that your application has a matching Customer for each user.

    For security, the Customer API is not directly accessible from the client. Instead, your server provides the SDK with an ephemeral key—a short-lived API key with restricted access to the Customer API. You can think of an ephemeral key as a session, authorizing the SDK to retrieve and update a specific Customer object s for the duration of the session.

    Server-side

    To provide an ephemeral key to the SDK, 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 will specify the version of the Stripe API that it expects the response to come from. Your endpoint must 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. 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():
        api_version = request.args['api_version']
        customerId = session['customerId']
        key = stripe.EphemeralKey.create(customer=customerId, api_version="2017-05-25")
        return jsonify(key)
    // This assumes that $customerId has been set appropriately from session data
    if (!isset($_POST['api_version']))
    {
        exit(http_response_code(400));
    }
    try {
        $key = \Stripe\EphemeralKey::create(
          ["customer" => $customerId],
          ["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;
      }
      // 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();
      });
    });
    // 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 {
                // Retrieve the customer id from your session for example
                Map<String, Object> options = new HashMap<String, Object>();
                options.put("customer", customerId);
    
                EphemeralKey key = EphemeralKey.create(options, requestOptions);
                return key.getRawJson();
            } catch (StripeException e) {
                response.status(500);
                return e;
            }
        }
    });
    // 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)
    }

    Client-side

    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, make your API client class implement the EphemeralKeyProvider interface, which defines a single method, createEphemeralKey(). When implementing this method, pass the apiVersion parameter along to your ephemeral keys endpoint. 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 ?: "")
                        }
                    })
        }
    }
    

    3 Set up a CustomerSession Client-side

    After creating an EphemeralKeyProvider, initialize a CustomerSession. A CustomerSession talks to your backend to retrieve an ephemeral key for your Customer with its EphemeralKeyProvider, and uses that key to manage retrieving and updating the Customer’s payment methods on your behalf.

    You can also use CustomerSession with your own custom UI to retrieve or refresh the Customer, and list, add, and delete their payment methods.

    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())
        }
    }
    

    To reduce load times, preload your customer’s information by initializing CustomerSession before they enter your payment flow.

    If your current user logs out of the app, clear the current CustomerSession singleton using the clearInstance method. When a new user logs in, re-initialize the instance. On your backend, create and return a new ephemeral key for the Customer object associated with the new user.

    4 Set up a PaymentSession Client-side

    The core of this integration is the PaymentSession class. It uses CustomerSession to launch full-screen activities to collect and store payment information, 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 PaymentSessionListener when your UI should change.

    To work with PaymentSession, you’ll need to:

    1. Create a PaymentSessionConfig object
    2. Implement a PaymentSessionListener

    PaymentSessionConfg

    The PaymentSessionConfig object is created using a Builder. All of the Builder’s fields are optional. See the API reference for details on each method.

    private PaymentSessionConfg createPaymentSessionConfig() {
        return PaymentSessionConfig.Builder()
    
                // hide the phone field on the shipping information form
                .setHiddenShippingInfoFields(
                    ShippingInfoWidget.CustomizableShippingField.PHONE_FIELD
                )
    
                // make the address line 2 field optional
                .setOptionalShippingInfoFields(
                    ShippingInfoWidget.CustomizableShippingField.ADDRESS_LINE_TWO_FIELD
                )
    
                // specify an address to pre-populate the shipping information form
                .setPrepopulatedShippingInfo(ShippingInformation(
                    new Address.Builder()
                        .setLine1("123 Market St")
                        .setCity("San Francisco")
                        .setState("CA")
                        .setPostalCode("94107")
                        .setCountry("US")
                        .build(),
                    "Jenny Rosen",
                    "4158675309"
                ))
    
                // collect shipping information
                .setShippingInfoRequired(true)
    
                // collect shipping method
                .setShippingMethodsRequired(true)
    
                // specify the payment method types that the customer can use;
                // defaults to PaymentMethod.Type.Card
                .setPaymentMethodTypes(
                    Arrays.asList(PaymentMethod.Type.Card)
                )
    
                // specify a layout to display under the payment collection form
                .setAddPaymentMethodFooter(R.layout.add_payment_method_footer)
                .build();
    }
    
    private fun createPaymentSessionConfig(): PaymentSessionConfg {
        return PaymentSessionConfig.Builder()
    
                // hide the phone field on the shipping information form
                .setHiddenShippingInfoFields(
                    ShippingInfoWidget.CustomizableShippingField.PHONE_FIELD
                )
    
                // make the address line 2 field optional
                .setOptionalShippingInfoFields(
                    ShippingInfoWidget.CustomizableShippingField.ADDRESS_LINE_TWO_FIELD
                )
    
                // specify an address to pre-populate the shipping information form
                .setPrepopulatedShippingInfo(ShippingInformation(
                    Address.Builder()
                        .setLine1("123 Market St")
                        .setCity("San Francisco")
                        .setState("CA")
                        .setPostalCode("94107")
                        .setCountry("US")
                        .build(),
                    "Jenny Rosen",
                    "4158675309"
                ))
    
                // collect shipping information
                .setShippingInfoRequired(true)
    
                // collect shipping method
                .setShippingMethodsRequired(true)
    
                // specify the payment method types that the customer can use;
                // defaults to PaymentMethod.Type.Card
                .setPaymentMethodTypes(
                    listOf(PaymentMethod.Type.Card)
                )
    
                // specify a layout to display under the payment collection form
                .setAddPaymentMethodFooter(R.layout.add_payment_method_footer)
                .build()
    }
    

    PaymentSessionListener

    After creating the PaymentSessionConfig, you’ll need to implement PaymentSessionListener.

    public class MyPaymentSessionListener
            implements PaymentSession.PaymentSessionListener {
        // Called whenever the PaymentSession's data changes,
        // e.g. when the user selects a new `PaymentMethod` or enters shipping info.
        @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.
            }
        }
    
        @Override
        public void onCommunicatingStateChanged(boolean isCommunicating) {
        }
    
        @Override
        public void onError(int errorCode, @NotNull String errorMessage) {
        }
    }
    
    class MyPaymentSessionListener : PaymentSession.PaymentSessionListener {
        // Called whenever the PaymentSession's data changes,
        // e.g. when the user selects a new `PaymentMethod` or enters shipping info.
        override fun onPaymentSessionDataChanged(data: PaymentSessionData) {
            val card = data.paymentMethod?.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.
            }
        }
    
        override fun onCommunicatingStateChanged(isCommunicating: Boolean) {}
    
        override fun onError(errorCode: Int, errorMessage: String) {}
    }
    

    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) {
            if (isCommunicating) {
                // update UI to indicate that network communication is in progress
            } else {
                // update UI to indicate that network communication has completed
            }
        }
    }
    
    class MyPaymentSessionListener : PaymentSession.PaymentSessionListener {
        override fun onCommunicatingStateChanged(isCommunicating: Boolean) {
            if (isCommunicating) {
                // update UI to indicate that network communication is in progress
            } else {
                // update UI to indicate that network communication has completed
            }
        }
    }
    

    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.

    Initialize a PaymentSession

    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 PaymentSession paymentSession;
        private Button startPaymentFlowButton;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            paymentSession = new PaymentSession(this);
            setupPaymentSession();
        }
    
        private void setupPaymentSession() {
            final boolean paymentSessionInitialized = paymentSession.init(
                    new PaymentSession.PaymentSessionListener() {
                        @Override
                        public void onCommunicatingStateChanged(boolean isCommunicating) {
                            // update UI, such as hiding or showing a progress bar
                        }
    
                        @Override
                        public void onError(int errorCode, @Nullable String errorMessage) {
                            // handle error
                        }
    
                        @Override
                        public void onPaymentSessionDataChanged(@NonNull PaymentSessionData data) {
                            final PaymentMethod paymentMethod = data.getPaymentMethod();
                            // use paymentMethod
                        }
                    },
                    new PaymentSessionConfig.Builder()
                            .setPrepopulatedShippingInfo(getDefaultShippingInfo())
                            .build()
            );
            if (paymentSessionInitialized) {
                startPaymentFlowButton.setEnabled(true);
            }
        }
    
        @Override
        protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
            if (data != null) {
                paymentSession.handlePaymentData(requestCode, resultCode, data);
            }
        }
    
        @NonNull
        private ShippingInformation getDefaultShippingInfo() {
            // optionally specify default shipping address
            return new ShippingInformation();
        }
    }
    
    class HostActivity : Activity() {
        private lateinit var paymentSession: PaymentSession
        private lateinit var startPaymentFlowButton: Button
    
        private val defaultShippingInfo: ShippingInformation
            // optionally specify default shipping address
            get() = ShippingInformation()
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            paymentSession = PaymentSession(this)
        }
    
        private fun setupPaymentSession() {
            val paymentSessionInitialized = paymentSession.init(
                object : PaymentSession.PaymentSessionListener {
                    override fun onCommunicatingStateChanged(isCommunicating: Boolean) {
                        // update UI, such as hiding or showing a progress bar
                    }
    
                    override fun onError(errorCode: Int, errorMessage: String) {
                        // handle error
                    }
    
                    override fun onPaymentSessionDataChanged(data: PaymentSessionData) {
                        val paymentMethod: PaymentMethod? = data.paymentMethod
                        // use paymentMethod
                    }
                },
                PaymentSessionConfig.Builder()
                    .setPrepopulatedShippingInfo(defaultShippingInfo)
                    .build()
            )
            if (paymentSessionInitialized) {
                startPaymentFlowButton.isEnabled = true
            }
        }
    
        override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
            super.onActivityResult(requestCode, resultCode, data)
            if (data != null) {
                paymentSession.handlePaymentData(requestCode, resultCode, data)
            }
        }
    }
    

    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.

    5 Collect the customer's payment and shipping details Client-side

    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 customer to choose a saved payment method, using CustomerSession as its data source. If the Add new card button is tapped, or there are no existing payment methods, AddPaymentMethodActivity is launched to add a credit card.

    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 {
        private static final List<ShippingMethod> SHIPPING_METHODS =
                Arrays.asList(
                        new ShippingMethod("UPS Ground", "ups-ground",
                                0, "USD", "Arrives in 3-5 days"),
                        new ShippingMethod("FedEx", "fedex",
                                599, "USD", "Arrives tomorrow")
                );
    
        private BroadcastReceiver broadcastReceiver;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstance) {
            super.onCreate(savedInstance);
    
            // Create the BroadcastReceiver in onCreate
            broadcastReceiver = new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    final ShippingInformation shippingInformation =
                            intent.getParcelableExtra(PaymentFlowExtras.EXTRA_SHIPPING_INFO_DATA);
                    final Intent shippingInfoProcessedIntent =
                            new Intent(PaymentFlowExtras.EVENT_SHIPPING_INFO_PROCESSED);
                    if (shippingInformation != null && (shippingInformation.getAddress() == null ||
                            !Locale.US.getCountry().equals(
                                    shippingInformation.getAddress().getCountry()))) {
                        // In this example, we check to see that the country is US.
                        // Your logic will probably be more complicated.
                        shippingInfoProcessedIntent
                                .putExtra(PaymentFlowExtras.EXTRA_IS_SHIPPING_INFO_VALID, false);
                    } else {
                        shippingInfoProcessedIntent
                                .putExtra(PaymentFlowExtras.EXTRA_IS_SHIPPING_INFO_VALID, true);
                        shippingInfoProcessedIntent.putParcelableArrayListExtra(
                                PaymentFlowExtras.EXTRA_VALID_SHIPPING_METHODS,
                                new ArrayList<>(SHIPPING_METHODS)
                        );
                        shippingInfoProcessedIntent
                                .putExtra(PaymentFlowExtras.EXTRA_DEFAULT_SHIPPING_METHOD,
                                        SHIPPING_METHODS.get(1));
                    }
                    LocalBroadcastManager.getInstance(HostActivity.this)
                            .sendBroadcast(shippingInfoProcessedIntent);
                }
            };
            // Register in onCreate
            LocalBroadcastManager.getInstance(this)
                    .registerReceiver(broadcastReceiver,
                            new IntentFilter(PaymentFlowExtras.EVENT_SHIPPING_INFO_SUBMITTED));
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            // Don't forget to unregister in onDestroy!
            LocalBroadcastManager.getInstance(this)
                    .unregisterReceiver(broadcastReceiver);
        }
    }
    
    class HostActivity : Activity() {
        private lateinit var broadcastReceiver: BroadcastReceiver
    
        override fun onCreate(savedInstance: Bundle?) {
            super.onCreate(savedInstance)
    
            // Create the BroadcastReceiver in onCreate
            broadcastReceiver = object : BroadcastReceiver() {
                override fun onReceive(context: Context, intent: Intent) {
                    val shippingInformation: ShippingInformation? =
                        intent.getParcelableExtra(PaymentFlowExtras.EXTRA_SHIPPING_INFO_DATA)
                    val shippingInfoProcessedIntent = Intent(PaymentFlowExtras.EVENT_SHIPPING_INFO_PROCESSED)
                    if (shippingInformation != null &&
                        (shippingInformation.address == null ||
                            Locale.US.country != shippingInformation.address?.country)) {
                        // In this example, we check to see that the country is US.
                        // Your logic will probably be more complicated.
                        shippingInfoProcessedIntent
                            .putExtra(PaymentFlowExtras.EXTRA_IS_SHIPPING_INFO_VALID, false)
                    } else {
                        shippingInfoProcessedIntent
                            .putExtra(PaymentFlowExtras.EXTRA_IS_SHIPPING_INFO_VALID, true)
                        shippingInfoProcessedIntent.putParcelableArrayListExtra(
                            PaymentFlowExtras.EXTRA_VALID_SHIPPING_METHODS,
                            ArrayList(SHIPPING_METHODS)
                        )
                        shippingInfoProcessedIntent
                            .putExtra(PaymentFlowExtras.EXTRA_DEFAULT_SHIPPING_METHOD,
                                SHIPPING_METHODS[1])
                    }
                    LocalBroadcastManager.getInstance(this@HostActivity)
                        .sendBroadcast(shippingInfoProcessedIntent)
                }
            }
            // Register in onCreate
            LocalBroadcastManager.getInstance(this)
                .registerReceiver(broadcastReceiver,
                    IntentFilter(PaymentFlowExtras.EVENT_SHIPPING_INFO_SUBMITTED))
        }
    
        override fun onDestroy() {
            super.onDestroy()
            // Don't forget to unregister in onDestroy!
            LocalBroadcastManager.getInstance(this)
                .unregisterReceiver(broadcastReceiver)
        }
    
        companion object {
            private val SHIPPING_METHODS = Arrays.asList(
                ShippingMethod("UPS Ground", "ups-ground",
                    0, "USD", "Arrives in 3-5 days"),
                ShippingMethod("FedEx", "fedex",
                    599, "USD", "Arrives tomorrow")
            )
        }
    }
    

    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.

    6 Complete the payment Client-side Server-side

    Once PaymentSession#isPaymentReadyToCharge() returns true, submit the payment to Stripe using a Payment Intent. Stripe uses this payment object to track and handle all the states of the payment—even when the bank requires customer intervention, like additional card authentication—until the payment completes.

    Server-side

    On your server, make an endpoint that creates a PaymentIntent with an amount and currency and returns its client secret to your client.

    Always decide how much to charge on the server side, a trusted environment, as opposed to the client. This prevents malicious customers from being able to choose their own prices.

    curl https://api.stripe.com/v1/payment_intents \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -d amount=1099 \
      -d currency=usd
    
    # Set your secret key: remember to change this to your live secret key in production
    # See your keys here: https://dashboard.stripe.com/account/apikeys
    Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
    
    intent = Stripe::PaymentIntent.create({
        amount: 1099,
        currency: 'usd',
    })
    client_secret = payment_intent['client_secret']
    # Pass the client secret to the client
    
    # Set your secret key: remember to change this to your live secret key in production
    # See your keys here: https://dashboard.stripe.com/account/apikeys
    stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
    
    intent = stripe.PaymentIntent.create(
      amount=1099,
      currency='usd',
    )
    client_secret = intent.client_secret
    # Pass the client secret to the client
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
    
    $intent = \Stripe\PaymentIntent::create([
        'amount' => 1099,
        'currency' => 'usd',
    ]);
    $client_secret = $intent->client_secret
    // Pass the client secret to the client
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";
    
    import com.stripe.model.PaymentIntent;
    import com.stripe.param.PaymentIntentCreateParams;
    
    PaymentIntentCreateParams createParams = new PaymentIntentCreateParams.Builder()
            .setCurrency("usd").setAmount(new Long(1099))
            .build();
    
    PaymentIntent intent = PaymentIntent.create(createParams);
    String clientSecret = intent.getClientSecret();
    // Pass the client secret to the client
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
    
    (async () => {
      const paymentIntent = await stripe.paymentIntents.create({
        amount: 1099,
        currency: 'usd',
      });
      const clientSecret = paymentIntent.client_secret
      // Pass the client secret to the client
    
    })();
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    stripe.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"
    
    params := &stripe.PaymentIntentParams{
      Amount: stripe.Int64(1099),
      Currency: stripe.String(string(stripe.CurrencyUSD)),
    }
    paymentintent.New(params)
    // Pass the client secret to the client
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";
    
    var service = new PaymentIntentService();
    var options = new PaymentIntentCreateOptions
    {
        Amount = 1099,
        Currency = "usd",
    };
    service.Create(options);
    // Pass the client secret to the client
    

    Client-side

    1. Request a PaymentIntent from your server
    2. Assemble a ConfirmPaymentIntentParams object with the PaymentIntent client secret from your server and the id of PaymentSessionData#paymentMethod obtained from PaymentSessionListenerImpl#onPaymentSessionDataChanged().
    3. Call the Stripe confirmPayment method to confirm the payment.
    public class CheckoutActivity extends Activity {
        private static final String RETURN_URL = "...";
    
        private void confirmPayment(
            @NonNull String clientSecret,
            @NonNull String paymentMethodId
        ) {
            stripe.confirmPayment(this,
                ConfirmPaymentIntentParams.createWithPaymentMethodId(
                    paymentMethodId,
                    clientSecret,
                    RETURN_URL
                )
            );
        }
    }
    
    class CheckoutActivity : Activity() {
        private fun confirmPayment(clientSecret: String, paymentMethodId: String) {
            stripe.confirmPayment(this,
                ConfirmPaymentIntentParams.createWithPaymentMethodId(
                    paymentMethodId,
                    clientSecret,
                    RETURN_URL
                )
            )
        }
    
        companion object {
            private const val RETURN_URL = "..."
        }
    }
    

    When the payment completes, onSuccess is called and the value of the returned PaymentIntent’s status is Succeeded. Any other value indicates the payment was not successful. Inspect lastPaymentError to determine the cause. End the payment session by calling PaymentSession#onCompleted().

    7 Manage PaymentSession in a host Activity Client-side

    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);
            paymentSession.handlePaymentData(requestCode, resultCode, data);
        }
    }
    
    class HostActivity : Activity() {
        override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
            super.onActivityResult(requestCode, resultCode, data)
            paymentSession.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 {
        private PaymentSession paymentSession;
    
        @Override
        public void onSaveInstanceState(@NonNull Bundle outState) {
            super.onSaveInstanceState(outState);
            paymentSession.savePaymentSessionInstanceState(outState);
        }
    
        // Can also be re-initialized in onRestoreInstanceState
        @Override
        public void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            // other onCreate logic
            paymentSession = new PaymentSession(this);
            // Create your listener and your configuration
            // ...
            // It's fine if the savedInstanceState is null.
            paymentSession.init(
                    createPaymentSessionListener(),
                    createPaymentSessionConfig(),
                    savedInstanceState
            );
        }
    
        @NonNull
        private PaymentSession.PaymentSessionListener createPaymentSessionListener() {
            return new PaymentSession.PaymentSessionListener() {
                @Override
                public void onCommunicatingStateChanged(boolean isCommunicating) {
                }
    
                @Override
                public void onError(int errorCode, @NotNull String errorMessage) {
                }
    
                @Override
                public void onPaymentSessionDataChanged(@NotNull PaymentSessionData data) {
                }
            };
        }
    
        @NonNull
        private PaymentSessionConfig createPaymentSessionConfig() {
            return new PaymentSessionConfig.Builder()
                    .build();
        }
    }
    
    class HostActivity : Activity() {
        private lateinit var paymentSession: PaymentSession
    
        public override fun onSaveInstanceState(outState: Bundle) {
            super.onSaveInstanceState(outState)
            paymentSession.savePaymentSessionInstanceState(outState)
        }
    
        // Can also be re-initialized in onRestoreInstanceState
        public override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            // other onCreate logic
            paymentSession = PaymentSession(this)
            // Create your listener and your configuration
            // ...
            // It's fine if the savedInstanceState is null.
            paymentSession.init(
                createPaymentSessionListener(),
                createPaymentSessionConfig(),
                savedInstanceState
            )
        }
    
        private fun createPaymentSessionListener(): PaymentSession.PaymentSessionListener {
            return object : PaymentSession.PaymentSessionListener {
                override fun onCommunicatingStateChanged(isCommunicating: Boolean) {}
    
                override fun onError(errorCode: Int, errorMessage: String) {}
    
                override fun onPaymentSessionDataChanged(data: PaymentSessionData) {}
            }
        }
    
        private fun createPaymentSessionConfig(): PaymentSessionConfig {
            return PaymentSessionConfig.Builder()
                .build()
        }
    }
    

    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() {
            paymentSession.onDestroy();
            super.onDestroy();
        }
    }
    
    class HostActivity : Activity() {
        override fun onDestroy() {
            paymentSession.onDestroy()
            super.onDestroy()
        }
    }
    

    8 Test the integration

    By this point you should have a basic card integration that collects card details and makes a payment.

    There are several test cards you can use in test mode to make sure this integration is ready. Use them with any CVC, postal code, and future expiration date.

    Number Description
    4242424242424242 Succeeds and immediately processes the payment.
    4000002500003155 Requires authentication. Stripe will trigger a modal asking for the customer to authenticate.
    4000000000009995 Always fails with a decline code of insufficient_funds.

    For the full list of test cards see our guide on testing.

    Optional Handle post-payment events

    Stripe sends a payment_intent.succeeded event when the payment completes. Use the Dashboard, a custom webhook, or a partner solution to receive these events and run actions, like sending an order confirmation email to your customer, logging the sale in a database, or starting a shipping workflow.

    Listen for these events rather than waiting on a callback from the client. On the client, the customer could close the browser window or quit the app before the callback executes. Setting up your integration to listen for asynchronous events also makes it easier to accept more payment methods in the future, like direct debit.

    Receive events and run business actions

    Manually

    Use the Stripe Dashboard to view all your Stripe payments, send email receipts, handle payouts, or retry failed payments.

    Custom Code

    Build a webhook handler to listen for events and build custom asynchronous payment flows. Test and debug your webhook integration locally with the Stripe CLI.

    Prebuilt Apps

    Handle common business events, like shipping and inventory management, by integrating a partner application.

    Optional Use individual activities Client-side

    The full-screen activities that Basic Integration displays are also available to use individually, without using PaymentSession.

    AddPaymentMethodActivity

    Use AddPaymentMethodActivity to collect card details from your customer. To launch the activity, use AddPaymentMethodActivityStarter#startForResult().

    PaymentMethodsActivity

    Use PaymentMethodsActivity to display the customer’s payment methods and allow them to select one or add new ones. First, follow the steps to set up an ephemeral key and CustomerSession. Then, use PaymentMethodsActivityStarter#startForResult() to launch the activity.

    public class HostActivity extends Activity {
        private void launchPaymentMethodsActivity() {
            new PaymentMethodsActivityStarter(this).startForResult();
        }
    }
    
    class HostActivity : Activity() {
        private fun launchPaymentMethodsActivity() {
            PaymentMethodsActivityStarter(this).startForResult()
        }
    }
    

    In your host Activity or Fragment, implement Activity#onActivityResult and handle the result.

    public class HostActivity extends Activity {
        @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
            if (requestCode == PaymentMethodsActivityStarter.REQUEST_CODE) {
                final PaymentMethodsActivityStarter.Result result =
                        PaymentMethodsActivityStarter.Result.fromIntent(data);
                final PaymentMethod paymentMethod = result != null ?
                        result.paymentMethod : null;
    
                // use paymentMethod
            }
        }
    }
    
    class HostActivity : Activity() {
        override fun onActivityResult(
            requestCode: Int, resultCode: Int, data: Intent?
        ) {
            super.onActivityResult(requestCode, resultCode, data)
            if (requestCode == PaymentMethodsActivityStarter.REQUEST_CODE && data != null) {
                val result = PaymentMethodsActivityStarter.Result.fromIntent(data)
                val paymentMethod = result?.paymentMethod
    
                // use paymentMethod
            }
        }
    }
    

    Optional Customize the UI Client-side

    The full-screen activities can be customized to fit your app’s look and feel by defining a StripeDefaultTheme theme that extends StripeBaseTheme. See below for an example as well as explanation of how each theme attribute is applied.

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <style name="StripeDefaultTheme" parent="StripeBaseTheme">
            <!-- Applied to toolbar background -->
            <item name="colorPrimary">@color/primary</item>
    
            <!-- Applied to status bar -->
            <item name="colorPrimaryDark">@color/primaryDark</item>
    
            <!-- Applied to inactive Shipping Method name and amount -->
            <item name="android:textColorPrimary">@color/text</item>
    
            <!-- Applied to inactive Shipping Method description -->
            <item name="android:textColorSecondary">@color/textPrimary</item>
    
            <!--
                Applied to Shipping Information drop-down items;
                add Payment Method footer text color
            -->
            <item name="android:textColor">@color/textPrimary</item>
    
            <!-- Applied to toolbar foreground/text -->
            <item name="titleTextColor">@color/title</item>
    
            <!--
                Applied to selected Payment Method; active field; links;
                add new Payment Method call-to-action
            -->
            <item name="colorAccent">@color/accent</item>
    
            <!-- Applied to: Payment Method icon; inactive form field highlight -->
            <item name="colorControlNormal">@color/accent</item>
    
            <!-- Applied to form field text -->
            <item name="android:editTextColor">@color/textPrimary</item>
    
            <item name="editTextColor">@color/textPrimary</item>
        </style>
    </resources>
    

    For more information on Android themes, see the Android Styles and Themes guide.

    See also

    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