Reader SDK

Build on Android

Build with Reader SDK on Android to process in-person payments with Square hardware.

Prerequisites

To build with Reader SDK, the following must be true:

  • Your application minSdkVersion is API 19 (KitKat 4.4) or higher.
  • Your application targetSdkVersion is API 26 (Oreo, 8.0) or lower.
  • You are using the Android Gradle Plugin version 3.0.0 or greater. Reader SDK may work with 2.3.0 or greater, but stability is not guaranteed.
  • Your application uses support library version 26.0.2 and Google Play Services version 12.0.1. Square cannot guarantee that the SDK will work with different versions of these libraries.
  • You are not using Proguard for code optimization. Compressing the Reader SDK binary removes critical bytecode elements and results in runtime errors.

Assumptions

This guide makes the following assumptions:

  • You have read the Reader SDK intro. This is a how-to guide and does not provide an overview of the SDK's functionality.
  • Your version of Reader SDK adheres to Square's update policy. To limit risk to developers and their users, Square enforces an update policy for Reader SDK requiring developers keep their version of Reader SDK current.
  • You have a Square Account enabled for payment processing. If you have not enabled payment processing on your account (or you're not sure), visit squareup.com/activate.
  • You are building with Android Studio and Gradle. Android Studio is used because it is the most common development environment for Android apps, but Reader SDK is IDE agnostic and can be used with other development environments if desired.
  • You are generally familiar with developing apps on Android. If you are new to Android development, we recommend reading the Getting Started Guide at the Android Developers site before continuing.

Device Permissions

To work with Square Readers, apps must have the device permissions noted below. If the required device permissions are not granted when the checkout flow initiates, Reader SDK prompts the user to grant the necessary permissions.

Android device permission Purpose
Location To confirm payments are occurring in a supported country.
Audio To connect Magstripe Readers.
Bluetooth To connect Contactless Readers.
Device Storage To store information during checkout.
Phone Access To identify the device sending information to Square servers.

Step 1: Request Reader SDK credentials

  1. Open the Square Application Dashboard. You will be prompted to login or create a new account.
  2. Create a new Square application.
  3. Click on the new application to bring up the Square application settings pages.
  4. Open the Reader SDK page and click "Request Credentials" to generate your Reader SDK repository password.

You will need the Application ID and Repository password from the Reader SDK page to configure Reader SDK in the next steps:

Readersdk settings page

Step 2: Configure your Android project for Reader SDK

Configure Gradle

  1. Update the gradle.properties file in the root folder of your project to increase the max heap size provided to the Gradle daemon and set variables for the Square application ID and repository password:
SQUARE_READER_SDK_APPLICATION_ID={APPLICATION ID FROM STEP 1}
SQUARE_READER_SDK_REPOSITORY_PASSWORD={REPO PASSWORD FROM STEP 1}
org.gradle.jvmargs=-Xmx4g
  1. Add the Reader SDK variables from your properties file and confirm that the Google repository is set properly in the build.gradle file of your :app module:
repositories {
  google()
  maven {
    url "https://sdk.squareup.com/android"
    credentials {
      username SQUARE_READER_SDK_APPLICATION_ID
      password SQUARE_READER_SDK_REPOSITORY_PASSWORD
    }
  }
  jcenter()
}

Configure the build dependencies

  1. Reader SDK and its dependencies contain more than 65k methods, so your build script must enable Multidex. If your minSdkVersion is less than 21, you also need to include the multidex dependency:
android {
  defaultConfig {
    minSdkVersion 19
    targetSdkVersion 26
    multiDexEnabled true
  }
}

dependencies {
  // Add this dependency if your minSdkVersion < 21
  implementation 'com.android.support:multidex:1.0.3'
  // ...
}
  1. Configure the Multidex options:
android {
  // ...
  dexOptions {
    // Ensures incremental builds remain fast
    preDexLibraries true
    // Required to build with Reader SDK
    jumboMode true
    // Required to build with Reader SDK
    keepRuntimeAnnotatedClasses false
  }
  // ...
}
  1. Add the Reader SDK dependencies:
dependencies {
  def readerSdkVersion = "1.0.4"
  implementation "com.squareup.sdk.reader:reader-sdk-$SQUARE_READER_SDK_APPLICATION_ID:$readerSdkVersion"
  runtimeOnly "com.squareup.sdk.reader:reader-sdk-internals:$readerSdkVersion"
  // ...
}

Working with higher support library versions

Reader SDK is guaranteed to work with support library version 26.0.2. If you want to use a higher version of support library, you will need to update build.gradle to add the targeted version of the required libraries (e.g. design, support-v4, recyclerview-v7).

If you use a higher version of the support library, we recommend establishing a resolution strategy in build.gradle to ensure all support library dependencies are on the same version. For example:

configurations.all {
    resolutionStrategy {
      eachDependency { details ->
        // Force all primary support libraries to the same version
        if (details.requested.group == 'com.android.support'
          && details.requested.name != 'multidex'
          && details.requested.name != 'multidex-instrumentation') {
          // Force the version that works for you.
          // Square has tested ReaderSDK with 26.0.2
          details.useVersion '26.0.2'
        }
      }
    }
  }

Step 3: Extend the Android Application class

  1. Create an Application class that extends android.app.Application and specify the android:name property for the <application> node in AndroidManifest.xml (for more help with this step, see Understanding the Android Application Class on Codepath).

  2. Import and initialize Reader SDK:

import com.squareup.sdk.reader.ReaderSdk;
import com.squareup.sdk.reader.authorization.AuthorizationManager;
import com.squareup.sdk.reader.authorization.AuthorizeErrorCode;
import com.squareup.sdk.reader.authorization.Location;
import com.squareup.sdk.reader.core.CallbackReference;
import com.squareup.sdk.reader.core.Result;
import com.squareup.sdk.reader.core.ResultError;

public class ExampleApplication extends Application {

  @Override public void onCreate() {
    super.onCreate();
    ReaderSdk.initialize(this);
  }

  @Override protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    // Required if minSdkVersion < 21
    MultiDex.install(this);
  }
}

