1. Documentation
  2. AppFlow
  3. Introduction
  4. Guides

Guides


Legacy

AppFlow v1 to v2 Migration

The second version of these APIs contain some breaking changes that means you will have to adapt your application if it is currently integrated with the first version. As a general rule - review the samples if unsure about what to do.

API structure

The in v1 separate APIs payment-service-api and flow-service-api have been merged in this version into a new payment-flow-service-api.

This means you have to update your dependencies accordingly to use com.aevi.sdk.pos.flow:payment-flow-service-api:<version> instead.

There is no longer any major distinction between what was previously known as flow services (aka VAAs) and payment services. A service simply defines what stages it wants to support and that determines what function(s) they can provide. A traditional payment application is a service that gets called during the TRANSACTION_PROCESSING stage, and optionally PAYMENT_CARD_READING and GENERIC.

API clients

FlowClient has been removed and all methods are now defined in PaymentClient to improve clarity of where to find things (in particular for JavaDocs). In addition, we have the following client changes

  • getSystemSettings(), getFlowServices() and getPaymentServices() replaced by getPaymentSettings()
  • subscribeToStatusUpdates() and getCurrentRequestStatus() removed as there was no use case for them
  • processRequest() renamed to initiateRequest() to follow initiatePayment() naming

Important, the initiate method return types have also changed from Single to Completable. Read more at Handling responses.

Service / activity changes

There is no longer any need to define a service per stage, and the previous base services like BasePreFlowService etc are all gone.

This has been replaced with a new concept of "stage models", which are independent components that can be created from both service and activity contexts and provide all the relevant functions specifically for that stage. See PreTransactionModel as an example. There is one of these defined for each stage.

There is a new BasePaymentFlowService which can be extended for cases where processing should be service based. All that is required is that you override the correct method, like onPreTransaction(PreTransactionModel) and use the intent filter for that, and you will get a callback into that method when a request comes in. See PaymentService as an example of this.

In addition, there is a new solution for processing requests in an activity via another new service, ActivityProxyService, that can be used to proxy any request onto an activity. This is to avoid the boilerplate services that exists purely to sit between FPS and an activity. The ActivityProxyService can be defined to handle any number of stages, and proxies requests on to activities with the relevant stage intent action. See FlowServiceSample manifest for examples of how this is defined in the flow service sample (do note though that it is using a subclass of the API service to add function - this is not required).

The activities then add intent filters relevant for the stage it wants to handle, like com.aevi.sdk.flow.action.PROCESS_PRE_TRANSACTION_IN_ACTIVITY.

See Implementing Flow Services for documentation around this.

Manifest changes

Intent actions

All intent actions have been updated to be consistent.

See IntentActions for the full list of action names to use in the manifest now.

Remember to update all your services and content providers or FPS won't find them.

Payment services

Payment services, as in services that support transaction processing, which previously used the payment-service-api, no longer needs to define the configuration-authority meta-data for the service that implements the old com.aevi.sdk.pos.flow.action.PROCESS_PAYMENT. (The intent action should be replaced with com.aevi.sdk.flow.action.PROCESS_TRANSACTION)

Instead, your applications needs to update the intent filter for the service info provider to com.aevi.sdk.flow.action.PROVIDE_SERVICE_INFO

Flow services

Flow services also need to update the intent action for their service info provider to com.aevi.sdk.flow.action.PROVIDE_SERVICE_INFO

Service info providers

As the APIs are merged, there is now also a single base service info provider that you need to extend from, and a single builder class to generate your service info.

public class SampleServiceInfoProvider extends BasePaymentFlowServiceInfoProvider

And the builder to use now for all flow services is PaymentFlowServiceInfoBuilder.

Changes to service info builder
  • withSupportedRequestTypes() and withSupportedTransactionTypes() have been replaced by the two methods below
  • New method withCustomRequestTypes() that allows any flow service to define custom/bespoke request types, if any. Example is showLoyaltyPointsBalance from the samples. Should not be called unless there are custom types.
  • New method withSupportedFlowTypes() allows a flow service to report which of the pre-defined flow types are supported, if any. See Flow Types for more info.
  • withPaymentMethods() replaced by withCanPayAmounts(boolean, paymentMethods)
  • Support to set any custom / arbitrary data via withAdditionalInfo()

For payment service migration, note the following changes

  • withTerminalId() removed - use AEVI DMS API for retrieving device ids
  • New merchants model to represent merchants, use withMerchants(Merchant... merchants)
  • withManualEntrySupport() and withPrintsReceipts() can now be set as additional data

