Android Pay Integration Experiment

I know when you read the topic of this android blog your mind will tell you that we are going to learn how to integrate Android Pay – a new android pay system to our android app.

This is like an experiment since Android Pay is used in few selected countries so there is no way I can test this android application to see that it is working.

For those planning to use Android Pay in countries where it is currently acceptable can used this base code as a starter in developing a full functional Android Pay feature.

 

What is Android Pay?

I asked this question knowing fully well that some of us might have not come in contact with this name.

I will take what Android Pay is from Google excerpt.

The Android Pay API offers a quick and easy path to enable secure, one touch payments in your app using your existing payment infrastructure. Once you complete the integration with your app’s payment processing flow, Android users will enjoy a rapid, consistent checkout process with Android Pay.

 

If you wish to:

Have Google process payments for you, or Sell digital goods such as movies or games

within your app, please use Google Play In-app Billing instead.

So Android Pay is use to to sell physical goods and services but if you want to sell digital product, Google suggests that you use Google Play In-app Billing. I wrote a tutorial on Android In-app Billing version 3 so you can read it to get an understanding of how to integrate it in your app.

When you integrate Android Pay in your android app, Google does not server as processing gateway rather you will choose one of supported Android Pay payment processors that will process your payment.

Information about your buyer and card details are stored in Google Server in an encrypted form.

The following steps is what happen in the Pay flow when using Android Pay according to Google

 

1. The checkout screen layout includes an instance of the WalletFragment class set to BUY_BUTTON mode. In this mode, the fragment displays an Android Pay button and sets a MaskedWalletRequest.

2. When the user clicks the Android Pay button, the app sends the Masked Wallet request to Android Pay, which loads the MaskedWallet.

3. The onActivityResult(…) method contains an Intent which in turn contains the MaskedWallet object.

4. Once it has the Masked Wallet, the app constructs an instance of the WalletFragment class set to SELECTION_DETAILS mode. In this mode, the fragment displays user options in the UI to change the payment methods and addresses. If the user changes selections, Android Pay returns a new masked wallet object.

5. When the user clicks the app’s confirmation button to place the order, the app creates a GoogleApiClient connection and requests the Full Wallet. Again, the onActivityResult(…) method contains an Intent which in turn contains the FullWallet object.

6. In order to process the payment, the app passes the Android Pay payment credentials provided by Google in the Full Wallet to the merchant server or payment gateway.

7.The end user receives a confirmation screen.

 

Create Android Pay Project

In this example, we are going to use Android Pay for checkout payment option in our previous Android Shopping Cart application.

If you have not read it I will suggest you start from there – Android Shopping Cart With PayPal Payment Integration

androidpay

First we need to enable Android Pay in our project Manifest.xml file. Add the code below to Manifest.xml file

compile 'com.google.android.gms:play-services-wallet:9.4.0'
<application...<!-- Enables the Android Pay API --><meta-data android:name="com.google.android.gms.wallet.api.enabled" android:value="true" /></application>

 

Add Android Pay SDK

For our application to have access to all the classes interfaces and methods provide by Android Pay, we are going to add Android Pay SDK in the dependency section of our app build.gradle.

compile 'com.google.android.gms:play-services-wallet:9.4.0'

 

Checkout PayMent

As seen in the previous tutorial on Android shopping cart, when a user clicks on the checkout button it will initialize Pay with Android Button.

To proceed with the payment, a user have to click the Pay with android button. Note that before you can use Android Pay you have to setup your Google Wallet Account.

 

PaymentsActivity.java

In other to experiment with some code, we will create a new activity class. Name it what you like but I will use PaymentsActivity.java.

The corresponding layout file which is activity_payments.xml will contain a FrameLayout which will hold the Pay with Android Button. The layout file code is shown below.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:wallet="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:background="@color/colorPrimaryLight"
    tools:context=".PaymentsActivity">

    <FrameLayout
        android:id="@+id/wallet_button_holder"
        android:layout_height="48dp"
        android:layout_width="200dp"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="10dp"
        android:layout_marginBottom="10dp" />

</RelativeLayout>

In the PaymentsActivity class, the Pay with Android button when clicked, will send a MaskedWalletRequest using the Android Pay API. The responses object is send to the onActivityResult() callback method.

The response object will contain partial information about the using registered address which will help to calculate the exact amount for tax and shipping.

It will also provide the user the option to change the billing or shipping address and to confirm the payment.

Once the payment is confirm, a FullWalletRequest is send and this returns full payment information about the user in an encrypted form. This will be passed to your payment processor for further processes.

Note that Android Pay API using GoogleClientApi to communicate with Google Server so we will need to instantiate a GoogleClientApi

