Hide

Android Pay API Tutorial

This tutorial guides you through integrating Android Pay into a purchase flow, in the context of an example bike store. The tutorial provides details and complete source code to help you complete the integration steps summarized in the integration overview, with the end goal of reproducing a flow similar to the Android Pay API Process Flow.

A significant portion of the Android Pay API's functionality is provided as part of Google Play Services, the core services for developing on Android. For the purposes of this tutorial, we'll assume that you are familiar with the basic concepts and skills of application development for the Android platform. If you need to learn about Android development before getting started, work through some lessons in the Training for Android Developers.

Contents

Setup

To get started with this tutorial, create a client ID for your project, download and review the source code, and set up Google Play services.

Obtain credentials and a client ID for your app

To access the Android Pay API, you'll need to obtain a client ID for OAuth 2.0 authorization in the Google Developers Console. The client ID is generated automatically when you register your app.. You'll also need the SHA1 fingerprint in your developer's key to generate a client ID.

  1. Go to the Google Developers Console.
  2. Select a project, or create a new one.
  3. In the sidebar on the left, select APIs & auth.
  4. In the sidebar on the left, select Credentials.
  5. To register your certificate, you need to get the certificate's SHA1 fingerprint, by switching to a terminal window and running the Keytool utility:
    keytool -exportcert -alias androiddebugkey -keystore path_to_debug_or_production_keystore -list -v

    For the debug keystore, the password is android.

    The Keytool prints the fingerprint to the shell. For example:

    $ keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore -list -v
    Enter keystore password: Type "android" if using debug.keystore
    Alias name: androiddebugkey
    Creation date: Aug 27, 2012
    Entry type: PrivateKeyEntry
    Certificate chain length: 1
    Certificate[1]:
    Owner: CN=Android Debug, O=Android, C=US
    Issuer: CN=Android Debug, O=Android, C=US
    Serial number: 503bd581
    Valid from: Mon Aug 27 13:16:01 PDT 2012 until: Wed Aug 20 13:16:01 PDT 2042
    Certificate fingerprints:
       MD5:  1B:2B:2D:37:E1:CE:06:8B:A0:F0:73:05:3C:A3:63:DD
       SHA1: D8:AA:43:97:59:EE:C5:95:26:6A:07:EE:1C:37:8E:F4:F0:C8:05:C8
       SHA256: F3:6F:98:51:9A:DF:C3:15:4E:48:4B:0F:91:E3:3C:6A:A0:97:DC:0A:3F:B2:D2:E1:FE:23:57:F5:EB:AC:13:30
       Signature algorithm name: SHA1withRSA
       Version: 3

    Copy the SHA1 fingerprint, which is highlighted in the example above.

  6. Back in the Console, do the following:
    1. Click Create New Client ID.
    2. Select Installed application and Android.
    3. In the Package name field, enter your Android's app's package name.
    4. Paste the SHA1 fingerprint into the Signing certificate fingerprint text box.
    5. Click Create Client ID.
    6. In the sidebar on the left, select Consent Screen, provide values for Email address and Product name, and click Save.

Set up the sample and Google Play Services

Follow these setup instructions to import both the Google Play Services library as well as the Android Pay sample. You can find the sample via GitHub. If you don't already have it, you'll need to get Android Studio.

Modify your Manifest

Before you can use Android Pay in your app, you need to add the following tag to the <application> tag of your AndroidManifest.xml:

<application

  ...

  <!-- Enables the Android Pay API -->
  <meta-data
    android:name="com.google.android.gms.wallet.api.enabled"
    android:value="true" />
</application>

About WalletFragment

This tutorial demonstrates typical usage of the WalletFragment class. The Android Pay API provides this class to handle user events and to automate key parts of the purchase lifecycle. As illustrated in this tutorial and sample, an app would typically use instances of the wallet fragment class in two key areas of the purchase flow:

  • A purchase fragment (defined by WalletFragmentMode.BUY_BUTTON), which displays a purchase button and sets a Masked Wallet request to send to Android Pay.
  • A confirmation fragment (defined by WalletFragmentMode.SELECTION_DETAILS) , which displays "change" buttons to let users optionally modify the masked wallet, and sets the Masked Wallet.

The final calls for loading the Full Wallet are not included in the automated functionality of the WalletFragment class. Instead, these calls are encapsulated in the FullWalletConfirmationButtonFragment class of the sample app. Relevant calls are described in detail in Retrieve the Full Wallet.

Create a Masked Wallet request

You'll need to create an instance of MaskedWalletRequest to invoke the Android Pay API to retrieve the Masked Wallet information (such as shipping address, masked backing instrument number, and cart items). The MaskedWalletRequest object must be passed in when you initialize the purchase wallet fragment in the next section.

