POS Integration Guide


Concepts

The SDK has a few core concepts before we get started.

Definitions

  • POS - The Android application using the SDK. This may or may not be an actual Point-of-Sale application. This application is generally used by a cashier or employee of the merchant.

  • POI - The point of interaction for the end customer, this is the device that collects card information and other similar input.

  • Payment Service - A background service running on the Android system that receives the calls from the SDK and either handles it directly or adapts it to the protocol used by the POI.

  • Payment App - The application running on the POI that handles the card information and other payment / transaction details, communicating this to the host to authorize the payment, or storing the data for later and issuing a temporary approval if offline authorization is enabled.

  • Host / Payment Host - Receives the card data and other information from the Payment App, and actually enables the funds to be transferred. This can be a payment gateway, processor, etc.

  • Session - The period between TransactionManager.startSession(...) and TransactionManager.endSession() where the POI is reserved for the POS, blocking anything else from performing operations with the POI, and keeping the screen in a ready state to display information from the POS.

  • Order - A collection of basket items and payments that are related by the same Invoice, linking the original basket items, refunded/replaced items, and all of the associated payments such as sales, pre-auths, pre-auth completions, voids, refunds, etc. There is no explicit object in the SDK for an order, it is an abstract concept represented by a single Invoice ID.

  • Transaction - A collection of basket items and payments that are related by the same Invoice for a specific purpose. Similar to an Order, except that it has a type which defines its purpose, such as Transaction.PAYMENT_TYPE or Transaction.REFUND_TYPE. There can be one or more transactions for an order. There is always one transaction object associated with a Session.

State Matters

There are 3 states in particular, TransactionManager.STATE_LOGGED_IN, TransactionManager.STATE_SESSION_OPEN, and TransactionManager.STATE_PAYMENT_PROCESSING. Some commands will only work in a specific state, though commands that work in an earlier state will also work in a later state (except for TransactionManager.STATE_PAYMENT_PROCESSING). For example, ReportManager.queryTransactions(query) will work once logged in, as well as during a session and the transitions between, but will not work while a payment is in progress, because the POI is completely dedicated to handling the payment during that state. The current state can be synchronously retrieved using TransactionManager.getState().

Logging in (TransactionManager.login(...)) informs the POI that a cashier is present, and enables the POI to download/configure itself based on the cashier ID.

Starting a session (TransactionManager.startSession(...)) reserves the POI for use by the POS, enabling an order to be built and payments to be processed. There should only be one “order” per session, where an order is an optional collection of basket items and a set of payments/refunds/voids, all associated with a single Invoice ID. It is acceptable to manage a single order across multiple sessions, but it’s not recommended to handle multiple orders within a single session.

Starting a payment or performing some other payment-related action will move it to the payment processing state.

@startuml
!include uml_styles.iuml
hide footbox

participant POS order 10
participant PSDK order 20
|||
POS -> PSDK: TransactionManager.getTransactionManager()
POS ->> PSDK: transactionManager.login()
POS <<- PSDK: TransactionEvent.LOGIN_COMPLETED
group STATE_LOGGED_IN
  POS ->> PSDK: transactionManager.startSession()
  group STATE_SESSION_OPEN
    POS <<- PSDK: CommerceEvent.SESSION_STARTED
    POS <<->> PSDK: update POI display with basket items
    POS ->> PSDK: transactionManager.startPayment()
    group STATE_PAYMENT_PROCESSING
      POS <<- PSDK: TransactionEvent.TRANSACTION_PAYMENT_STARTED
      ...notifications/other messages...
      POS <<- PSDK: TransactionEvent.TRANSACTION_PAYMENT_COMPLETED
    end
    == Repeat payments until full payment is collected ==
    POS ->> PSDK: transactionManager.endSession()
    POS <<- PSDK: CommerceEvent.SESSION_ENDED
  end
  == Repeat sessions until cashier logs out ==
  ...
  POS ->> PSDK: transactionManager.logout()
  POS <<- PSDK: TransactionEvent.LOGOUT_COMPLETED