Transaction ids

The way transaction ids are managed have changed a bit. See [Transactions]((topics#transaction-details) for details.

Specifying a payment app

There is no longer a way to specify a payment service via an id as per before, as flow configs alone now determine what applications are available for any flow and stage.

Request -> flow mapping

The way flows are managed has changed in order to improve its flexibility. The previous withTransactionType() method in PaymentBuilder has been replaced by two new methods - withPaymentFlow(String flowType) and withPaymentFlow(String flowType, String flowName). This is due to new support for multiple flows per type. A client can still set the type only and FPS will map that to available flows, which will only be one match in the majority of cases. In cases where there is more than one flow per type, a runtime selection dialog will be presented to allow the merchant/operator to choose.

For development, testing, prototyping, etc, setting the type only is the best approach. However, for production scenarios it is generally recommended to also explicitly specify the flow to use. In most acquirer environments the defined flows will be known up front and that information is provided to all developers.

Various other model changes

There are too many minor changes to the models to cover here, so please see the change log for more details.

  • PaymentStage enum is gone and replaced by the FlowStages string definitions
  • TransactionRequest.getPaymentStage() now getFlowStage() and returns a string instead
  • Basket and Customer are now first level citizens in Payment and TransactionRequest
  • There can now be more than one basket, as any "upsells" provided by flow services will be represented in individual baskets
  • TransactionRequest has a new getTransactionId() method that returns the id for the whole transaction

Legacy Simple Payment API

AEVI has provided a "payment initiation" API referred to as the Simple Payment API for many years now. This is a simple activity-based API that allows a VAA to initiate payments against a single payment app installed on the device.

AppFlow is not a direct replacement for this API as it is much broader than that, we strongly recommend using the new APIs for any new development.

We will be referring to any applications using this old API as legacy apps and these should be migrated to the new AppFlow APIs as soon as possible.

Existing legacy Payment Applications

AppFlow provides compatibility support for existing payment applications that are integrated with the legacy APIs for development and testing purposes. This means that legacy Payment Apps can still accept request and process payments as per before, whilst you are migrating to the AppFlow APIs.

Migration

Payment apps

For payment apps that do not wish to support the separate card reading step, the migration process is fairly straightforward.

  1. Replace the activity that implements the intent action com.aevi.payment.CONFIGURATION with a PaymentFlowServiceInfoProvider, return the relevant model and update the manifest.
  2. Replace the activity that implements the intent action com.aevi.payment.REQUEST with an implementation of BasePaymentFlowService and update manifest according to docs. This service handles what corresponds to the following in the legacy API: PAYMENT, MOTO_PAYMENT, REFUND, MOTO_REFUND, PRE_AUTHORISATION, COMPLETION, DEPOSIT
  3. Ensure that required legacy API parameters are added as references to the AppFlow TransactionResponse. These ensure backwards compatibility with any legacy VAAs. The keys that should be added as references to all TransactionResponses are merchantId, merchantName, terminalId and transactionDateTime.
  4. Add an implementation of BaseGenericService to handle what in legacy APIs is known as REVERSAL, TOKEN and RECEIPT requests
  5. You can now wire up these services to the same activity/fragment UI that were used previously for legacy
VAAs / POS apps

The complexity of migration for VAAs / POS apps depend on whether they want to support split scenarios via AppFlow. For non-split scenarios, the migration to AppFlow is fairly straightforward. To add support for split as well does however make the process fairly complex.

For non-split scenarios, the main differences from a POS app point of view are, * Unbound request/transaction types - AppFlow supports custom request and transaction types * Monetary representation is in subunit form via a long instead of BigDecimal * Native basket support * Native customer details support * Support for arbitrary data models being passed * A request (of type Payment) can result in multiple transaction responses * A request (of type Payment) can be partially fulfilled, leaving it to the POS app to decide what to do

The last point has the biggest impact, as there can be scenarios where a portion of the request is paid via loyalty points in a flow application, followed by the payment service declining the remainder. This must be handled by the POS application in a sensible way.

For split scenarios, please contact AEVI for assistance.

Model conversions

See Legacy Conversion.


Legacy conversion

This page details conversions of data to and from Legacy APIs and AppFlow.

Conversion of references from Legacy API to AppFlow

public class Converter {

    public static AdditionalData getAdditionalDataFromResultReferences(TransactionReferences transactionReferences) {
        AdditionalData additionalData = new AdditionalData();
        for (TransactionReferenceCode code : transactionReferences.getTransactionCodes()) {
            additionalData.addData(code.getName(), code.getValue());
        }
        return additionalData;
    }
}

For completion requests or other two stage financial transactions the transaction references of the previous request (pre-authorisation) that initiated the payment must be returned along with the request. For conversion between a previous v2 TransactionResult references and the new API you can make use of the following example.

static Amounts getCompletionAmounts(TransactionResult result) throws LegacyConversionException {
    TransactionReferences references = result.getTransactionReferences();
    String currency = null;
    if (references.getTransactionReferenceCode(TransactionReferences.CURRENCY) != null) {
        currency = references.getTransactionReferenceCode(TransactionReferences.CURRENCY).getValue();
    }
    if (currency == null) {
        Log.e(TAG, "No currency reference available for completion request - erroring out");
        throw new LegacyConversionException(CURRENCY_NOT_SUPPORTED);
    }
    BigDecimal amount = null;
    if (references.getTransactionReferenceCode(TransactionReferences.AMOUNT) != null) {
        String amountStr = references.getTransactionReferenceCode(TransactionReferences.AMOUNT).getValue();
        if (amountStr.matches("[0-9]+")) {
            amount = longToBigDecimal(Long.parseLong(amountStr));
        } else if (amountStr.contains(".")) {
            amount = new BigDecimal(amountStr);
        }
    }

    if (amount == null) {
        Log.e(TAG, "No amount available for completion request - erroring out");
        throw new LegacyConversionException(MALFORMED_REQUEST);
    }

    return new Amounts(convertBigDecimalToLong(amount), currency);
}

private static BigDecimal longToBigDecimal(long l) {
    return BigDecimal.valueOf(l).movePointLeft(2);
}

private static long convertBigDecimalToLong(BigDecimal amount) {
    return Math.round(amount.doubleValue() * 100);
}

POS Flow preAuthCompletions also require the amount to be specifically set. If you would like to obtain the amount directly from a previousTransactionResult the following code can be used to obtain it.

Conversion of references from AppFlow requests

Conversely for example to VOID/REVERSE a completion transaction performed via the POS Flow API you must include the references into the v2 ReversalRequest.

public static TransactionReferences getReferencesFromAdditionalData(AdditionalData additionalData) {
    LegacyTransactionResult legacyTransactionResult = new LegacyTransactionResult();
    for (String key : additionalData.getKeys()) {
        legacyTransactionResult.addTransactionReference(key, additionalData.getValue(key, String.class));
    }

    return legacyTransactionResult.getTransactionReferences();
}

static class LegacyTransactionResult extends TransactionResult {

    private final Bundle transactionReferences = new Bundle();

    LegacyTransactionResult() {
        super(new Bundle());
    }

    void addTransactionReference(String name, String value) {
        transactionReferences.putString(name, value);
        getBundle().putBundle(ResultOption.TRANSACTION_REFERENCES.toString(), transactionReferences);
    }
}

A TransactionReferences object can be obtained as below.

Additional data
TransactionResponse transactionResponse = response.getTransactions().get(0).getTransactionResponses().get(0);
AdditionalData previousTransactionRefs = transactionResponse.getReferences();

The AdditionalData parameter passed to the method above should be obtained from a valid TransactionResponse object returned via this API. The API is designed to support multiple transactions and multiple responses per transaction. In most instances however there will only ever be one transaction and one response. Therefore, the previous transaction references can be obtained using.

Where response above is the PaymentResponse object obtained in the initiatePayment example above. The above is for example only and obviously production code should ideally check the Transaction list and TransactionResponse list sizes before attempting to extract the data.

Error messages
TransactionResponse transactionResponse = response.getTransactions().get(0).getTransactionResponses().get(0);
String failureMessage = transactionResponse.getOutcomeMessage();

Error messages are associated with each TransactionResponse that they were generated for. Therefore, in order to identify an error message the list of TransactionResponse objects should be iterated. Again in most situations there will be only a single TransactionResponse, therefore, as above you can obtain this simply by using the following.


Miscellaneous

Example flows

Full sale flow

{
  "name": "coffeeSale",
  "type": "sale",
  "description": "Sale flow for coffee shop",
  "restrictedToApp": "com.aevi.sdk.demo.pos",
  "apiMajorVersion": 2,
  "stages": [
    {
      "appExecutionType": "SINGLE",
      "name": "PRE_FLOW"
    },
    {
      "name": "SPLIT",
      "appExecutionType": "SINGLE",
      "flowApps": [
        {
          "id": "com.aevi.sdk.demo.partpay"
        }
      ],
      "innerFlow": {
        "stages": [
          {
            "appExecutionType": "MULTIPLE",
            "name": "PRE_TRANSACTION"
          },
          {
            "appExecutionType": "SINGLE_SELECT",
            "name": "PAYMENT_CARD_READING",
            "flowApps": [
              {
                "id": "com.aevi.sdk.demo.pa"
              }
            ]
          },
          {
            "appExecutionType": "MULTIPLE",
            "name": "POST_CARD_READING",
            "flowApps": [
              {
                "id": "com.aevi.sdk.demo.loyalty"
              }
            ]
          },
          {
            "appExecutionType": "SINGLE_SELECT",
            "name": "TRANSACTION_PROCESSING",
            "flowApps": [
              {
                "id": "com.aevi.sdk.demo.pa"
              }
            ]
          },
          {
            "appExecutionType": "MULTIPLE",
            "name": "POST_TRANSACTION",
            "flowApps": [
              {
                "id": "com.aevi.sdk.demo.loyalty"
              },
              {
                "id": "com.aevi.sdk.demo.receipt"
              }
            ]
          }
        ],
        "type": "transaction"
      }
    },
    {
      "appExecutionType": "SINGLE",
      "name": "POST_FLOW"
    }
  ]
}

Generic tokenisation flow

{
  "name": "sampleTokenisation",
  "type": "tokenisation",
  "description": "sample tokenisation flow",
  "apiMajorVersion": 2,
  "stages": [
    {
      "appExecutionType": "SINGLE",
      "name": "GENERIC",
      "flowApps": [
        {
          "id": "com.aevi.sdk.pos.flow.paymentservicesample"
        }
      ]
    },
    {
      "appExecutionType": "SINGLE",
      "name": "POST_GENERIC",
      "flowApps": [
        {
          "id": "com.aevi.sdk.pos.flow.flowservicesample"
        }
      ]
    }
  ]
}

Developer Bundle

How to use the developer bundle

The developer bundle has four purposes;

  1. To provide sample applications that can be used to explore how AppFlow works
  2. To provide reference applications to test integration of your own application with in a flow
  3. To provide code examples of how to interact with the APIs
  4. To provide an AppFlow Configuration Provider (named AppFlow Configuration) tailored for the above use cases

AppFlow Configuration App

The AppFlow Configuration application (blue icon on launcher) provides a way to manage the flow configurations on the device, as well as the general AppFlow and FPS settings.

This application comes with a set of pre-defined flows for use with the samples, but in addition, it will scan for AppFlow compliant flow services and automatically add them to the relevant flows and stages.

This means that as a developer, you don't necessarily have to interact with this application, nor understand the details of flow configurations, in order to test your own application in a flow.

Flow configurations

When you start the application the first time, you will be presented with a landing page that explains the distinction between automatic and manual management of the flows. At this stage you can either choose to enable manual configuration, which will stop all automatic management, or you can choose to view the flow configurations in read-only mode.

Manual management

The defined flows can be selected via the drop down at the top of the screen. From that, the list of flow stages will be presented below the drop down. By default, flows and stages which no application installed on the device supports are hidden. This can be changed via the preferences (top right).

A stage can be expanded by clicking on it, which will present the applications supporting that stage. The toggle will indicate whether or not the application is enabled in that stage or not. If it not enabled, it will never be called at runtime for that stage.

When you enable or disable an application for a stage, you may get a popup asking whether you want to enable/disable for all stages. This will happen for applications that supports more than one stage for the current flow. You can choose to enable/disable individual stages, or via the popup apply for all stages.

Applications within a stage can be re-ordered via drag and dropping. You can initiate drag mode via long pressing the application and then dragging it to the desired position.

In addition, any metadata associated with the application can be viewed and modified via clicking the info icon on the right hand side.

Define new flows

If you require additional flows, you can either contact AEVI for assistance, or clone the developer-config-provider repo and add these yourself.

The flows are defined as JSON files bundled with the apk.

Flow Service Sample

The flow service sample supports all the stages for the sale flow, but it only enabled for the POST_CARD_READING and POST_TRANSACTION stages by default. In AppFlow v2.0.X, enabling and disabling stages could be done via the Flow Service Sample Settings application, but as of v2.1.X, this can now be achieved via this application instead which allows you to toggle individual stages.


Bespoke data

Acquirers and flow service developers may define their own project or application specific data.

At this time of writing, there is not yet any acquirer or application specific data.