Build a Fast Checkout Experience on Android with Google Pay

1. Introduction

Google Pay API gives users the opportunity to pay everywhere, using the payment information stored in their Google Accounts. In this lab, you make use of Google Pay's client library for Android to improve the checkout experience of a simplified sample mobile application, by creating a faster, more convenient, and safer experience, which in turn leads to more conversions and happier customers.

Auto T-Shirt Shop is an innovative store that leverages the latest advances in artificial intelligence and using information like style preferences, weather, time of the year, and fashion trends, suggests you the most appropriate item to purchase.

Metrics on engagement are over the roof. Unfortunately, numbers also reflect a large number of abandonments during the checkout process. Determined to tackle that, one of the owners of the project recalls having seen a video showing the promising results that Google Pay yielded for other similar sites, so they decide to give it a go and trust you to take care of the integration.

Overview

This codelab walks you through integrating Google Pay into an existing application, including determining whether a user is able to pay using a payment method supported by Google Pay, the placement and design of the payment button and the execution of the transaction.

Sample application

In this codelab, you will learn how to:

  1. Integrate Google Pay in an existing Android application
  2. Determine whether a user is ready to pay using Google Pay
  3. Add the Google Pay button to your user interface
  4. Complete a payment operation with Google Pay

Prerequisites

  • Git
  • Android Studio or an alternative development environment for Android applications
  • An Android device or emulator with the latest version of Google Play services installed

Support

If you get stuck, the google-pay/android-quickstart GitHub repository contains a complete solution for reference.

2. Get started

Clone the repository from GitHub

Use the following command to clone the repository into a folder on your computer:

git clone https://github.com/google-pay/android-quickstart

Or if you prefer a zip archive:

Skim through the sample application

As you can see, the repository features an uncomplicated file structure. The primary objective of this codelab is to give you the ability to adapt this integration to your existing and future applications, independently of the programming language, libraries or tools you choose to work with.

3. Open the project in Android Studio

The GitHub repository you cloned contains an Android project with a basic activity. In this step, you will edit in this activity to verify Google Pay readiness and show a Google Pay button.

  1. Open Android Studio
  2. Select File, then Open
  3. Select the kotlin directory in the repository
  4. Select Open

Add the Google Pay library as a dependency in your build.gradle file

  1. Open the module-level Gradle build file (kotlin/app/build.gradle.kts)
  2. Add the Google Pay library to the dependencies section
implementation "com.google.android.gms:play-services-wallet:19.3.0"
  1. Save the file
  2. Select File, then Sync Project with Gradle Files

Enable the Google Pay API in your Android manifest file

Finally, add a meta-data element inside of the application node of your manifest file:

<meta-data
    android:name="com.google.android.gms.wallet.api.enabled"
    android:value="true" />

4. Decide where to place the Google Pay button in your interface

The layout and overall placement in your views are important aspects that impact how likely your users are to complete a payment transaction. The ability to choose a form of payment in a few taps using Google Pay enables new options on where and when to offer users a way to pay in your application. For example, you can add quick checkout options earlier in the process, in areas like an item's detail view, to allow users to quickly pay for a single item they like.

Once you have decided how to arrange your user interface, the next step is to place the Google Pay button. You can configure some visual features of the button like the type, theme and corner roundness. Here are some examples:

Google Pay dynamic buttons example

The Google Pay library includes a view to simplify adding the button to your user interface. Here's how you do that if you are building your layouts using XML:

<com.google.android.gms.wallet.button.PayButton
    android:id="@+id/googlePayButton"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

You'll later initialize the button programmatically (see example).
If you are using Jetpack Compose, include the latest version of the following dependency in your build.gradle or build.gradle.kts file:

implementation "com.google.pay.button:compose-pay-button:1.0.0"

And add the Google Pay button to your Compose layout like so:

PayButton(
    modifier = Modifier
        .testTag("payButton")
        .fillMaxWidth(),
    onClick = onGooglePayButtonClick,
    allowedPaymentMethods = PaymentsUtil.allowedPaymentMethods.toString()
)

aside Check out the sample in GitHub to learn more about how to construct the list of accepted payment methods.

5. Initialize and configure the Google Pay API

Instantiate the API client

In order to start using the API, you need to instantiate a client object, which you use to make calls to the Google Pay API. You can do that as soon as your activity or controller is created:

private val paymentsClient: PaymentsClient = createPaymentsClient(context)

fun createPaymentsClient(context: Context): PaymentsClient {
    val walletOptions = Wallet.WalletOptions.Builder()
            .setEnvironment(WalletConstants.ENVIRONMENT_TEST).build()
    return Wallet.getPaymentsClient(context, walletOptions)
}

The payment client is initialized with a WalletOptions object. Setting the environment to ENVIRONMENT_TEST allows you to experiment with test payment information. Once you are ready to issue actual payment transactions, you can update the environment property to ENVIRONMENT_PRODUCTION.