Step 4: Add code to request and use a mobile authorization code

To authorize the SDK, you must build an authorization service to retrieve a mobile authorization code from the Mobile Authorization API and return it to your application.

Mobile authorization codes are short lived and should be used immediately to authorize Reader SDK. Authorization is valid until it is explicitly revoked by calling deauthorize or your application fails to take a payment within 90 days. Mobile authorization codes do not need to be manually refreshed under normal operations.

Create a new AuthorizeActivity with 2 skeleton functions (retrieveAuthorizationCode() and onAuthorizationCodeRetrieved()), which can be customized to call your authorization service:

public class AuthorizeActivity extends Activity {

  @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.authorize_activity);

    View authorizeButton = findViewById(R.id.authorize_button);
    authorizeButton.setOnClickListener(view -> retrieveAuthorizationCode());
  }

  private void retrieveAuthorizationCode() {
    // TODO: Asynchronous code to retrieve a mobile authorization code.
    // Calls onAuthorizationCodeRetrieved(String) with the resulting code.
  }

  private void onAuthorizationCodeRetrieved(String authorizationCode) {
    showAuthorizationInProgress(true);
    ReaderSdk.authorizationManager().authorize(authorizationCode);
  }

  private void showAuthorizationInProgress(boolean inProgress) {
    // TODO: Display or hide a loading indicator
  }
}

Step 5: Add code to handle authorization results

  1. Add a callback (onAuthorizeResult()) to the Authorization Manager in the onCreate method of your authorization activity using AuthorizationManager.addAuthorizeCallback():
public class AuthorizeActivity extends Activity {

  // ...

  private CallbackReference authorizeCallbackRef;

  @Override protected void onCreate(Bundle savedInstanceState) {

    //...

    AuthorizationManager authManager = ReaderSdk.authorizationManager();
    authorizeCallbackRef =
        authManager.addAuthorizeCallback(this::onAuthorizeResult);
  }

  // ...