end
|||

@enduml

Note

There are some actions that can be performed without a session active but after login, primarily actions on the ReportManager. Other actions, related to setting parameters such as TransactionManager.setDeviceParams(Map) or TransactionManager.getDeviceInformation() can be performed even before login.

All calls are asynchronous (except a few)

Nearly all of the calls are asynchronous, returning an immediate value to indicate if the call could be successfully transmitted, and later returning the actual result to the listener. The immediate value returned only indicates if it was possible to send the call across the bound interface and that the bound interface is in a state to accept the call and queue it to be sent to the POI. If this immediate result returns an error status, the listener will not receive any event for the call.

The asynchronous calls are placed into a queue, so if one were to call login(), startSession(), startPayment(), the payment would be properly handled if the preceding calls succeeded without error, otherwise they are going to fail due to a state issue (see State Matters).

The exceptions to this rule do not require communication with the POI, allowing the result to be returned immediately. These are listed below.

Separate Processes means Separate Objects

The Services are separate processes, which means that the memory is separate. Each API call to a processes marshalls and unmarshalls the objects across the process boundary, meaning that each application has a copy of the objects being used. This can be confusing and lead to mistakes while programming, such as setting a field on an object within the POS and expecting it to be set within the Payment Service, or modifying an object locally without using the latest version of that object from the service, and causing a merge issue. The services can perform most of the basic merges necessary to run easily, but it’s important to remember that if an object is changed locally and the update needs to be reflected in the service, this object must be sent to the service using one of the API calls defined. It is also important to keep local copies updated with or replaced by the latest objects received by the listener.



Getting Started

Get Transaction Manager

The first step is to acquire an instance of the TransactionManager. This is best done on a background thread so that the retrieved instance can be already bound to the service and immediately useable (see Best in the background). Below is a snippet to easily retrieve it from the background, but this can be done in a variety of ways, and should be consistent with the existing design instead of doing this exact code.

private TransactionManager mTransactionManager;
private void getStarted() {
    new AsyncTask<Context, Void, Void>(){
        @Override
        protected Void doInBackground(Context... contexts) {
            mTransactionManager = TransactionManager.getTransactionManager(contexts[0]);
            return null;
        }
    }.execute(someContext);
}

If it must be called from a foreground thread, it is best to use TransactionManager.getTransactionManager(Context, ServiceConnection), using the ServiceConnection’s onServiceConnected method to be notified when the service is properly bound, and is now ready to be called. Otherwise, most of the methods will return an error status (see When the status code is non-zero after calling a method).

Create a Commerce Listener

A listener must be created so various events derived from CommerceEvent can be processed. The CommerceListener must implement the CommerceEvent.handleEvent() method. The event type can be determined by calling the CommerceEvent.getType() method of CommerceEvent. The listener can must be passed for TransactionManager.login(...) and TransactionManager.startSession(...). Additional listeners can be added/removed using TransactionManager.addGeneralListener() for events not within a session, TransactionManager.addSessionListener() for events within a session, and then the matching RemoveListener() calls. There must be at least one valid listener while a session is open, the last listener cannot be removed during this state.

private CommerceListener mCommerceListener = new CommerceListener() {
    @Override
    public CommerceResponse handleEvent(CommerceEvent commerceEvent) {
        switch (commerceEvent.getType()) {
            // Received in response to TransactionManager.login().
            case TransactionEvent.LOGIN_COMPLETED:
              break;

            // Received in response to TransactionManager.startSession().
            case CommerceEvent.SESSION_STARTED:
                break;

            // Received for methods called on the BasketManager.
            case BasketEvent.TYPE:

            // Received in response to TransactionManager.startPayment().
            case TransactionEvent.TRANSACTION_PAYMENT_COMPLETED:
                break;

            // Received in response to TransactionManager.processVoid()
            // or TransactionManager.processRefund().
            case TransactionEvent.TRANSACTION_ENDED:
                break;

            // Received in response to TransactionManager.endSession().
            case CommerceEvent.SESSION_ENDED:
                break;

            // Received in response to ReportManager reconciliation calls.
            case ReconciliationEvent.TYPE:
                break;

            default:
                break;
        }
    }
}

