Tutorial

Follow the steps in this tutorial to integrate your Android application that is distributed through the Google Play Store with the Google Pay API, and configure it to accept payment cards.

    Step 1: Define your Google Pay API version

    Declare the version of the Google Pay API that your app uses. The major and minor version affects the fields expected in each passed object and is included in the response.

    Create a base request object that contains properties that are present in all other request objects.

    
      private static JSONObject getBaseRequest() throws JSONException {
        return new JSONObject().put("apiVersion", 2).put("apiVersionMinor", 0);
      }
    

    Step 2: Choose a payment tokenization method

    Google encrypts information about a payer's selected card for secure processing by a supported gateway or directly on a merchant's secured servers. Examples for both options are provided below.

    Gateway

    
      private static JSONObject getGatewayTokenizationSpecification() throws JSONException {
        return new JSONObject(){{
          put("type", "PAYMENT_GATEWAY");
          put("parameters", new JSONObject(){{
            put("gateway", "example");
            put("gatewayMerchantId", "exampleGatewayMerchantId");
            }
          });
        }};
      }
    

    Replace example with a gateway identifier of a Google-supported gateway and exampleGatewayMerchantId with a unique merchant identifier provided by your gateway.

    See gateway tokenization documentation for more information about supported gateways and unique identifiers. The PAYMENT_GATEWAY tokenization type is the most common merchant implementation of the card payment method in the Google Pay API.

    Direct

    
      private static JSONObject getDirectTokenizationSpecification()
          throws JSONException, RuntimeException {
        if (Constants.DIRECT_TOKENIZATION_PARAMETERS.isEmpty()
            || Constants.DIRECT_TOKENIZATION_PUBLIC_KEY.isEmpty()
            || Constants.DIRECT_TOKENIZATION_PUBLIC_KEY == null
            || Constants.DIRECT_TOKENIZATION_PUBLIC_KEY == "REPLACE_ME") {
          throw new RuntimeException(
              "Please edit the Constants.java file to add protocol version & public key.");
        }
        JSONObject tokenizationSpecification = new JSONObject();
    
        tokenizationSpecification.put("type", "DIRECT");
        JSONObject parameters = new JSONObject(Constants.DIRECT_TOKENIZATION_PARAMETERS);
        tokenizationSpecification.put("parameters", parameters);
    
        return tokenizationSpecification;
      }
    

    Replace the abbreviated publicKey value with a public key you provided in your Google Pay Developer Profile to receive payment information encrypted with that key. See our payment data cryptography documentation for information about configuring encryption keys suitable for the Google Pay API.

    Step 3: Define supported payment card networks

    Define the card networks that your app accepts.

    private static JSONArray getAllowedCardNetworks() {
      return new JSONArray()
          .put("AMEX")
          .put("DISCOVER")
          .put("INTERAC");
          .put("JCB")
          .put("MASTERCARD")
          .put("VISA");
    }

    The Google Pay API may return cards on file on Google.com (PAN_ONLY) and/or a device token on an Android device authenticated with a 3-D Secure cryptogram (CRYPTOGRAM_3DS).

    private static JSONArray getAllowedCardAuthMethods() {
      return new JSONArray()
          .put("PAN_ONLY")
          .put("CRYPTOGRAM_3DS");
    }

    See CardParameters in our JSON object reference for more information. Check with your gateway or processor for the card networks supported and support for Android device tokens.

    Step 4: Describe your allowed payment methods

    Combine your supported authentication methods and supported card networks in order to describe your app's support for the CARD payment method.

    
      private static JSONObject getBaseCardPaymentMethod() throws JSONException {
        JSONObject cardPaymentMethod = new JSONObject();
        cardPaymentMethod.put("type", "CARD");
    
        JSONObject parameters = new JSONObject();
        parameters.put("allowedAuthMethods", getAllowedCardAuthMethods());
        parameters.put("allowedCardNetworks", getAllowedCardNetworks());
        // Optionally, you can add billing address/phone number associated with a CARD payment method.
        parameters.put("billingAddressRequired", true);
    
        JSONObject billingAddressParameters = new JSONObject();
        billingAddressParameters.put("format", "FULL");
    
        parameters.put("billingAddressParameters", billingAddressParameters);
    
        cardPaymentMethod.put("parameters", parameters);
    
        return cardPaymentMethod;
      }
    

    Extend the base card payment method object to describe information expected to be returned to your application including tokenized payment data.

    
      private static JSONObject getCardPaymentMethod() throws JSONException {
        JSONObject cardPaymentMethod = getBaseCardPaymentMethod();
        cardPaymentMethod.put("tokenizationSpecification", getGatewayTokenizationSpecification());
    
        return cardPaymentMethod;
      }
    

    See CardParameters in our JSON object reference for more information about supported parameters.

    In addition to CARD, Google Pay also supports the PAYPAL payment method. For more details about how to add PAYPAL as a payment method to Google Pay, refer to PayPal’s developer documentation.

    Step 5: Create a PaymentsClient instance

    Create an instance of PaymentsClient in the onCreate method in your Activity. The PaymentsClient is used for interaction with the Google Pay API.

    
      public static PaymentsClient createPaymentsClient(Activity activity) {
        Wallet.WalletOptions walletOptions =
            new Wallet.WalletOptions.Builder().setEnvironment(Constants.PAYMENTS_ENVIRONMENT).build();
        return Wallet.getPaymentsClient(activity, walletOptions);
      }
    

    Step 6: Determine readiness to pay with the Google Pay API

    Add your allowed payment methods to your base request object with the following code snippet:

    
      public static Optional<JSONObject> getIsReadyToPayRequest() {
        try {
          JSONObject isReadyToPayRequest = getBaseRequest();
          isReadyToPayRequest.put(
              "allowedPaymentMethods", new JSONArray().put(getBaseCardPaymentMethod()));
    
          return Optional.of(isReadyToPayRequest);
        } catch (JSONException e) {
          return Optional.empty();
        }
      }
    

    Before you display the Google Pay button, call the isReadyToPay API to determine if the user can make payments with the Google Pay API. See the IsReadyToPayRequest JSON object documentation for a full list of configuration properties.

    
      private void possiblyShowGooglePayButton() {
        final Optional<JSONObject> isReadyToPayJson = PaymentsUtil.getIsReadyToPayRequest();
        if (!isReadyToPayJson.isPresent()) {
          return;
        }
        IsReadyToPayRequest request = IsReadyToPayRequest.fromJson(isReadyToPayJson.get().toString());
        if (request == null) {
          return;
        }
    
        // The call to isReadyToPay is asynchronous and returns a Task. We need to provide an
        // OnCompleteListener to be triggered when the result of the call is known.
        Task<Boolean> task = mPaymentsClient.isReadyToPay(request);
        task.addOnCompleteListener(this,
            new OnCompleteListener<Boolean>() {
              @Override
              public void onComplete(@NonNull Task<Boolean> task) {
                if (task.isSuccessful()) {
                  setGooglePayAvailable(task.getResult());
                } else {
                  Log.w("isReadyToPay failed", task.getException());
                }
              }
            });
      }
    

    As in this example, Google Pay should be presented as a payment option only after you receive a successful result from the isReadyToPay function. It's most common to display a Google Pay payment button through a layout include when you implement a payment option. See Brand guidelines for more information about Google Pay payment buttons, logos, and marks available for use in your app.

    Step 7: Create a PaymentDataRequest object

    A PaymentDataRequest JSON object describes the information you would like to request from a payer in a Google Pay payment sheet.

    Provide information about the transaction price and the status of the provided price. See TransactionInfo JSON object documentation for more information.

    The following example shows how to get transaction information, specifically price, price status, and currency.

    
      private static JSONObject getTransactionInfo(String price) throws JSONException {
        JSONObject transactionInfo = new JSONObject();
        transactionInfo.put("totalPrice", price);
        transactionInfo.put("totalPriceStatus", "FINAL");
        transactionInfo.put("currencyCode", Constants.CURRENCY_CODE);
    
        return transactionInfo;
      }
    

    Provide a user-visible merchant name. See the MerchantInfo JSON object documentation for more information.

    The following example shows how to get the merchant's name:

    
      private static JSONObject getMerchantInfo() throws JSONException {
        return new JSONObject().put("merchantName", "Example Merchant");
      }
    

    Assign your base request object to a new PaymentDataRequest JSON object. Then, add the payment methods supported by your app, such as any configuration of additional data expected in the response. Finally, add information about the transaction and the merchant who makes the request.

    The following example shows how to request payment data:

    
      public static Optional<JSONObject> getPaymentDataRequest(String price) {
        try {
          JSONObject paymentDataRequest = PaymentsUtil.getBaseRequest();
          paymentDataRequest.put(
              "allowedPaymentMethods", new JSONArray().put(PaymentsUtil.getCardPaymentMethod()));
          paymentDataRequest.put("transactionInfo", PaymentsUtil.getTransactionInfo(price));
          paymentDataRequest.put("merchantInfo", PaymentsUtil.getMerchantInfo());
    
          /* An optional shipping address requirement is a top-level property of the PaymentDataRequest
          JSON object. */
          paymentDataRequest.put("shippingAddressRequired", true);
    
          JSONObject shippingAddressParameters = new JSONObject();
          shippingAddressParameters.put("phoneNumberRequired", false);
    
          JSONArray allowedCountryCodes = new JSONArray(Constants.SHIPPING_SUPPORTED_COUNTRIES);
    
          shippingAddressParameters.put("allowedCountryCodes", allowedCountryCodes);
          paymentDataRequest.put("shippingAddressParameters", shippingAddressParameters);
          return Optional.of(paymentDataRequest);
        } catch (JSONException e) {
          return Optional.empty();
        }
      }
    

    See the PaymentDataRequest JSON object documentation for more information.

    Step 8: Register event handler for user gesture

    Define an OnClickListener to request display of a Google Pay payment sheet after user activation of a Google Pay payment button.

    
    
        mGooglePayButton.setOnClickListener(
            new View.OnClickListener() {
              @Override
              public void onClick(View view) {
                requestPayment(view);
              }
            });
    

    The PaymentDataRequest object is a Parcelable representing a payment data request, which provides information that is necessary in order to support a payment. Use the AutoResolveHelper class to auto resolve the Task and then handle the result in the onActivityResult method of your Activity class.

    Step 9: Handle the response object

    A PaymentData object is returned to onActivityResult after a payer successfully provides the requested information in the Google Pay payment sheet.

    Convert a successful response to JSON in order to pass payment information to your processor and to present the user with a confirmation of their purchase. If the transactionInfo.totalPriceStatus passed to PaymentDataRequest is ESTIMATED, you must show a final price before you charge the returned payment method.

    Extract the payment token from the paymentData response. If you implement a gateway integration, pass this token to your gateway without any modifications.

    
      public void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
            // value passed in AutoResolveHelper
          case LOAD_PAYMENT_DATA_REQUEST_CODE:
            switch (resultCode) {
              case Activity.RESULT_OK:
                PaymentData paymentData = PaymentData.getFromIntent(data);
                handlePaymentSuccess(paymentData);
                break;
              case Activity.RESULT_CANCELED:
                // Nothing to here normally - the user simply cancelled without selecting a
                // payment method.
                break;
              case AutoResolveHelper.RESULT_ERROR:
                Status status = AutoResolveHelper.getStatusFromIntent(data);
                handleError(status.getStatusCode());
                break;
              default:
                // Do nothing.
            }
    
            // Re-enables the Google Pay payment button.
            mGooglePayButton.setClickable(true);
            break;
        }
      }
    

    See the PaymentData JSON object reference for more information about the content and structure of the response.

    Put it all together

    The following example shows a complete example for a properly configured project. See the "Configure your project" section of Set up Google Pay API for the project-level setup steps.

    CheckoutActivity.java

    This example Activity assumes a Google Pay payment button with an ID attribute of googlepay_button exists in your layout.

    /*
     * Copyright 2017 Google Inc.
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    
    package com.google.android.gms.samples.wallet;
    
    import android.app.Activity;
    import android.app.AlertDialog;
    import android.content.Intent;
    import android.os.Bundle;
    import android.support.annotation.NonNull;
    import android.util.Log;
    import android.view.View;
    import android.widget.ImageView;
    import android.widget.TextView;
    import android.widget.Toast;
    import com.google.android.gms.common.api.ApiException;
    import com.google.android.gms.common.api.Status;
    import com.google.android.gms.tasks.OnCompleteListener;
    import com.google.android.gms.tasks.Task;
    import com.google.android.gms.wallet.AutoResolveHelper;
    import com.google.android.gms.wallet.IsReadyToPayRequest;
    import com.google.android.gms.wallet.PaymentData;
    import com.google.android.gms.wallet.PaymentDataRequest;
    import com.google.android.gms.wallet.PaymentsClient;
    import java.util.Optional;
    import org.json.JSONException;
    import org.json.JSONObject;
    
    /**
     * Checkout implementation for the app
     */
    public class CheckoutActivity extends Activity {
      /**
       * A client for interacting with the Google Pay API.
       *
       * @see <a
       *     href="https://developers.google.com/android/reference/com/google/android/gms/wallet/PaymentsClient">PaymentsClient</a>
       */
      private PaymentsClient mPaymentsClient;
    
      /**
       * A Google Pay payment button presented to the viewer for interaction.
       *
       * @see <a href="https://developers.google.com/pay/api/android/guides/brand-guidelines">Google Pay
       *     payment button brand guidelines</a>
       */
      private View mGooglePayButton;
    
      /**
       * Arbitrarily-picked constant integer you define to track a request for payment data activity.
       *
       * @value #LOAD_PAYMENT_DATA_REQUEST_CODE
       */
      private static final int LOAD_PAYMENT_DATA_REQUEST_CODE = 991;
    
      private TextView mGooglePayStatusText;
    
      private ItemInfo mBikeItem = new ItemInfo("Simple Bike", 300 * 1000000, R.drawable.bike);
      private long mShippingCost = 90 * 1000000;
      /**
       * Initialize the Google Pay API on creation of the activity
       *
       * @see Activity#onCreate(android.os.Bundle)
       */
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_checkout);
    
        // Set up the mock information for our item in the UI.
        initItemUI();
    
        mGooglePayButton = findViewById(R.id.googlepay_button);
        mGooglePayStatusText = findViewById(R.id.googlepay_status);
    
        // Initialize a Google Pay API client for an environment suitable for testing.
        // It's recommended to create the PaymentsClient object inside of the onCreate method.
        mPaymentsClient = PaymentsUtil.createPaymentsClient(this);
        possiblyShowGooglePayButton();
    
        mGooglePayButton.setOnClickListener(
            new View.OnClickListener() {
              @Override
              public void onClick(View view) {
                requestPayment(view);
              }
            });
      }
    
      /**
       * Determine the viewer's ability to pay with a payment method supported by your app and display a
       * Google Pay payment button.
       *
       * @see <a href=
       *     "https://developers.google.com/android/reference/com/google/android/gms/wallet/PaymentsClient.html#isReadyToPay(com.google.android.gms.wallet.IsReadyToPayRequest)">PaymentsClient#IsReadyToPay</a>
       */
      private void possiblyShowGooglePayButton() {
        final Optional<JSONObject> isReadyToPayJson = PaymentsUtil.getIsReadyToPayRequest();
        if (!isReadyToPayJson.isPresent()) {
          return;
        }
        IsReadyToPayRequest request = IsReadyToPayRequest.fromJson(isReadyToPayJson.get().toString());
        if (request == null) {
          return;
        }
    
        // The call to isReadyToPay is asynchronous and returns a Task. We need to provide an
        // OnCompleteListener to be triggered when the result of the call is known.
        Task<Boolean> task = mPaymentsClient.isReadyToPay(request);
        task.addOnCompleteListener(this,
            new OnCompleteListener<Boolean>() {
              @Override
              public void onComplete(@NonNull Task<Boolean> task) {
                if (task.isSuccessful()) {
                  setGooglePayAvailable(task.getResult());
                } else {
                  Log.w("isReadyToPay failed", task.getException());
                }
              }
            });
      }
    
      /**
       * If isReadyToPay returned {@code true}, show the button and hide the "checking" text. Otherwise,
       * notify the user that Google Pay is not available. Please adjust to fit in with your current
       * user flow. You are not required to explicitly let the user know if isReadyToPay returns {@code
       * false}.
       *
       * @param available isReadyToPay API response.
       */
      private void setGooglePayAvailable(boolean available) {
        if (available) {
          mGooglePayStatusText.setVisibility(View.GONE);
          mGooglePayButton.setVisibility(View.VISIBLE);
        } else {
          mGooglePayStatusText.setText(R.string.googlepay_status_unavailable);
        }
      }
    
      /**
       * Handle a resolved activity from the Google Pay payment sheet.
       *
       * @param requestCode Request code originally supplied to AutoResolveHelper in requestPayment().
       * @param resultCode Result code returned by the Google Pay API.
       * @param data Intent from the Google Pay API containing payment or error data.
       * @see <a href="https://developer.android.com/training/basics/intents/result">Getting a result
       *     from an Activity</a>
       */
      @Override
      public void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
            // value passed in AutoResolveHelper
          case LOAD_PAYMENT_DATA_REQUEST_CODE:
            switch (resultCode) {
              case Activity.RESULT_OK:
                PaymentData paymentData = PaymentData.getFromIntent(data);
                handlePaymentSuccess(paymentData);
                break;
              case Activity.RESULT_CANCELED:
                // Nothing to here normally - the user simply cancelled without selecting a
                // payment method.
                break;
              case AutoResolveHelper.RESULT_ERROR:
                Status status = AutoResolveHelper.getStatusFromIntent(data);
                handleError(status.getStatusCode());
                break;
              default:
                // Do nothing.
            }
    
            // Re-enables the Google Pay payment button.
            mGooglePayButton.setClickable(true);
            break;
        }
      }
    
      /**
       * PaymentData response object contains the payment information, as well as any additional
       * requested information, such as billing and shipping address.
       *
       * @param paymentData A response object returned by Google after a payer approves payment.
       * @see <a
       *     href="https://developers.google.com/pay/api/android/reference/object#PaymentData">Payment
       *     Data</a>
       */
      private void handlePaymentSuccess(PaymentData paymentData) {
        String paymentInformation = paymentData.toJson();
    
        // Token will be null if PaymentDataRequest was not constructed using fromJson(String).
        if (paymentInformation == null) {
          return;
        }
        JSONObject paymentMethodData;
    
        try {
          paymentMethodData = new JSONObject(paymentInformation).getJSONObject("paymentMethodData");
          // If the gateway is set to "example", no payment information is returned - instead, the
          // token will only consist of "examplePaymentMethodToken".
          if (paymentMethodData
                  .getJSONObject("tokenizationData")
                  .getString("type")
                  .equals("PAYMENT_GATEWAY")
              && paymentMethodData
                  .getJSONObject("tokenizationData")
                  .getString("token")
                  .equals("examplePaymentMethodToken")) {
            AlertDialog alertDialog =
                new AlertDialog.Builder(this)
                    .setTitle("Warning")
                    .setMessage(
                        "Gateway name set to \"example\" - please modify "
                            + "Constants.java and replace it with your own gateway.")
                    .setPositiveButton("OK", null)
                    .create();
            alertDialog.show();
          }
    
          String billingName =
              paymentMethodData.getJSONObject("info").getJSONObject("billingAddress").getString("name");
          Log.d("BillingName", billingName);
          Toast.makeText(this, getString(R.string.payments_show_name, billingName), Toast.LENGTH_LONG)
              .show();
    
          // Logging token string.
          Log.d("GooglePaymentToken", paymentMethodData.getJSONObject("tokenizationData").getString("token"));
        } catch (JSONException e) {
          Log.e("handlePaymentSuccess", "Error: " + e.toString());
          return;
        }
      }
    
      /**
       * At this stage, the user has already seen a popup informing them an error occurred. Normally,
       * only logging is required.
       *
       * @param statusCode will hold the value of any constant from CommonStatusCode or one of the
       *     WalletConstants.ERROR_CODE_* constants.
       * @see <a
       *     href="https://developers.google.com/android/reference/com/google/android/gms/wallet/WalletConstants#constant-summary">
       *     Wallet Constants Library</a>
       */
      private void handleError(int statusCode) {
        Log.w("loadPaymentData failed", String.format("Error code: %d", statusCode));
      }
    
      // This method is called when the Pay with Google button is clicked.
      public void requestPayment(View view) {
        // Disables the button to prevent multiple clicks.
        mGooglePayButton.setClickable(false);
    
        // The price provided to the API should include taxes and shipping.
        // This price is not displayed to the user.
        String price = PaymentsUtil.microsToString(mBikeItem.getPriceMicros() + mShippingCost);
    
        // TransactionInfo transaction = PaymentsUtil.createTransaction(price);
        Optional<JSONObject> paymentDataRequestJson = PaymentsUtil.getPaymentDataRequest(price);
        if (!paymentDataRequestJson.isPresent()) {
          return;
        }
        PaymentDataRequest request =
            PaymentDataRequest.fromJson(paymentDataRequestJson.get().toString());
    
        // Since loadPaymentData may show the UI asking the user to select a payment method, we use
        // AutoResolveHelper to wait for the user interacting with it. Once completed,
        // onActivityResult will be called with the result.
        if (request != null) {
          AutoResolveHelper.resolveTask(
              mPaymentsClient.loadPaymentData(request), this, LOAD_PAYMENT_DATA_REQUEST_CODE);
        }
      }
    
      private void initItemUI() {
        TextView itemName = findViewById(R.id.text_item_name);
        ImageView itemImage = findViewById(R.id.image_item_image);
        TextView itemPrice = findViewById(R.id.text_item_price);
    
        itemName.setText(mBikeItem.getName());
        itemImage.setImageResource(mBikeItem.getImageResourceId());
        itemPrice.setText(PaymentsUtil.microsToString(mBikeItem.getPriceMicros()));
      }
    }
    
    

    PaymentsUtil.java

    This example file builds JSON objects suitable to build an IsReadyToPayRequest or a PaymentDataRequest.

    /*
     * Copyright 2017 Google Inc.
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    
    package com.google.android.gms.samples.wallet;
    
    import android.app.Activity;
    import com.google.android.gms.wallet.PaymentsClient;
    import com.google.android.gms.wallet.Wallet;
    import java.math.BigDecimal;
    import java.math.RoundingMode;
    import java.util.HashMap;
    import java.util.Optional;
    import org.json.JSONArray;
    import org.json.JSONException;
    import org.json.JSONObject;
    
    /**
     * Contains helper static methods for dealing with the Payments API.
     *
     * <p>Many of the parameters used in the code are optional and are set here merely to call out their
     * existence. Please consult the documentation to learn more and feel free to remove ones not
     * relevant to your implementation.
     */
    public class PaymentsUtil {
      private static final BigDecimal MICROS = new BigDecimal(1000000d);
    
      private PaymentsUtil() {}
    
      /**
       * Create a Google Pay API base request object with properties used in all requests.
       *
       * @return Google Pay API base request object.
       * @throws JSONException
       */
      private static JSONObject getBaseRequest() throws JSONException {
        return new JSONObject().put("apiVersion", 2).put("apiVersionMinor", 0);
      }
    
      /**
       * Creates an instance of {@link PaymentsClient} for use in an {@link Activity} using the
       * environment and theme set in {@link Constants}.
       *
       * @param activity is the caller's activity.
       */
      public static PaymentsClient createPaymentsClient(Activity activity) {
        Wallet.WalletOptions walletOptions =
            new Wallet.WalletOptions.Builder().setEnvironment(Constants.PAYMENTS_ENVIRONMENT).build();
        return Wallet.getPaymentsClient(activity, walletOptions);
      }
    
      /**
       * Gateway Integration: Identify your gateway and your app's gateway merchant identifier.
       *
       * <p>The Google Pay API response will return an encrypted payment method capable of being charged
       * by a supported gateway after payer authorization.
       *
       * <p>TODO: Check with your gateway on the parameters to pass and modify them in Constants.java.
       *
       * @return Payment data tokenization for the CARD payment method.
       * @throws JSONException
       * @see <a href=
       *     "https://developers.google.com/pay/api/android/reference/object#PaymentMethodTokenizationSpecification">PaymentMethodTokenizationSpecification</a>
       */
      private static JSONObject getGatewayTokenizationSpecification() throws JSONException {
        return new JSONObject(){{
          put("type", "PAYMENT_GATEWAY");
          put("parameters", new JSONObject(){{
            put("gateway", "example");
            put("gatewayMerchantId", "exampleGatewayMerchantId");
            }
          });
        }};
      }
    
      /**
       * {@code DIRECT} Integration: Decrypt a response directly on your servers. This configuration has
       * additional data security requirements from Google and additional PCI DSS compliance complexity.
       *
       * <p>Please refer to the documentation for more information about {@code DIRECT} integration. The
       * type of integration you use depends on your payment processor.
       *
       * @return Payment data tokenization for the CARD payment method.
       * @throws JSONException
       * @see <a
       *     href="https://developers.google.com/pay/api/android/reference/object#PaymentMethodTokenizationSpecification">PaymentMethodTokenizationSpecification</a>
       */
      private static JSONObject getDirectTokenizationSpecification()
          throws JSONException, RuntimeException {
        if (Constants.DIRECT_TOKENIZATION_PARAMETERS.isEmpty()
            || Constants.DIRECT_TOKENIZATION_PUBLIC_KEY.isEmpty()
            || Constants.DIRECT_TOKENIZATION_PUBLIC_KEY == null
            || Constants.DIRECT_TOKENIZATION_PUBLIC_KEY == "REPLACE_ME") {
          throw new RuntimeException(
              "Please edit the Constants.java file to add protocol version & public key.");
        }
        JSONObject tokenizationSpecification = new JSONObject();
    
        tokenizationSpecification.put("type", "DIRECT");
        JSONObject parameters = new JSONObject(Constants.DIRECT_TOKENIZATION_PARAMETERS);
        tokenizationSpecification.put("parameters", parameters);
    
        return tokenizationSpecification;
      }
    
      /**
       * Card networks supported by your app and your gateway.
       *
       * <p>TODO: Confirm card networks supported by your app and gateway & update in Constants.java.
       *
       * @return Allowed card networks
       * @see <a
       *     href="https://developers.google.com/pay/api/android/reference/object#CardParameters">CardParameters</a>
       */
      private static JSONArray getAllowedCardNetworks() {
        return new JSONArray(Constants.SUPPORTED_NETWORKS);
      }
    
      /**
       * Card authentication methods supported by your app and your gateway.
       *
       * <p>TODO: Confirm your processor supports Android device tokens on your supported card networks
       * and make updates in Constants.java.
       *
       * @return Allowed card authentication methods.
       * @see <a
       *     href="https://developers.google.com/pay/api/android/reference/object#CardParameters">CardParameters</a>
       */
      private static JSONArray getAllowedCardAuthMethods() {
        return new JSONArray(Constants.SUPPORTED_METHODS);
      }
    
      /**
       * Describe your app's support for the CARD payment method.
       *
       * <p>The provided properties are applicable to both an IsReadyToPayRequest and a
       * PaymentDataRequest.
       *
       * @return A CARD PaymentMethod object describing accepted cards.
       * @throws JSONException
       * @see <a
       *     href="https://developers.google.com/pay/api/android/reference/object#PaymentMethod">PaymentMethod</a>
       */
      private static JSONObject getBaseCardPaymentMethod() throws JSONException {
        JSONObject cardPaymentMethod = new JSONObject();
        cardPaymentMethod.put("type", "CARD");
    
        JSONObject parameters = new JSONObject();
        parameters.put("allowedAuthMethods", getAllowedCardAuthMethods());
        parameters.put("allowedCardNetworks", getAllowedCardNetworks());
        // Optionally, you can add billing address/phone number associated with a CARD payment method.
        parameters.put("billingAddressRequired", true);
    
        JSONObject billingAddressParameters = new JSONObject();
        billingAddressParameters.put("format", "FULL");
    
        parameters.put("billingAddressParameters", billingAddressParameters);
    
        cardPaymentMethod.put("parameters", parameters);
    
        return cardPaymentMethod;
      }
    
      /**
       * Describe the expected returned payment data for the CARD payment method
       *
       * @return A CARD PaymentMethod describing accepted cards and optional fields.
       * @throws JSONException
       * @see <a
       *     href="https://developers.google.com/pay/api/android/reference/object#PaymentMethod">PaymentMethod</a>
       */
      private static JSONObject getCardPaymentMethod() throws JSONException {
        JSONObject cardPaymentMethod = getBaseCardPaymentMethod();
        cardPaymentMethod.put("tokenizationSpecification", getGatewayTokenizationSpecification());
    
        return cardPaymentMethod;
      }
    
      /**
       * An object describing accepted forms of payment by your app, used to determine a viewer's
       * readiness to pay.
       *
       * @return API version and payment methods supported by the app.
       * @see <a
       *     href="https://developers.google.com/pay/api/android/reference/object#IsReadyToPayRequest">IsReadyToPayRequest</a>
       */
      public static Optional<JSONObject> getIsReadyToPayRequest() {
        try {
          JSONObject isReadyToPayRequest = getBaseRequest();
          isReadyToPayRequest.put(
              "allowedPaymentMethods", new JSONArray().put(getBaseCardPaymentMethod()));
    
          return Optional.of(isReadyToPayRequest);
        } catch (JSONException e) {
          return Optional.empty();
        }
      }
    
      /**
       * Provide Google Pay API with a payment amount, currency, and amount status.
       *
       * @return information about the requested payment.
       * @throws JSONException
       * @see <a
       *     href="https://developers.google.com/pay/api/android/reference/object#TransactionInfo">TransactionInfo</a>
       */
      private static JSONObject getTransactionInfo(String price) throws JSONException {
        JSONObject transactionInfo = new JSONObject();
        transactionInfo.put("totalPrice", price);
        transactionInfo.put("totalPriceStatus", "FINAL");
        transactionInfo.put("currencyCode", Constants.CURRENCY_CODE);
    
        return transactionInfo;
      }
    
      /**
       * Information about the merchant requesting payment information
       *
       * @return Information about the merchant.
       * @throws JSONException
       * @see <a
       *     href="https://developers.google.com/pay/api/android/reference/object#MerchantInfo">MerchantInfo</a>
       */
      private static JSONObject getMerchantInfo() throws JSONException {
        return new JSONObject().put("merchantName", "Example Merchant");
      }
    
      /**
       * An object describing information requested in a Google Pay payment sheet
       *
       * @return Payment data expected by your app.
       * @see <a
       *     href="https://developers.google.com/pay/api/android/reference/object#PaymentDataRequest">PaymentDataRequest</a>
       */
      public static Optional<JSONObject> getPaymentDataRequest(String price) {
        try {
          JSONObject paymentDataRequest = PaymentsUtil.getBaseRequest();
          paymentDataRequest.put(
              "allowedPaymentMethods", new JSONArray().put(PaymentsUtil.getCardPaymentMethod()));
          paymentDataRequest.put("transactionInfo", PaymentsUtil.getTransactionInfo(price));
          paymentDataRequest.put("merchantInfo", PaymentsUtil.getMerchantInfo());
    
          /* An optional shipping address requirement is a top-level property of the PaymentDataRequest
          JSON object. */
          paymentDataRequest.put("shippingAddressRequired", true);
    
          JSONObject shippingAddressParameters = new JSONObject();
          shippingAddressParameters.put("phoneNumberRequired", false);
    
          JSONArray allowedCountryCodes = new JSONArray(Constants.SHIPPING_SUPPORTED_COUNTRIES);
    
          shippingAddressParameters.put("allowedCountryCodes", allowedCountryCodes);
          paymentDataRequest.put("shippingAddressParameters", shippingAddressParameters);
          return Optional.of(paymentDataRequest);
        } catch (JSONException e) {
          return Optional.empty();
        }
      }
    
      /**
       * Converts micros to a string format accepted by {@link PaymentsUtil#getPaymentDataRequest}.
       *
       * @param micros value of the price.
       */
      public static String microsToString(long micros) {
        return new BigDecimal(micros).divide(MICROS).setScale(2, RoundingMode.HALF_EVEN).toString();
      }
    }