チュートリアル

このチュートリアルでは、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 に置き換えます。

    サポートされているゲートウェイと固有の 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;
          }
        

    略記された 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)、または 3D セキュア クリプトグラムで認証された Android デバイス トークン(CRYPTOGRAM_3DS)を返します。

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

    詳しくは、JSON オブジェクト リファレンスの CardParameters をご覧ください。サポートされているカード ネットワークと 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 に加えて、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 をお支払いオプションとして提示します。Google Pay 支払いボタンを表示する方法として最も一般的なのは、お支払いオプションを実装するときにレイアウト include を通じて表示する方法です。アプリで使用できる 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 に変換します。PaymentDataRequest に渡された transactionInfo.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 の例は、googlepay_button という ID 属性を持つ 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

    次のサンプル ファイルは、IsReadyToPayRequest または PaymentDataRequest の構築に適した 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();
          }
        }