Note

The Payment Service is a separate process, and does not keep the listeners from being destroyed in the POS process. It is the responsibility of the POS to maintain a valid reference to the listeners while they need to be active. See Separate Processes means Separate Objects for more information.

Login / Logout

Once the transaction manager is obtained and bound, and there is a listener ready, it’s time to login. The login and logout calls should match the presence of the cashier at the station, when the cashier logs in to the device, call TransactionManager.login(...), if the cashier logs out or is logged out automatically, call TransactionManager.logout(). Reusing the code example from above, the login call can be done as soon as the transaction manager is retrieved on the background thread, or called as soon as the ServiceConnection object is notified. In most cases, only the listener is required for logging in, the other parameters are optional.

private TransactionManager mTransactionManager;
private BasketManager mBasketManager;
private CommerceListener mCommerceListener = new CommerceListener() {
    @Override
    public CommerceResponse handleEvent(CommerceEvent commerceEvent) {
        switch (commerceEvent.getType()) {
            // Received in response to TransactionManager.login().
            case TransactionEvent.LOGIN_COMPLETED:
                break;
        }
    }
};

private void getStarted() {
    new AsyncTask<Context, Void, Void>(){
        @Override
        protected Void doInBackground(Context... contexts) {
            mTransactionManager = TransactionManager.getTransactionManager(contexts[0]);
            mTransactionManager.login(mCommerceListener, null, null, null);
            return null;
        }
    }.execute(someContext);
}

If there is no authentication mechanism for the cashier, then login should be called when the device wakes, and logout when the device sleeps.

Start a Session

After login, it is necessary to start a session for the transaction. An instance of the CommerceListener class must be passed to the TransactionManager.startSession(...) method. This class acts as the listener for events passed back from TransactionManager. Building on the example above, after login is completed we can start the session. Once we have a session open, items can be added to the POI display and payments can be processed.

private TransactionManager mTransactionManager;
private BasketManager mBasketManager;
private CommerceListener mCommerceListener = new CommerceListener() {
    @Override
    public CommerceResponse handleEvent(CommerceEvent commerceEvent) {
        switch (commerceEvent.getType()) {
            // Received in response to TransactionManager.login().
            case TransactionEvent.LOGIN_COMPLETED:
                mTransactionManager.startSession(mCommerceListener);
                break;

            // Received in response to TransactionManager.startSession().
            case CommerceEvent.SESSION_STARTED:
                // Session is good to go, start adding items or processing payments.
                mBasketManager = mTransactionManager.getBasketManager();
                break;
        }
    }
};


Best Practices

User Inactivity

It is important that a session not be kept open without any interaction, as it blocks the POI from performing important internal updates and prevents any other application from being able to use the device. We recommend ending the session if the cashier is inactive somewhere between 10 and 30 seconds. It can be good to immediately start a session once a previous session is ended, this can help a cashier dealing with a long line and needing to key in orders rapidly, but if the cashier is not doing anything, please end the session.

Best in the background

The SDK provides the interface to bound services running separately on the device. Android returns the binding on the main thread, which means the SDK can block a background thread when first initializing, waiting until the background service is started and bound before returning, but it cannot block if it is initializing on the main UI thread. Calls to the SDK will not do anything until the service is bound, so it is recommended that all calls into the SDK be handled from a background thread, or at least that the initialization of the SDK happens from a background thread. After the service is bound, it doesn’t really matter which threads are used.



Warning

Verifone Confidential

This documentation is protected by law from any form of duplication unless prior permission is obtained from the officers of Verifone.