At this point, you won't have the user's chosen shipping address, so you'll need to create an estimate of the shipping costs and tax. If you set the shopping cart as shown below (highly recommended), make sure the cart total matches the sum of the line items added to the cart.

Here is an example of creating the Masked Wallet request using the builder pattern. This example assumes that Stripe is your processor, but you can also use other payment providers listed on the Android Pay home page.

MaskedWalletRequest maskedWalletRequest =
  MaskedWalletRequest.newBuilder()
  .setMerchantName(Constants.MERCHANT_NAME)
  .setPhoneNumberRequired(true)
  .setShippingAddressRequired(true)
  .setCurrencyCode("USD")
  .setShouldRetrieveWalletObjects(true)
  .setCart(Cart.newBuilder()
      .setCurrencyCode(Constants.CURRENCY_CODE_USD)
      .setTotalPrice("150.00")
      .addLineItem(LineItem.newBuilder()
              .setCurrencyCode(Constants.CURRENCY_CODE_USD)
              .setDescription(itemInfo.name)
              .setQuantity("1")
              .setUnitPrice(toDollars(context, itemInfo.priceMicros))
              .setTotalPrice("130.00")
              .build())
      .addLineItem(LineItem.newBuilder()
              .setCurrencyCode(Constants.CURRENCY_CODE_USD)
              .setDescription(Constants.DESCRIPTION_LINE_ITEM_SHIPPING)
              .setRole(LineItem.Role.SHIPPING)
              .setTotalPrice("13.00")
              .build())
      .addLineItem(LineItem.newBuilder()
              .setCurrencyCode(Constants.CURRENCY_CODE_USD)
              .setDescription(Constants.DESCRIPTION_LINE_ITEM_TAX)
              .setRole(LineItem.Role.TAX)
              .setTotalPrice("7.00")
              .build())
      .build())
  // Example using Stripe to request a payment gateway token 
  .setPaymentMethodTokenizationParameters(PaymentMethodTokenizationParameters.newBuilder()
          .setPaymentMethodTokenizationType(PaymentMethodTokenizationType.PAYMENT_GATEWAY)
          .addParameter("gateway", "stripe")
          .addParameter("stripe:publishableKey", <YOUR_STRIPE_PUBLISHABLE_KEY>)
          .addParameter("stripe:version", com.stripe.Stripe.VERSION)
          .build())
  .setEstimatedTotalPrice("150.00")
  .build();

This Masked Wallet object is the first parameter taken by the loadMaskedWallet call described in the next section.

Add a purchase button and request the Masked Wallet

Next, construct an instance of WalletFragment to add to your checkout activity:

    WalletFragmentOptions walletFragmentOptions = WalletFragmentOptions.newBuilder()
            .setEnvironment(WalletConstants.ENVIRONMENT_SANDBOX)
            .setFragmentStyle(walletFragmentStyle)
            .setTheme(WalletConstants.THEME_HOLO_LIGHT)
            .setMode(WalletFragmentMode.BUY_BUTTON)
            .build();
    mWalletFragment = SupportWalletFragment.newInstance(walletFragmentOptions);

When you initialize the purchase fragment, pass in the maskedWalletRequest that you created in the previous step, as well as the code REQUEST_CODE_MASKED_WALLET used to uniquely identify this call in the onActivityResult() callback:

  MaskedWalletRequest maskedWalletRequest =
          WalletUtil.createMaskedWalletRequest(Constants.ITEMS_FOR_SALE[mItemId]);
  WalletFragmentInitParams.Builder startParamsBuilder = WalletFragmentInitParams.newBuilder()
          .setMaskedWalletRequest(maskedWalletRequest)
          .setMaskedWalletRequestCode(REQUEST_CODE_MASKED_WALLET);
  mWalletFragment.initialize(startParamsBuilder.build());

  // add Wallet fragment to the UI
  getSupportFragmentManager().beginTransaction()
          .replace(R.id.dynamic_wallet_button_fragment, mWalletFragment)
          .commit();