  private void onAuthorizeResult(Result<Location,
      ResultError<AuthorizeErrorCode>> result) {
    // TODO: Handle the authorization result
  }
  1. Implement the onAuthorizeResult() callback. AuthorizeCallback.onResult() is invoked asynchronously on the main thread with a Result object. The result includes the authorized Location (in case of success) or a ResultError (in case of error):
  private void onAuthorizeResult(Result<Location,
      ResultError<AuthorizeErrorCode>> result) {
    showAuthorizationInProgress(false);
    if (result.isSuccess()) {
      goToCheckoutActivity();
    } else {
      ResultError<AuthorizeErrorCode> error = result.getError();
      switch (error.getCode()) {
        case NO_NETWORK:
          showDialog(getString(R.string.no_network), error.getMessage());
          break;
        case USAGE_ERROR:
          String dialogMessage = error.getMessage();
          if (BuildConfig.DEBUG) {
            dialogMessage += "\n\nDebug Message: " + error.getDebugMessage();
            Log.d("Auth", error.getDebugCode() + ", " + error.getDebugMessage());
          }
          showDialog(getString(R.string.error_dialog_title), dialogMessage);
          break;
      }
    }
 }
  1. Add code to clear the callback reference in your onDestroy() method to avoid memory leaks:
public class AuthorizeActivity extends Activity {

  // ...

  @Override protected void onDestroy() {
    super.onDestroy();
    authorizeCallbackRef.clear();
  }
}

Step 6: Create a CheckoutActivity

Create a new activity, CheckoutActivity, with a button that calls startCheckout() to begin the checkout flow:

import com.squareup.sdk.reader.checkout.AdditionalPaymentType;
import com.squareup.sdk.reader.checkout.CheckoutErrorCode;
import com.squareup.sdk.reader.checkout.CheckoutManager;
import com.squareup.sdk.reader.checkout.CheckoutParameters;
import com.squareup.sdk.reader.checkout.CheckoutResult;
import com.squareup.sdk.reader.checkout.CurrencyCode;
import com.squareup.sdk.reader.checkout.Money;
import com.squareup.sdk.reader.core.CallbackReference;
import com.squareup.sdk.reader.core.Result;
import com.squareup.sdk.reader.core.ResultError;

public class CheckoutActivity extends Activity {

  @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    if (!ReaderSdk.authorizationManager().getAuthorizationState().isAuthorized()) {
      goToAuthorizeActivity();
    }

    setContentView(R.layout.checkout_activity);

    View startCheckoutButton = findViewById(R.id.start_checkout_button);
    startCheckoutButton.setOnClickListener(view -> startCheckout());
  }

  private void goToAuthorizeActivity() {
      // TODO: Reader SDK is not authorized, move to Authorize Activity
  }
}

Step 7: Add code to start the checkout flow

  1. Implement the startCheckout() method by adding code to build the CheckoutParameters object and configure the checkout experience. In the code below we use CurrencyCode#current to obtain the currency code of the authorized location and set CASH as one of the additional tender types, which lets us test payments without charging a card.
public class CheckoutActivity extends Activity {
  // ...

  private CallbackReference checkoutCallbackRef;

  private void startCheckout() {
    CheckoutManager checkoutManager = ReaderSdk.checkoutManager();
    Money amountMoney = new Money(100, CurrencyCode.current());
    CheckoutParameters.Builder parametersBuilder =
        CheckoutParameters.newBuilder(amountMoney);
    if (BuildConfig.DEBUG) {
      parametersBuilder.additionalPaymentTypes(AdditionalPaymentType.CASH);
    }
    checkoutManager.startCheckoutActivity(this, parametersBuilder.build());
  }
}
  1. Add a callback (onCheckoutResult()) to the Checkout Manager in the onCreate method of CheckoutActivity using CheckoutManager.addCheckoutActivityCallback:
  @Override protected void onCreate(Bundle savedInstanceState) {

    // ...

    CheckoutManager checkoutManager = ReaderSdk.checkoutManager();
    checkoutCallbackRef =   
        checkoutManager.addCheckoutActivityCallback(this::onCheckoutResult);

  }
}
  1. Add code to clear the checkout callback reference in your onDestroy() method to avoid memory leaks:
public class CheckoutActivity extends Activity {

  // ...

    @Override protected void onDestroy() {
    super.onDestroy();
    checkoutCallbackRef.clear();
  }
}

Step 8: Add code to handle checkout results

Implement the onCheckoutResult callback to parse the checkout response. CheckoutActivityCallback.onResult() is invoked asynchronously on the main thread with a Result object. The result includes the CheckoutResult (in case of success) or a ResultError (in case of error):