The anatomy of a Google Pay request

When you issue requests against the Google Pay API, there are a number of configuration parameters that you need to include in your requests, such as the version of the API you are targeting. For the purpose of this codelab, this object also contains information about the payment methods accepted in your application. The final structure looks as follows:

{
    apiVersion: number,
    apiVersionMinor: number,
    allowedPaymentMethods: Array
}

The property allowedPaymentMethods takes a list of payment methods. For every payment method you are required to include the following properties:

{
    type: 'CARD',
    parameters: {
        allowedCardNetworks: Array.<string>,
        allowedAuthMethods: Array.<string>
    }
}

The payment method configuration

For the purpose of this example, you are only going to accept card payments for Mastercard and Visa, both in a tokenized and primary account number (PAN) forms. This is what your payment method looks like:

private val baseCardPaymentMethod =
    JSONObject()
        .put("type", "CARD")
        .put("parameters", JSONObject()
            .put("allowedCardNetworks", JSONArray(listOf("VISA", "MASTERCARD")))
            .put("allowedAuthMethods", JSONArray(listOf("PAN_ONLY", "CRYPTOGRAM_3DS")))
        )

Putting it all together

Let's recap. You have defined one payment method to be accepted in your application, and you are going to work with version 2.0 of the API. This is what the resulting configuration looks like:

private val baseCardPaymentMethod = JSONObject()
    .put("type", "CARD")
    .put("parameters", JSONObject()
        .put("allowedCardNetworks", JSONArray(listOf("VISA", "MASTERCARD")))
        .put("allowedAuthMethods", JSONArray(listOf("PAN_ONLY", "CRYPTOGRAM_3DS")))
    )

private val googlePayBaseConfiguration = JSONObject()
    .put("apiVersion", 2)
    .put("apiVersionMinor", 0)
    .put("allowedPaymentMethods",  JSONArray(listOf(baseCardPaymentMethod)))
}

6. Determine readiness to pay with Google Pay

Using the isReadyToPay request allows you to determine whether a user in your application is able to pay with Google Pay. Use this information to adjust the user experience in your application accordingly.

This request requires you to specify the version of the Google Pay API and the list of allowed payment methods in your application. This is exactly what the base configuration object defined in the previous step contains:

private suspend fun fetchCanUseGooglePay(): Boolean {
    val request = IsReadyToPayRequest.fromJson(googlePayBaseConfiguration.toString())
    return paymentsClient.isReadyToPay(request).await()
}

aside The Google Pay API uses Tasks to resolve remote calls. The Tasks API lets you resolve Task operations using coroutines. Learn more

As you can see, checking for readiness to Pay with Google Pay returns a boolean object. If the result is positive, the next step is to show the Google Pay button in your user interface. Otherwise, consider showing additional UI that supports other means of payment.

Show the Google Pay button

Now go back to your user interface and update the placement of the Google Pay button based on the result of the previous call. This example uses a class to hold the state of the view, which is updated based on input like the call to isReadyToPay.

if (payUiState !is PaymentUiState.NotStarted) {
    PayButton(
        modifier = Modifier
            .testTag("payButton")
            .fillMaxWidth(),
        onClick = onGooglePayButtonClick,
        allowedPaymentMethods = PaymentsUtil.allowedPaymentMethods.toString()
    )
}

7. It's pay time!

### Prepare the payment request

At this point, you have loaded the Google Pay API and determined that the user in your application is able to use Google Pay to make a payment. As a result, you have shown the Google Pay payment button in the UI and now your user is ready to initiate the transaction. It is now time to load the payment sheet with the forms of payment available for your user.

Similar to the call to isReadyToPay, this request requires the properties in the base configuration object defined earlier (apiVersion, apiVersionMinor and allowedPaymentMethods). This time, you also need a new property called tokenizationSpecification, and additional parameters to describe the transaction and request information like billing and shipping addresses or users' email address or phone number.

#### The tokenizationSpecification property

The tokenization specification determines how the payment method selected by your users is handled and used to complete a transaction.

There are two different types of handling mechanisms supported. If you are processing the payment transaction from within your PCI DSS compliant servers, use the DIRECT specification type. In this example, you use a payment gateway to process the payment, hence, you set the PAYMENT_GATEWAY specification type:

private val tokenizationSpecification = JSONObject()
    .put("type", "PAYMENT_GATEWAY")
    .put("parameters", JSONObject(mapOf(
            "gateway" to "example",
            "gatewayMerchantId" to "exampleGatewayMerchantId")))
}

In the parameters section, you can specify a gateway from the list of providers supported by the Google Pay API, along with additional configuration required by each gateway. For the purpose of this lab, you will use the example gateway, which returns test results for the transactions executed.

