Руководство пользователя

Из этого руководства вы узнаете, как интегрировать приложение Android, распространяемое через Google Play Маркет, с Google Pay API и настроить его для поддержки платежных карт.

    Шаг 1. Укажите версию Google Pay API

    Укажите версию Google Pay API, используемую в вашем приложении. Старшая и младшая версии указываются в ответе и влияют на то, какой набор полей будет проверяться во всех объектах.

    Создайте базовый объект запроса со свойствами, имеющимися во всех прочих объектах запроса.

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

    Шаг 2. Выберите способ токенизации

    Компания Google шифрует информацию о выбранной карте покупателя, чтобы обеспечить безопасную обработку данных в поддерживаемом шлюзе или на безопасных серверах продавца. Примеры приведены ниже.

    Шлюз

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

    Замените example на идентификатор поддерживаемого Google шлюза, а exampleGatewayMerchantId – на предоставленный шлюзом уникальный идентификатор продавца.

    Подробную информацию о поддерживаемых шлюзах и уникальных идентификаторах можно найти в разделе Справочная документация о токенизации шлюзов. PAYMENT_GATEWAY – наиболее распространенный способ внедрения 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;
      }
    

    Замените сокращенное значение параметра publicKey на открытый ключ, который вы указали в профиле разработчика Google Pay, чтобы получить зашифрованные платежные данные. Более подробную информацию о настройке шифровальных ключей для Google Pay API можно найти в документации по криптографии платежных данных.

    Шаг 3. Задайте поддерживаемые платежные системы

    Выберите поддерживаемые вашим приложением платежные системы.

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

    Google Pay API может показать результаты поиска карт в архивах Google.com (PAN_ONLY) и/или токенов на устройстве Android, аутентифицированном с помощью криптограммы 3-D Secure (CRYPTOGRAM_3DS).

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

    Дополнительную информацию можно найти в разделе CardParameters в документации по объектам JSON. Список поддерживаемых платежных систем и токенов для устройств Android можно получить через шлюз или с помощью обработчика платежей.

    Шаг 4. Укажите поддерживаемые способы оплаты

    Чтобы задать для приложения поддерживаемый способ оплаты CARD, укажите поддерживаемые методы аутентификации и платежные системы.

    
      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;
      }
    

    Расширьте объект базового способа оплаты кредитной картой, чтобы указать, какая информация (включая токенизированные платежные данные) должна возвращаться в приложение.

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

    Дополнительную информацию о содержании и структуре ответа можно найти в PaymentDataCardParameters.

    Помимо карт (CARD) в Google Pay поддерживается такой способ оплаты, как PAYPAL. Подробнее о том, как добавить аккаунт PAYPAL в Google Pay, написано здесь.

    Шаг 5. Создайте экземпляр объекта PaymentsClient

    Создайте экземпляр PaymentsClient в методе onCreate объекта Activity. Экземпляр PaymentsClient используется для взаимодействия с 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);
      }
    

    Шаг 6. Определите готовность к оплате с помощью Google Pay API

    Добавьте разрешенные способы оплаты в базовый объект запроса с помощью следующего фрагмента кода:

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

    Прежде чем отображать кнопку Google Pay, вызовите isReadyToPay API, чтобы определить, может ли пользователь расплатиться через Google Pay API. Полный список свойств конфигурации можно найти в документации по объекту JSON IsReadyToPayRequest.

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

    В этом примере варианты оплаты Google Pay должен появиться только после получения положительного результата от isReadyToPay. Как правило, кнопка Google Pay отображается в макете include, когда вы применяете способ оплаты. Более подробную информацию о доступных кнопках оплаты, логотипах и отметках Google Pay можно найти в разделе Правила использования бренда.

    Шаг 7. Создайте объект PaymentDataRequest

    PaymentDataRequest описывает информацию, которую нужно запросить у покупателя на странице оплаты Google Pay.

    Укажите данные о цене сделки и о ее статусе. Дополнительную информацию можно найти в TransactionInfo.

    В примере ниже показано, как получть информацию о транзакции, в частности цену, статус цены и валюту.

    , и . Это требуется для процедуры строгой аутентификации клиента (SCA).
    
      private static JSONObject getTransactionInfo(String price) throws JSONException {
        JSONObject transactionInfo = new JSONObject();
        transactionInfo.put("totalPrice", price);
        transactionInfo.put("totalPriceStatus", "FINAL");
        transactionInfo.put("countryCode", Constants.COUNTRY_CODE);
        transactionInfo.put("currencyCode", Constants.CURRENCY_CODE);
    
        return transactionInfo;
      }
    

    Укажите название продавца для пользователей. Дополнительную информацию можно найти в документации JSON по объекту MerchantInfo.

    В примере ниже показано, как получить имя продавца.

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

    Выберите базовый объект запроса в качестве нового объекта JSON PaymentDataRequest. Добавьте способы оплаты, поддерживаемые вашим приложением, с указанием конфигураций дополнительных данных, ожидаемых в ответе. Укажите данные о цене сделки и о продавце, отправившем запрос.

    В примере ниже показано, как запросить платежные данные:

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

    Дополнительную информацию можно найти в документации JSON по объекту PaymentDataRequest.

    Шаг 8. Настройте обработчик жестов пользователей

    Определите параметр OnClickListener, чтобы запросить вывод страницы оплаты Google Pay при нажатии пользователем кнопки оплаты Google Pay.

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

    Объект PaymentDataRequest – это Parcelable, который представляет собой запрос платежных данных, необходимых для оплаты. Используйте класс AutoResolveHelper, чтобы автоматически выполнить поставленную задачу Task. Затем обработайте результат с помощью метода onActivityResult класса Activity.

    Шаг 9. Обработайте объект ответа

    Объект PaymentData возвращается в onActivityResult после того, как покупатель укажет всю запрашиваемую информацию на странице оплаты Google Pay.

    Преобразуйте ответ в JSON, чтобы передать платежные данные обработчику, а покупателю выдать подтверждение покупки. Если параметр transactionInfo.totalPriceStatus, переданный в PaymentDataRequest, имеет статус ESTIMATED, необходимо показать итоговую цену до списания средств в рамках возвращенного способа оплаты.

    Извлеките токен из ответа paymentData. Если вы выполняете интеграцию шлюза, передайте этот токен шлюзу, не совершая никаких изменений.

    
      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;
        }
      }
    

    Дополнительную информацию о содержании и структуре ответа можно найти в документации по объекту JSON PaymentData.

    Выводы

    Далее представлен полный пример правильно настроенного проекта. Рекомендации см. в разделе "Настройте ваш проект" руководства Как настроить Google Pay API.

    CheckoutActivity.java

    В рамках данного примера Activity предполагается, что кнопка оплат Google Pay с атрибутом ID в googlepay_button имеется в макете.

    /*
     * 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

    Ниже приведен пример файла для создания объектов JSON, используемых для построения запросов IsReadyToPayRequest или 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("countryCode", Constants.COUNTRY_CODE);
        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();
      }
    }