public class CheckoutActivity extends Activity {
  // ...

  private void onCheckoutResult(Result<CheckoutResult, ResultError<CheckoutErrorCode>> result) {
    if (result.isSuccess()) {
      CheckoutResult checkoutResult = result.getSuccessValue();
      showCheckoutResult(checkoutResult);
    } else {
      ResultError<CheckoutErrorCode> error = result.getError();

      switch (error.getCode()) {
        case SDK_NOT_AUTHORIZED:
          goToAuthorizeActivity();
          break;
        case CANCELED:
          Toast.makeText(this, "Checkout canceled", Toast.LENGTH_SHORT).show();
          break;
        case USAGE_ERROR:
          showErrorDialog(error);
          break;
      }
    }
  }

  private void showCheckoutResult(CheckoutResult checkoutResult) {
    // TODO: Display the checkout result.
  }

  private void showErrorDialog(ResultError<?> error) {
    String dialogMessage = error.getMessage();
    if (BuildConfig.DEBUG) {
      dialogMessage += "\n\nDebug Message: " + error.getDebugMessage();
      Log.d("Checkout", error.getDebugCode() + ", " + error.getDebugMessage());
    }
    showDialog(getString(R.string.error_dialog_title), dialogMessage);
  }

  private void showDialog(CharSequence title, CharSequence message) {
    // TODO: Display a dialog to the user.
  }
}

The example code above prompts for authorization if Reader SDK is not authorized, displays the transaction details from CheckoutResult in the case of a successful transaction, and displays an error dialog (showErrorDialog) in the case of failure.

Optional: Connect a Contactless Reader

If you are using a Magstripe Reader, you do not need to write explicit code to connect the Reader. If you are using a Contactless + Chip Reader, you must add code to handle pairing.

  1. Add code to your Checkout activity to display a button that launches the Square Card Reader settings flow by calling ReaderManager#startReaderSettingsActivity() and passing in the activity:
import com.squareup.sdk.reader.hardware.ReaderManager;
import com.squareup.sdk.reader.hardware.ReaderSettingsErrorCode;

public class CheckoutActivity extends Activity {

  private CallbackReference readerSettingsCallbackRef;

  @Override protected void onCreate(Bundle savedInstanceState) {

    // ...

    View readerSettingsButton = findViewById(R.id.reader_settings_button);
    readerSettingsButton.setOnClickListener(view -> startReaderSettings());

  }

  private void startReaderSettings() {
    ReaderManager readerManager = ReaderSdk.readerManager();
    readerManager.startReaderSettingsActivity(this);
  }
  1. Add a callback (onReaderSettingsResult()) to the Reader Manager in the onCreate() method of your checkout activity using ReaderManager.addReaderSettingsActivityCallback:
  @Override protected void onCreate(Bundle savedInstanceState) {

    // ...

    ReaderManager readerManager = ReaderSdk.readerManager();
    readerSettingsCallbackRef = readerManager.addReaderSettingsActivityCallback(
        this::onReaderSettingsResult
    );

    private void onReaderSettingsResult(
        Result<Void, ResultError<ReaderSettingsErrorCode>> result) {
      // TODO: Handle the pairing result
    }
  }
}
  1. Implement the onReaderSettingsResult() method by adding code to respond to the pairing result:
private void onReaderSettingsResult(
    Result<Void, ResultError<ReaderSettingsErrorCode>> result) {
  if (result.isError()) {
    ResultError<ReaderSettingsErrorCode> error = result.getError();
      switch (error.getCode()) {
        case SDK_NOT_AUTHORIZED:
          goToAuthorizeActivity();
          break;
        case USAGE_ERROR:
          showErrorDialog(error);
          break;
      }
    }
  }
  1. Add code to clear the reader settings callback reference in your onDestroy() method to avoid memory leaks:
public class CheckoutActivity extends Activity {

  // ...

  @Override protected void onDestroy() {
    super.onDestroy();
    readerSettingsCallbackRef.clear();
  }
}

Optional: Deauthorize Reader SDK

To switch Square locations or to deauthorize the current location, you will need to deauthorize Reader SDK.

  1. Add code to your Checkout activity to display a button that deauthorizes Reader SDK by calling AuthorizationManager#deauthorize():