Additional parameters

Similarly, you can request more details from your user in order to successfully place the order. See how in this example, you add the properties billingAddressRequired and billingAddressParameters, to indicate that, for this transaction, the billing address of the user is required in full format and including a phone number:

private val cardPaymentMethod = JSONObject()
    .put("type", "CARD")
    .put("tokenizationSpecification", tokenizationSpecification)
    .put("parameters", JSONObject()
        .put("allowedCardNetworks", JSONArray(listOf("VISA", "MASTERCARD")))
        .put("allowedAuthMethods", JSONArray(listOf("PAN_ONLY", "CRYPTOGRAM_3DS")))
        .put("billingAddressRequired", true)
        .put("billingAddressParameters", JSONObject(mapOf("format" to "FULL")))
    )

#### Include additional information about the transaction

The transactionInfo property contains an object with financial details about the transaction, namely the price and currency code (ISO 4217 alpha format) along with the status of the price, which can be either final or estimated depending on the nature of the transaction (eg.: the price may vary depending on the shipping address specified):

private val transactionInfo = JSONObject()
    .put("totalPrice", "123.45")
    .put("totalPriceStatus", "FINAL")
    .put("currencyCode", "USD")
}

#### Adding your business information

The payment request takes information about the merchant performing the request under the merchantInfo property. In this codelab you will focus on two properties:

  • merchantId holds the identifier associated with your account. You can obtain your merchant identifier in the Pay & Wallet Console. Note that this field is not evaluated when using the TEST environment.
  • merchantName is the user-visible name of your application or organization. This may be shown inside of the Google Pay payment sheet to provide users with more context about who is requesting the operation.

Once ready, simply add the information about the merchant to the paymentDataRequest object:

private val merchantInfo = JSONObject()
    .put("merchantName", "Example Merchant")
    .put("merchantId", "01234567890123456789")

Issue the request and process the result

Now, bring the configuration object together and pass it to the loadPaymentData request:

private val paymentDataRequestJson = JSONObject(googlePayBaseConfiguration.toString())
    .put("allowedPaymentMethods", JSONArray().put(cardPaymentMethod))
    .put("transactionInfo", transactionInfo)
    .put("merchantInfo", merchantInfo)

At this point, you have everything you need to ask the Google Pay API for a valid form of payment. To do that, use the loadPaymentData method in the PaymentsClient object, passing in the configuration you just defined:

fun getLoadPaymentDataTask(): Task<PaymentData> {
    val request = PaymentDataRequest.fromJson(paymentDataRequestJson.toString())
    return paymentsClient.loadPaymentData(request)
}

private fun requestPayment() {
    val task = getLoadPaymentDataTask()
    task.addOnCompleteListener(paymentDataLauncher::launch)
}

The Google Pay API uses the Activity Result API to return a result. The API contains contracts to simplify processing and reacting to the result. This example uses the GetPaymentDataResult contract, which includes information about the operation in addition to the payment result:

private val paymentDataLauncher = registerForActivityResult(GetPaymentDataResult()) { taskResult ->
    when (taskResult.status.statusCode) {
        CommonStatusCodes.SUCCESS -> {
            taskResult.result!!.let {
                Log.i("Google Pay result:", it.toJson())
                // TODO something with the result
            }
        }
        CommonStatusCodes.CANCELED -> // The user canceled
        AutoResolveHelper.RESULT_ERROR -> // The API returned an error (it.status: Status)
        CommonStatusCodes.INTERNAL_ERROR -> // Handle other unexpected errors
    }
}

Calling this method triggers the presentation of the Google Pay payment sheet. If there are no configuration errors, you can see a list of valid payment methods associated with the currently logged in account.

Upon selection, the sheet closes and the result is sent back to your activity and captured by the activity result launcher defined above.

If the selection is successful, the result is returned with a PaymentData object that includes relevant information about the payment method selected:

{
  "apiVersionMinor": 0,
  "apiVersion": 2,
  "paymentMethodData": {
    "description": "Visa •••• 1234",
    "tokenizationData": {
      "type": "PAYMENT_GATEWAY",
      "token": "examplePaymentMethodToken"
    },
    "type": "CARD",
    "info": {
      "cardNetwork": "VISA",
      "cardDetails": "1234",
      "billingAddress": {
        "phoneNumber": ...,
        ...
      }
    }
  }
}

You can now use this payment method to complete the transaction with your payment gateway:

private fun handlePaymentSuccess(paymentData: PaymentData) {
    val paymentMethodToken = paymentData
            .getJSONObject("tokenizationData")
            .getString("token")

    // Sample TODO: Use this token to perform a payment through your payment gateway
}

8. Congratulations!

You have successfully added Google Pay to an Android application.

### Next steps

### Learn more

Did you find this useful?

Very useful Just about what I was expecting Not really