Note that I was planning to go to UK and I want to use the opportunity to complete the code and test the application but my plan changed and I did not travel again. So I will update this tutorial when I am able to use Android Pay.

The code for the class is shown below.

import android.app.Activity;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.Toast;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.BooleanResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.identity.intents.Address;
import com.google.android.gms.wallet.Cart;
import com.google.android.gms.wallet.FullWalletRequest;
import com.google.android.gms.wallet.MaskedWallet;
import com.google.android.gms.wallet.MaskedWalletRequest;
import com.google.android.gms.wallet.PaymentMethodTokenizationParameters;
import com.google.android.gms.wallet.PaymentMethodTokenizationType;
import com.google.android.gms.wallet.Wallet;
import com.google.android.gms.wallet.WalletConstants;
import com.google.android.gms.wallet.fragment.SupportWalletFragment;
import com.google.android.gms.wallet.fragment.WalletFragmentInitParams;
import com.google.android.gms.wallet.fragment.WalletFragmentMode;
import com.google.android.gms.wallet.fragment.WalletFragmentOptions;
import com.google.android.gms.wallet.fragment.WalletFragmentStyle;
import com.inducesmile.androidpayexample.helpers.Constants;

public class PaymentsActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener{

    private static final String TAG = PaymentsActivity.class.getSimpleName();

    private GoogleApiClient mGoogleApiClient;

    private SupportWalletFragment mWalletFragment;

    private MaskedWallet maskedWallet;

    private static final int REQUEST_CODE_MASKED_WALLET = 1000;

    public static final int FULL_WALLET_REQUEST_CODE = 889;