import com.squareup.sdk.reader.authorization.AuthorizationManager;
import com.squareup.sdk.reader.authorization.DeauthorizeErrorCode;

public class CheckoutActivity extends Activity {
  // ...

  @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.checkout_activity);

    View deauthorizeButton = findViewById(R.id.deauthorize_button);
    deauthorizeButton.setOnClickListener(view -> deauthorize());
  }

  private void deauthorize() {
    AuthorizationManager authorizationManager = ReaderSdk.authorizationManager();
    if (authorizationManager.getAuthorizationState().canDeauthorize()) {
      authorizationManager.deauthorize();
    } else {
      showDialog("Unable to deauthorize",
          "There are Square payments on this device that need to be " +
          "uploaded. Please ensure you are connected to the Internet.");
    }
  }
}
  1. Add a callback (onDeauthorizeResult()) to the Authorization Manager in the onCreate method of your checkout activity using AuthorizationManager.addDeauthorizeCallback:
public class CheckoutActivity extends Activity {

  private CallbackReference deauthorizeCallbackRef;

  @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // ...

    AuthorizationManager authorizationManager = ReaderSdk.authorizationManager();
    deauthorizeCallbackRef =
        authorizationManager.addDeauthorizeCallback(this::onDeauthorizeResult);

  }

  private void onDeauthorizeResult(
      Result<Void, ResultError<DeauthorizeErrorCode>> result) {
    // TODO: Handle the deathorize result
  }
}
  1. Implement the onDeauthorizeResult method by adding code to respond to the deauthorization result:
private void onDeauthorizeResult(
    Result<Void, ResultError<DeauthorizeErrorCode>> result) {
  if (result.isSuccess()) {
    goToAuthorizeActivity();
  } else {
    ResultError<DeauthorizeErrorCode> error = result.getError();
    if (error.getCode()) {
      showErrorDialog(error);
    }
  }
}
  1. Add code to clear the deauthorization callback reference in your onDestroy() method to avoid memory leaks:
public class CheckoutActivity extends Activity {

  // ...

  @Override protected void onDestroy() {
    super.onDestroy();
    deauthorizeCallbackRef.clear();
  }
}

Optional: Configure APK splits across ABIs

You can configure APK splitting for each of the different supported ABI architectures. If you do this, you will have to ship multiple APKs but they will be 35 – 40MB smaller than a universal APK.

android {
  // ...
  splits {
    abi {
      enable true
      reset() // clear from 'all ABIs' to 'none'
      include 'armeabi', 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
      universalApk true
    }
  }
}

// Map for the version code that gives each ABI a value.
ext.abiCodes = ['armeabi':1, 'armeabi-v7a':2, 'arm64-v8a':3, 'x86':4, 'x86_64':5]

import com.android.build.OutputFile

// For each APK output variant, override versionCode with a combination of
// ext.abiCodes * 1000 + variant.versionCode. In this example, variant.versionCode
// is equal to defaultConfig.versionCode. If you configure product flavors that
// define their own versionCode, variant.versionCode uses that value instead.
android.applicationVariants.all { variant ->

  // Assigns a different version code for each output APK
  // other than the universal APK.
  variant.outputs.each { output ->

    // Stores the value of ext.abiCodes that is associated with the ABI for this variant.
    def baseAbiVersionCode =
        // Determines the ABI for this variant and returns the mapped value.
        project.ext.abiCodes.get(output.getFilter(OutputFile.ABI))

    // Because abiCodes.get() returns null for ABIs that are not mapped by ext.abiCodes,
    // the following code does not override the version code for universal APKs.
    // However, because we want universal APKs to have the lowest version code,
    // this outcome is desirable.
    if (baseAbiVersionCode != null) {

      // Assigns the new version code to versionCodeOverride, which changes the version code
      // for only the output APK, not for the variant itself. Skipping this step simply
      // causes Gradle to use the value of variant.versionCode for the APK.
      output.versionCodeOverride =
          baseAbiVersionCode * 1000 + variant.versionCode
    }
  }
}
Prev
< Reader SDK Overview
Next
Reader SDK Technical Reference (Android) >

Contact Developer Support, join our Slack channel, or ask for help on Stack Overflow