When the user clicks the buy button, the Masked Wallet is retrieved and returned in the onActivityResult of the enclosing activity as shown below. If the user has not authorized this app to use their payment information or future purchases, Android Pay presents a chooser dialog, handles preauthorization, and returns control to the app.

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    // retrieve the error code, if available
    int errorCode = -1;
    if (data != null) {
        errorCode = data.getIntExtra(WalletConstants.EXTRA_ERROR_CODE, -1);
    }
    switch (requestCode) {
        case REQUEST_CODE_MASKED_WALLET:
            switch (resultCode) {
                case Activity.RESULT_OK:
                    MaskedWallet maskedWallet =
                            data.getParcelableExtra(WalletConstants.EXTRA_MASKED_WALLET);
                    launchConfirmationPage(maskedWallet);
                    break;
                case Activity.RESULT_CANCELED:
                    break;
                default:
                    handleError(errorCode);
                    break;
            }
            break;

Confirm the purchase and set the Masked Wallet

After the app obtains the Masked Wallet, it should present a confirmation page showing the total cost of the items purchased in the transaction. To do this, construct an instance of WalletFragment in Mode.SELECTION_DETAILS as shown:

WalletFragmentStyle walletFragmentStyle = new WalletFragmentStyle()
        .setMaskedWalletDetailsTextAppearance(
                R.style.BikestoreWalletFragmentDetailsTextAppearance)
        .setMaskedWalletDetailsHeaderTextAppearance(
                R.style.BikestoreWalletFragmentDetailsHeaderTextAppearance)
        .setMaskedWalletDetailsBackgroundColor(
                getResources().getColor(R.color.bikestore_white))
        .setMaskedWalletDetailsButtonBackgroundResource(
                R.drawable.bikestore_btn_default_holo_light)
        .setMaskedWalletDetailsLogoTextColor(
                getResources().getColor(R.color.wallet_dark_grey));

WalletFragmentOptions walletFragmentOptions = WalletFragmentOptions.newBuilder()
        .setEnvironment(WalletConstants.ENVIRONMENT_SANDBOX)
        .setFragmentStyle(walletFragmentStyle)
        .setTheme(WalletConstants.THEME_HOLO_LIGHT)
        .setMode(WalletFragmentMode.SELECTION_DETAILS)
        .build();
mWalletFragment = SupportWalletFragment.newInstance(walletFragmentOptions);

Note that this method also sets a number of WalletFragmentStyleoptions controlling the appearance of the following screen:

Reviewing order details

At this point the app has the shipping address and billing address, so it can calculate exact total purchase price and display it. This activity also allows the user to change the Android Pay payment instrument and change the shipping address for the purchase.

Request the Full Wallet

When the user confirms the order, you are ready to request the Full Wallet. The Full Wallet Request should have the total charge that you are requesting including exact shipping, handling and tax. You must include the GoogleTransactionId that you received in the Masked Wallet response.

Create a FullWalletRequest object that contains the various line items (including tax and shipping if necessary) and a Cart object.

FullWalletRequest fullWalletRequest = FullWalletRequest.newBuilder()
  .setGoogleTransactionId(googleTransactionId)
  .setCart(Cart.newBuilder()
          .setCurrencyCode(Constants.CURRENCY_CODE_USD)
          .setTotalPrice(toDollars(context, itemInfo.getTotalPrice()))
          .addLineItem(LineItem.newBuilder()
                  .setCurrencyCode(Constants.CURRENCY_CODE_USD)
                  .setDescription(itemInfo.name)
                  .setQuantity("1")
                  .setUnitPrice(toDollars(context, itemInfo.priceMicros))
                  .setTotalPrice(toDollars(context, itemInfo.priceMicros))
                  .build())
          .addLineItem(LineItem.newBuilder()
                  .setCurrencyCode(Constants.CURRENCY_CODE_USD)
                  .setDescription(Constants.DESCRIPTION_LINE_ITEM_SHIPPING)
                  .setRole(LineItem.Role.SHIPPING)
                  .setTotalPrice(toDollars(context, itemInfo.shippingPriceMicros))
                  .build())
          .addLineItem(LineItem.newBuilder()
                  .setCurrencyCode(Constants.CURRENCY_CODE_USD)
                  .setDescription(Constants.DESCRIPTION_LINE_ITEM_TAX)
                  .setRole(LineItem.Role.TAX)
                  .setTotalPrice(toDollars(context, itemInfo.taxMicros))
                  .build())
          .build())
  .build();

Initialize the Google API client

The GoogleApiClient object wraps a ServiceConnection to Google Play services and is used to communicate with the Android Pay API. Though it is not required when working with WalletFragment, you'll need to initialize it to retrieve the Full Wallet.

The GoogleApiClient becomes functional after the asynchronous connection has been established with the service, indicating that:

  • Google Play services is running on the device and your Activity successfully bound the service connection,
  • the user has selected an account that they wish to use with your app, and
  • the user's account has granted the permissions that your app is requesting.

The following code snippet (from a fragment) illustrates how to instantiate a Google API client for your Android Pay app and then connect:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    ...

mGoogleApiClient = new GoogleApiClient.Builder(getActivity())
        .addConnectionCallbacks(this)
        .addOnConnectionFailedListener(this)
        .addApi(Wallet.API, new Wallet.WalletOptions.Builder()
            .setEnvironment(Constants.WALLET_ENVIRONMENT)
            .setTheme(WalletConstants.THEME_HOLO_LIGHT)
            .build())
        .build();

      ...

@Override
public void onStart() {
    super.onStart();

    // Connect to Google Play Services
    mGoogleApiClient.connect();
}
  • Environment is a parameter in WalletOptions whose value indicates the environment (production or sandbox) in which the server supporting the app is running. Its value may be WalletConstants.ENVIRONMENT_PRODUCTION or WalletConstants.ENVIRONMENT_SANDBOX For testing and development always use only the sandbox environment.
  • Theme is another WalletOptions parameter to set the UI theme for the app. A value of WalletConstants.THEME_HOLO_LIGHT optionally sets a light UI theme instead of the default dark theme (WalletConstants.THEME_HOLO_DARK).
  • connectionCallBacks is an object that implements the interface com.google.android.gms.common.GooglePlayServicesClient.ConnectionCallbacks. Android calls the onActivityResult method of the object to notify the app when it connects and disconnects with Google Play Services.
  • OnconnectionFailedListener is an object that implements the interface com.google.android.gms.common.GooglePlayServicesClient.OnConnectionFailedListener. Android calls the onActivityResult method of the object to notify the app of a failed attempt to connect with Google Play Services.

This call results in a callback to one of two methods: OnConnectionCallbackListener.onConnected if the connection succeeded, or OnConnectionFailedListener.onConnectionFailed if the connection failed. The callback receives an error code which describes the reason for the failure.

Retrieve the Full Wallet

Once you have constructed the Full Wallet request instance, call the loadFullWallet method. When the Full Wallet request is completed, Google calls the onActivityResult method, passing the intent result.

private void getFullWallet() {
    Wallet.Payments.loadFullWallet(mGoogleApiClient,
            WalletUtil.createFullWalletRequest(mItemInfo,
                    mMaskedWallet.getGoogleTransactionId()),
            REQUEST_CODE_RESOLVE_LOAD_FULL_WALLET);

...

public void onActivityResult(int requestCode, int resultCode, Intent data) {
    mProgressDialog.hide();

    int errorCode = -1;
    if (data != null) {
        errorCode = data.getIntExtra(WalletConstants.EXTRA_ERROR_CODE, -1);
    }

    switch (requestCode) {
        case REQUEST_CODE_RESOLVE_ERR:
            if (resultCode == Activity.RESULT_OK) {
                mGoogleApiClient.connect();
            } else {
                handleUnrecoverableGoogleWalletError(errorCode);
            }
            break;
        case REQUEST_CODE_RESOLVE_LOAD_FULL_WALLET:
            switch (resultCode) {
                case Activity.RESULT_OK:
                    if (data.hasExtra(WalletConstants.EXTRA_FULL_WALLET)) {
                        FullWallet fullWallet =
                                data.getParcelableExtra(WalletConstants.EXTRA_FULL_WALLET);
                        fetchTransactionStatus(fullWallet);

Once you have retrieved the Full Wallet in the onActivityResult() callback, you have enough information to proceed to payment processing for this transaction. You can access the details of the payment credentials from the Full Wallet as shown below:

// Get payment method token
PaymentMethodToken token = fullWallet.getPaymentMethodToken();

// Get the JSON of the token object as a String
String tokenJSON = token.getToken();

You can then send the payment token to your processor to be charged. Please see your processor documentation for further details.

Handle errors

Since a typical activity or fragment can call multiple Google API methods, it's advisable to have a common code block in onActivityResult()that fetches error codes.

    int errorCode = -1;
    if (data != null) {
        errorCode = data.getIntExtra(
            WalletConstants.EXTRA_ERROR_CODE, -1);
    }

After you fetch the codes, you can handle errors in the default block of a switch/case statement by calling handleError(errorCode):

void handleError(int errorCode) {
    switch (errorCode) {
        case WalletConstants.ERROR_CODE_SPENDING_LIMIT_EXCEEDED:
            Toast.makeText(getActivity(),
                    getString(R.string.spending_limit_exceeded, errorCode),
                    Toast.LENGTH_LONG).show();
            break;
        case WalletConstants.ERROR_CODE_INVALID_PARAMETERS:
        case WalletConstants.ERROR_CODE_AUTHENTICATION_FAILURE:
        case WalletConstants.ERROR_CODE_BUYER_ACCOUNT_ERROR:
        case WalletConstants.ERROR_CODE_MERCHANT_ACCOUNT_ERROR:
        case WalletConstants.ERROR_CODE_SERVICE_UNAVAILABLE:
        case WalletConstants.ERROR_CODE_UNSUPPORTED_API_VERSION:
        case WalletConstants.ERROR_CODE_UNKNOWN:
        default:
            // unrecoverable error
            mGoogleWalletDisabled = true;
            displayGoogleWalletErrorToast(errorCode);
            break;
    }
}

You can augment this implementation to handle your app's specific needs. For example, if you support multiple payment methods, you can choose to use another payment method for unrecoverable errors.

Send feedback about...

Android Pay