implementing flow services - AEVI-AppFlow/pos-android-sdk GitHub Wiki

This is an introduction to the Payment Flow Service API relevant for value added services and payment applications.

There are three types of flows; payment, generic and status updates. These flows may have one or multiple stages defined. Any given application with a particular purpose will fit into AppFlow via what stage it will be called in. More details of flow stages can be found here.

Defining the stage entry points

Depending on the needs and requirements of your application, there are different approaches to defining your entry points for your service to be called.

If your service supports multiple stages where the majority can be handled without presenting a UI, the best option is to extend BasePaymentFlowService, which gives you callbacks into a service for each stage and gives you full freedom to choose how to handle that request.

If on the other hand all your request handling involves user interaction, looking at the ActivityProxyService to delegate straight to your activities might be a better option.

Before we look at these two options, we need to cover stage models, which are the models your service will be working with to read and augment data for any given stage.

Stage models

As each stage of the flow typically deals with different input and output data, there is generally one stage model defined per stage. The one exception is for PRE_TRANSACTION and POST_CARD_READING, where they do in fact share the same data types and same rules, meaning they also share model.

All the stage models provide a similar set of functions,

  • Initialise from a service context
  • Initialise from an activity context
  • Get the request (input data) for this stage
  • A number of augmentation functions
  • A method to send the response
  • Start an activity to handle the request (from a service context)

In addition, the stage models sets up some interaction points with the API behind the scenes to reduce the amount of boilerplate required for app developers.

See PreTransactionModel for an example of a stage model.

Further down you can find a table which outlines what intent actions and stage models to use for each stage.

Extending BasePaymentFlowService

The BasePaymentFlowService, contains a method per stage that a subclass can override to handle that particular stage.

An example of such a method is,

protected void onPreFlow(PreFlowModel model) {}

Here is an example of an implementation of this from the Payment Service Sample. It illustrates how some stages can be delegated to activities, and others to be handled from the service context itself.

public class PaymentService extends BasePaymentFlowService {

    @Override
    protected void onPaymentCardReading(CardReadingModel model) {
        model.processInActivity(context, PaymentCardReadingActivity.class);
    }

    @Override
    protected void onTransactionProcessing(TransactionProcessingModel model) {
        model.processInActivity(context, TransactionProcessingActivity.class);
    }

    @Override
    protected void onGeneric(GenericStageModel model) {
        GenericStageHandler.handleGenericRequest(model);
    }

    @Override
    protected void onForceFinish(ClientCommunicator clientCommunicator) {
        super.onForceFinish(clientCommunicator); // be sure to call the super method here for auto finish

        // Finish up anything you might have started in here. Activities started via `processInActivity` will
        // be finished automatically and any response sent here will be ignored
    }
}

This is defined in the manifest as follow,

        <service
            android:name=".service.PaymentService"
            android:exported="true">
            <intent-filter>
                <action android:name="com.aevi.sdk.flow.action.PROCESS_GENERIC"/>
                <action android:name="com.aevi.sdk.flow.action.READ_PAYMENT_CARD"/>
                <action android:name="com.aevi.sdk.flow.action.PROCESS_TRANSACTION"/>
            </intent-filter>
        </service>

Using ActivityProxyService

ActivityProxyService is a service defined in the API that can be used to proxy through any request to an activity. This is useful if all your requests handling requires user interaction, as you can avoid setting up pure boilerplate services just to start activities.

The service is defined in the manifest as follows, where you choose which stages you want to handle via the intent actions. The example below is set up for PRE_FLOW only.

        <service
            android:name="com.aevi.sdk.pos.flow.service.ActivityProxyService"
            android:exported="true">
            <intent-filter>
                <action android:name="com.aevi.sdk.flow.action.PROCESS_PRE_FLOW"/>
            </intent-filter>
        </service>

You then need to define an activity for each stage you want the requests proxied to, as per below for PRE_FLOW. Pay attention to the intent action.

        <activity
            android:name=".ui.PreFlowActivity"
            android:excludeFromRecents="true">
            <intent-filter>
                <action android:name="com.aevi.sdk.flow.action.PROCESS_PRE_FLOW_IN_ACTIVITY"/>
            </intent-filter>
        </activity>

The way the ActivityProxyService looks up what activity to delegate to is via the stage name. It will build the intent action as follows, intentAction = "com.aevi.sdk.flow.action.PROCESS_" + stageName + "_IN_ACTIVITY".

Once your activity is created (via onCreate(), you can then initialise the stage model from the static fromActivity() method, as per below for PRE_FLOW;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_pre_flow);
        PreFlowModel preFlowModel = PreFlowModel.fromActivity(this);
    }

The models only keep a weak reference to your activities, so there are no risks of activity memory leaks via the models even if your code passes these onto other classes in your application.

Defining the generic stage entry points

In addition to being called for payment flows, your application can also handle what we refer to as generic requests that applications can initiate via the client APIs. These generic requests may be of well-defined types like tokenisation, or be custom for a particular app or group of apps. There is one such example in the samples where the initiation sample calls into the flow service sample to show loyalty point balance.

There are only two possible stages for generic

  • GENERIC
  • POST_GENERIC

See Flow Stages for details on the stages.

As outlined earlier, the stages can be handled via the BasePaymentFlowService and ActivityProxyService.In addition, for generic stages, there are also specific base classes (due to the fact that generic requests are part of the AppFlow base APIs and not unique to the POS implementation of AppFlow).