    private double cartTotal = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_payment);

        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

        setTitle("Payment Page");

        double totalPrice = getIntent().getExtras().getDouble("TOTAL_PRICE");

        Address.AddressOptions options = new Address.AddressOptions(WalletConstants.THEME_LIGHT);

        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .enableAutoManage(this, this)
                .setAccountName("Inducesmile")
                .addApi(Wallet.API, new Wallet.WalletOptions.Builder()
                        .setEnvironment(Constants.WALLET_ENVIRONMENT)
                        .setTheme(WalletConstants.THEME_LIGHT)
                        .build())
                .build();

        mWalletFragment = SupportWalletFragment.newInstance(getWalletFragmentOption());
        initializeWalletFragment();

        Wallet.Payments.isReadyToPay(mGoogleApiClient).setResultCallback(new ResultCallback<BooleanResult>() {
            @Override
            public void onResult(@NonNull BooleanResult booleanResult) {
                if (booleanResult.getStatus().isSuccess()) {
                    if (booleanResult.getValue()) {
                        // Show Android Pay buttons
                        initializeWalletFragment();
                    } else {
                        // Hide Android Pay buttons, show a message that Android Pay
                        // cannot be used yet, and display a traditional checkout button
                    }
                } else {
                    // Error making isReadyToPay call
                    Log.e(TAG, "isReadyToPay:" + booleanResult.getStatus());
                }
            }
        });
    }

    private PaymentMethodTokenizationParameters useStripePaymentGatewayForProcessing() {
        PaymentMethodTokenizationParameters parameters = PaymentMethodTokenizationParameters.newBuilder()
                .setPaymentMethodTokenizationType(PaymentMethodTokenizationType.PAYMENT_GATEWAY)
                .addParameter("gateway", "stripe")
                .addParameter("stripe:publishableKey", Constants.PUBLISHABLE_KEY)
                .addParameter("stripe:version", Constants.VERSION)
                .build();
        return parameters;
    }

    private WalletFragmentOptions getWalletFragmentOption(){
        WalletFragmentStyle walletFragmentStyle = new WalletFragmentStyle()
                .setBuyButtonText(WalletFragmentStyle.BuyButtonText.BUY_WITH)
                .setBuyButtonAppearance(WalletFragmentStyle.BuyButtonAppearance.ANDROID_PAY_DARK)
                .setBuyButtonWidth(WalletFragmentStyle.Dimension.MATCH_PARENT);

        WalletFragmentOptions walletFragmentOptions = WalletFragmentOptions.newBuilder()
                .setEnvironment(Constants.WALLET_ENVIRONMENT)
                .setFragmentStyle(walletFragmentStyle)
                .setTheme(WalletConstants.THEME_LIGHT)
                .setMode(WalletFragmentMode.BUY_BUTTON)
                .build();
        return walletFragmentOptions;
    }

    private void initializeWalletFragment(){
        WalletFragmentInitParams.Builder startParamsBuilder = WalletFragmentInitParams.newBuilder()
                .setMaskedWalletRequest(sendMaskedWalletRequest())
                .setMaskedWalletRequestCode(REQUEST_CODE_MASKED_WALLET)
                .setAccountName("Inducesmile");
        mWalletFragment.initialize(startParamsBuilder.build());

        // add Wallet fragment to the UI
        getSupportFragmentManager().beginTransaction().replace(R.id.wallet_button_holder, mWalletFragment).commit();
    }

     private MaskedWalletRequest sendMaskedWalletRequest() {
        MaskedWalletRequest request = MaskedWalletRequest.newBuilder()
                .setMerchantName(Constants.MERCHANT_NAME)
                .setPhoneNumberRequired(true)
                .setShippingAddressRequired(true)
                .setCurrencyCode(Constants.CURRENCY_CODE_USD)
                .setEstimatedTotalPrice(String.valueOf(cartTotal))
                // Create a Cart with the current line items. Provide all the information
                // available up to this point with estimates for shipping and tax included.

                .setCart(Cart.newBuilder()
                        .setCurrencyCode(Constants.CURRENCY_CODE_USD)
                        .setTotalPrice(String.valueOf(cartTotal))
                        //.setLineItems()
                        .build())
                .setPaymentMethodTokenizationParameters(useStripePaymentGatewayForProcessing())
                .build();
        return request;
    }

    private void makeFullWalletRequest(){
        Wallet.Payments.loadFullWallet(mGoogleApiClient, generateFullWalletRequest(), FULL_WALLET_REQUEST_CODE);
    }

    private FullWalletRequest generateFullWalletRequest() {
        FullWalletRequest request = FullWalletRequest.newBuilder()
                .setGoogleTransactionId(maskedWallet.getGoogleTransactionId())
                        .setCart(Cart.newBuilder()
                                .setCurrencyCode(Constants.CURRENCY_CODE_USD)
                                .setTotalPrice(String.valueOf(cartTotal))
                                //.setLineItems(lineItems)
                                .build())
                        .build();
        return request;
    }

    @Override
    public void onConnected(@Nullable Bundle bundle) {

    }

    @Override
    public void onConnectionSuspended(int i) {

    }

    @Override
    public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {

    }

    @Override
    public void onStart() {
        super.onStart();
        mGoogleApiClient.connect();
    }

    @Override
    public void onStop() {
        super.onStop();
        if (mGoogleApiClient.isConnecting() || mGoogleApiClient.isConnected()) {
            mGoogleApiClient.disconnect();
        }
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        int errorCode = -1;
        if (data != null) {
            errorCode = data.getIntExtra(WalletConstants.EXTRA_ERROR_CODE, -1);
        }
        switch (requestCode) {
            case REQUEST_CODE_MASKED_WALLET:
                switch (resultCode) {
                    case Activity.RESULT_OK:
                        Log.d(TAG, "Testing for output");
                        if (data != null) {
                            maskedWallet = data.getParcelableExtra(WalletConstants.EXTRA_MASKED_WALLET);

                            // call to get the Google transaction id
                            String googleTransactionId = maskedWallet.getGoogleTransactionId();
                            detailedMaskedWalletInformation(maskedWallet);
                        }
                        break;
                    case Activity.RESULT_CANCELED:
                        break;
                    default:
                        handleError(errorCode);
                        break;
                }
                break;

            case WalletConstants.RESULT_ERROR:
                handleError(errorCode);
                break;

            default:
                super.onActivityResult(requestCode, resultCode, data);
                break;
        }
    }

    void handleError(int errorCode) {
        switch (errorCode) {
            case WalletConstants.ERROR_CODE_SPENDING_LIMIT_EXCEEDED:
                Toast.makeText(PaymentsActivity.this, "Error with your payment", Toast.LENGTH_LONG).show();
                break;
            case WalletConstants.ERROR_CODE_INVALID_PARAMETERS:
            case WalletConstants.ERROR_CODE_AUTHENTICATION_FAILURE:
            case WalletConstants.ERROR_CODE_BUYER_ACCOUNT_ERROR:
            case WalletConstants.ERROR_CODE_MERCHANT_ACCOUNT_ERROR:
            case WalletConstants.ERROR_CODE_SERVICE_UNAVAILABLE:
            case WalletConstants.ERROR_CODE_UNSUPPORTED_API_VERSION:
            case WalletConstants.ERROR_CODE_UNKNOWN:
            default:
                // unrecoverable error
                break;
        }
    }

    private void detailedMaskedWalletInformation(MaskedWallet maskedWallet){

    }
}

 

Final note

If you have worked with Android Pay, it will be good if you share your experience with us.

This brings us to the end of this experiment. I hope that you have learn something new.

You can download the code for this tutorial below. If you are having hard time downloading the tutorial, kindly contact me.

Remember to subscribe with your email address so that you will be among the first to receive my new android blog post once it is published.

4 Comments

    • Inducesmile

Add a Comment