教學課程

如要將透過 Google Play 商店發行的 Android 應用程式與 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 支援之閘道的閘道 ID,並將 exampleGatewayMerchantId 替換為您的閘道提供的不重複商家識別碼。

    如要進一步瞭解支援的閘道與不重複的 ID,請參閱閘道代碼化說明文件PAYMENT_GATEWAY 代碼化類型是商家最常在 Google Pay API 中實作的卡片付款方式。

    直接

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

    以 Google Pay 開發人員設定檔中提供的公開金鑰取代「publicKey」縮寫值,藉此接收使用該金鑰加密的付款資訊。如要瞭解如何設定適合 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;
          }
        

    如要進一步瞭解支援的 parameters,請參閱 JSON 物件參考資料中的 CardParameters 相關說明。

    除了支援 CARD 以外,Google Pay 也支援 PAYPAL 付款方式。如要進一步瞭解如何將 PAYPAL 新增為 Google Pay 付款方式,請參閱 PayPal 的開發人員說明文件

    步驟 5:建立 PaymentsClient 執行個體

    ActivityonCreate 方法中建立 PaymentsClient 的執行個體。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 付款。如需完整的設定屬性清單,請參閱 IsReadyToPayRequest JSON 物件說明文件

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

    如本例所示,在您收到來自 isReadyToPay 函式的成功結果後,Google Pay 才會以付款選項的形式呈現。實作付款選項時,在版面配置中透過 include 來顯示 Google Pay 付款按鈕,是最常見的做法。如要進一步瞭解可在應用程式中使用的 Google Pay 付款按鈕、標誌和標記,請參閱品牌宣傳指南

    步驟 7:建立 PaymentDataRequest 物件

    PaymentDataRequest JSON 物件會說明要在 Google Pay 付款畫面中,要求付款人提供的資訊。

    請提供交易價格和所提供價格之狀態的資訊。詳情請參閱 TransactionInfo JSON 物件說明文件

    以下範例顯示如何取得交易資訊,特別是價格、價格狀態和幣別這幾種交易資訊。

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

    提供可讓使用者查看的商家名稱。詳情請參閱 MerchantInfo JSON 物件說明文件

    以下範例顯示如何取得商家名稱:

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

    將基本要求物件指派給新的 PaymentDataRequest JSON 物件。接下來,請新增應用程式支援的付款方式,包括回應中預期的任何其他資料設定。最後,請新增交易和提出要求之商家的相關資訊。

    以下範例顯示如何要求付款資料:

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

    詳情請參閱 PaymentDataRequest JSON 物件說明文件

    步驟 8:針對使用者手勢註冊事件處理常式

    定義 OnClickListener,要求系統在使用者啟用 Google Pay 付款按鈕後顯示 Google Pay 付款畫面。

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

    PaymentDataRequest 物件是代表付款資料要求的 Parcelable,可提供要支援付款所需的資訊。請使用 AutoResolveHelper 類別來自動解析 Task,並處理 Activity 類別之 onActivityResult 方法 中的結果。

    步驟 9:處理回應物件

    當付款人成功在 Google Pay 付款畫面中提供要求的資訊之後,系統就會將 PaymentData 物件傳回給 onActivityResult

    請將成功的回應轉換為 JSON,以便將付款資訊傳送至處理方,並向使用者顯示購買確認訊息。如果傳送至 PaymentDataRequesttransactionInfo.totalPriceStatusESTIMATED,您必須先顯示最終價格,才能以傳回的付款方式扣款。

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

    如要進一步瞭解回應的內容和結構,請參閱 PaymentData JSON 物件參考資料

    完整的實作範例

    以下範例顯示正確設定的完整專案範例。如需專案層級的設定步驟,請參閱設定 Google Pay API 的〈設定您的專案〉一節。

    CheckoutActivity.java

    此範例 Activity 假設您的版面配置中有 ID 屬性為 googlepay_button 的 Google Pay 付款按鈕。

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

    此範例檔案會建構適合建立 IsReadyToPayRequestPaymentDataRequest 的 JSON 物件。

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