See BaseGenericService and BasePostGenericService for base classes specifically for these stages. These are demonstrated via the samples as well. These may be preferred if your service is only interested in generic flows, as otherwise the other options are more suitable.

Defining status update entry point

Status update flows are a bit different in that they are processed in the background and requests are submitted to a request queue. They can be processed in parallel to the standard payment and generic flows.

Applications that process status updates must only run in the background - i.e handle the update in the service and not launch any activities. Applications can choose to finish without passing back any data to the client via the finish() method in the model, or pass back a set of references via the finishWithReferences() method.

To handle these updates, either extend the BaseStatusUpdateService or override onStatusUpdate() method in a BasePaymentFlowService implementation.

Stages summary

Stage Intent Action Stage model
PRE_FLOW com.aevi.sdk.flow.action.PROCESS_PRE_FLOW PreFlowModel
SPLIT com.aevi.sdk.flow.action.PROCESS_SPLIT SplitModel
PRE_TRANSACTION com.aevi.sdk.flow.action.PROCESS_PRE_TRANSACTION PreTransactionModel
POST_CARD_READING com.aevi.sdk.flow.action.PROCESS_POST_CARD_READING PreTransactionModel
POST_TRANSACTION com.aevi.sdk.flow.action.PROCESS_POST_TRANSACTION PostTransactionModel
POST_FLOW com.aevi.sdk.flow.action.PROCESS_POST_FLOW PostFlowModel
GENERIC com.aevi.sdk.flow.action.PROCESS_GENERIC GenericModel
POST_GENERIC com.aevi.sdk.flow.action.PROCESS_POST_GENERIC PostGenericModel
STATUS_UPDATE com.aevi.sdk.flow.action.PROCESS_StatusUpdate StatusUpdateModel

Asynchronous request handling

A flow service can handle a request asynchronously in a fire-and-forget fashion where FPS does not wait for any response. This is mainly useful for the "post" stages where there may be apps that are interested in the outcome of things (such as for analytics), but do not want to provide any input back and delay the processing completion.

Any flow service can do this via simply returning back an empty response immediately from the model (finish(), skip(), etc) and then continuing to execute. It is up to each flow service if and when the service is stopped as described in a section further down on this page.

Undoing your augmentation

Due to the nature of services getting called in sequence, where an outcome is determined somewhere in the middle, it is possible that a flow will be cancelled, declined or otherwise aborted before it is successfully completed. This means that any action you performed at a stage early in the flow, like using a customers loyalty points for a purchase, is now invalid.

To mitigate this and allow a flow service to undo previously made changes, AppFlow supports response listeners, which is a separate entry point into your application to which a PaymentResponse or Response is passed so that your application can review the outcome.

See Flow Response Listeners for more information.

Service lifecycle

AppFlow does not ever stop your service. It is entirely up to your implementation if the service should remain running in the background (until Android kills it) and accepts requests, or shut down after each request has been handled via a call to stopSelf().

You can also call setStopServiceOnEndOfStream(true) from the service to indicate that you want the service to automatically be stopped after the client (FPS) has closed the connection.

Do note however that if you wish to process data in the background / asynchronously to the flow (via passing back an empty response and continuing parsing the input data - typically for analytics, etc), you need to ensure you don't call the above method or stopSelf() too early.

Supported flow types

Via the PaymentFlowServiceInfo model, your service will be reporting various details, including what flow types are supported and whether the service supports any custom request types.

Most flow services will be supporting a specific set of flow types, like sale or refund or tokenisation. As an example, a loyalty application would typically only be applicable to sale flows.

It is important to understand here that the flow types reported by a flow service does not automatically enable a client to initiate those types. What a client can initiate is entirely based on the defined flows and their associated flow types. It is possible for a flow service to support a type for which there is no flow and for a flow to list a flow service for a type it does not support.

The latter scenario is where the supported flow types reported from the flow service comes into effect. FPS will ensure that a flow service that does not support a given type does not get called. This is to ensure that flow services do not need to worry about handling unsupported types.

However, there are use cases where a flow service may want to get called for any type and handle it on its own, in which case it can leave the supported flow types set empty, which means FPS will always allow any type for request to be sent to it.

Defining custom types

In addition to specifying support for the pre-defined types like sale etc, a flow service can also define a new custom type via the PaymentFlowServiceInfo. It is possible for a flow service to only support custom request types and none of the pre-defined types.

An example of a custom request type can be found in the Flow Service Sample, which defines showLoyaltyPointsBalance as a type, allowing a client to initiate a request which will show a customer their loyalty points balance.

If your custom request requires input data and/or contains output data, please see Reference Values.

AppFlow actions

AppFlow provides the merchant with flow controls via a standard sticky Android notification. The possible actions a merchant can choose here are

  • Cancel - Cancel the flow (only applicable to PRE_FLOW and SPLIT)
  • Resume - Resume the current step (if merchant left the activity for some reason)
  • Skip - Skip this step (not applicable to all stages)

These choices can lead to two outcomes for your service, documented below.

Resume

If your service is handling a request in an activity, and the merchant/operator presses the home button, or otherwise leave that activity, they may then choose to resume it later via the notification. This means your activity (provided it follows the AppFlow Guidelines, may then be re-created.

Force finish

If the merchant cancels the flow, or chooses to skip the application via the notification, or your service times out, your service will receive a "force finish" request. At this stage, your service can no longer send back a response and you must shut down any UI or other processing in progress.

Next up

⚠️ **GitHub.com Fallback** ⚠️