1. Documentation
  2. Payment Applications
  3. API

Expose app info

In order for your application to be AppFlow compliant, it must expose certain information. It does this via implementing a content provider and exposing it via the application manifest.

Adding the service info provider

FPS and other applications require certain information about every value added service.

public class PaymentFlowServiceInfoProvider extends BasePaymentFlowServiceInfoProvider {
  @Override
  protected PaymentFlowServiceInfo getPaymentFlowServiceInfo() {
    return new PaymentFlowServiceInfoBuilder()
      .withVendor("AEVI")
      .withDisplayName("Flow Service Sample")
      .withCanAdjustAmounts(true)
      .withCanPayAmounts(true, PaymentMethods.LOYALTY_POINTS, PaymentMethods.GIFT_CARD, PaymentMethods.CASH)
      .withSupportedFlowTypes(FlowTypes.SALE)
      .withCustomRequestTypes(ShowLoyaltyPointsBalanceService.SHOW_LOYALTY_POINTS_REQUEST)
      .build(getContext());
  }

  @Override
  protected boolean onServiceInfoError(@NonNull String errorType, @NonNull String errorMessage) {
    switch (errorType) {
      case RETRIEVAL_TIME_OUT:
        Log.d(TAG, "Retrieval of service info timed out");
        break;
      case INVALID_STAGE_DEFINITIONS:
        Log.d(TAG, "Problems with stage definitions: " + errorMessage);
        break;
      case INVALID_SERVICE_INFO:
        Log.d(TAG, "Invalid service info: " + errorMessage);
        break;
    }
    return true;
  }
}

This is done by extending BasePaymentFlowServiceInfoProvider, as the sample code from the Flow Service Sample below shows

Returning PaymentFlowServiceInfo data

See PaymentFlowServiceInfoBuilder for complete details of what can be set.

You can also explore the Flow Service Sample provider to see the above as used in code.

The following data must be set for value added applications;

  • withVendor() - The company providing the application
  • withDisplayName() - The name of the application to show to merchants/customers
  • withCanPayAmounts() - If you provide any form of payment method or apply discounts to baskets
  • withCanAdjustAmounts() - If you add amounts, like tip, fees, charity, tax or add baskets

The following data is optional, but we recommend you understand and consider each item as they impact how your service is called;

  • withSupportedFlowTypes() - Define what flow types your app supports (such as sale or refund). (details further down)
  • withCustomRequestTypes() - Define custom/bespoke types that your application can handle (details further down)
  • withSupportedCurrencies() - Restrict what currencies your application can handle (default is all currencies)
  • withSupportedDataKeys() - Define what data keys (for additional data) your application can read/write/update

See Flow Types for a list of all defined flow types.

See Bespoke Data for details.

Errors

Via the optional, but highly recommended, overriden method onServiceInfoError , your flow service can receive details of when your flow service is rejected for some reason. See ServiceInfoErrors for a list of possible error types.

Expose via manifest

<provider
  android:name=".PaymentFlowServiceInfoProvider"
  android:authorities="<your authority>"
  android:exported="true">
  <intent-filter>
      <action android:name="com.aevi.sdk.flow.action.PROVIDE_SERVICE_INFO"/>
  </intent-filter>
</provider>

The provider needs to be exposed in the application manifest as follows

The authority should be unique. We recommend you use your package name as a prefix.

Notifying data changes

If the data reported via PaymentFlowServiceInfo has changed, you need notify FPS of this.

Call BasePaymentFlowServiceInfoProvider.notifyServiceInfoChange() statically and FPS will invalidate any cached configuration data for your application and call your provider again to get the new information.

Implementing services

Now that you have decided what stages are relevant for your application and set up the content provider, it is time to implement the actual service. The service (which is an Android Service) is what FPS will be calling into for passing flow data and how your application can send back a response.

Determining when to get called

Depending on the type of payment application, different flow stages are relevant. All payment applications must support the TRANSACTION_PROCESSING stage, which is the point at which money is transferred during a payment flow. As an example. for a sale flow, it is when the customer pays the merchant via some payment method such as card or cash.

AppFlow does not dictate what payment methods a payment application uses, as long as it accurately reports the correct payment method(s) in the app info and transaction responses.

Payment card reading

For traditional payment applications using payment cards, there is an optional (but recommended) stage called PAYMENT_CARD_READING. Read more about this here.

Non-payment functions

For other functions, such as reversal (void), tokenisation, etc, the GENERIC stage is used. See Flow Types for the different generic types and information on what data is passed in the request and what is required in the response. The generic scenarios are explained in more detail at the end of this page.

The service

public class PaymentService extends BasePaymentFlowService {

  @Override
  protected void onPaymentCardReading(CardReadingModel model) {
      // TODO handle
  }

  @Override
  protected void onTransactionProcessing(TransactionProcessingModel model) {
      // TODO handle
  }

  @Override
  protected void onGeneric(GenericStageModel model) {
      // TODO handle
  }
}

Below is an example from the Payment Service Sample of how to implement a service that exposes the three stages.

Expose in manifest

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

Transaction processing

The request for a payment flow comes in the form of a TransactionRequest object and can be fetched via TransactionProcessingModel.getTransactionRequest(). There are two different ids defined in this class - please see Transactions for more information about what the ids are how they can be used.

You can determine what type of transaction it is from TransactionRequest.getFlowType(). This will map to one of the types defined in Flow Types for where the request class if of type Payment.

The amounts relevant for the transaction are available via the TransactionRequest.getAmounts() method. Note that it is very important to understand the way AppFlow represents amounts and what the structure of it means for a payment application. See parsing amounts for details.

It is possible that TransactionRequest.getCard() contains some form of card data either if a card was read in the payment card reading stage or the POS app provided a card token to use for payment. You can check if the Card object contains any data via the Card.isEmpty() method, or check if a token is set via the Card.getCardToken() method.

In addition, depending on the use cases for your payment application, the following information may also be available; - Basket data (via getBasket()) - Customer data (via getCustomer()) - Custom/bespoke data (via getAdditionalData())

Building the transaction processing response

When your payment app has finished processing the payment and determined the outcome, you should construct an appropriate TransactionResponse object using the TransactionResponseBuilder retrieved via TransactionProcessingModel.getTransactionResponseBuilder().

Transaction outcome

At a minimum, the outcome must be set via calling either one of the approve methods or the decline method on the builder. If a specific response code is supported via a defined protocol, such as ISO-8583, this should be set via the withResponseCode() method. In addition, free text information about the outcome can be provided via the withOutcomeMessage() method.

Processed amounts

If your payment application (host/gateway) supports partial auth and such a scenario occurs, then you can of course not use the request amounts as they were. In this case, it is recommended that you first "fill up" the base amount with the amounts authorised, and then split any remaining amounts uniformly (as a fraction) of the requested additional amounts.

Card details

If the payment method used for the transaction was a payment card (and it was read in this stage and not in payment card reading stage), it is strongly recommended that the following fields at a minimum are populated in the Card model and then set via withCard() method;

  • Card network / scheme
  • Card entry method
  • Card token

Transaction references

Your application can provide any arbitrary references related to the transaction in the response. This is particularily important for functions that are based on a previously completed payment, such as reversals/voids and pre-auth completions.

See Reversal and Pre-authorisation / Completion for details on how to manage these types.

This, and any other relevant information the payment application may have about a transaction can be added to the transaction references via the withReference() method. See TransactionResponse References for details on defined additional values. You may also set any custom and bespoke references your payment application and/or host/gateway may have defined.

Send the response

The response is then sent back via calling TransactionProcessingModel.sendResponse(). This method will generate the response from the builder and send it back to FPS.

Payment card reading

As for transaction processing, the request type is TransactionRequest, which can be fetched via CardReadingModel.getTransactionRequest().

Setting the response

If this stage is supported, the expectation is that a Card object is created and passed back on successful card reading. The card reading can have three outcomes, reflected by these three methods in CardReadingModel

  • approveWithCard(Card) - If a card was read successfully
  • skipCardReading() - If card reading should be bypassed in this stage (for whatever reason)
  • declineTransaction(String) - If card reading failed and transaction should be declined

All these methods above will also send the response straight back to FPS, so there is no need to call any further methods in the model to finalise.

All the various fields in Card are optional, but for this to be useful for any purpose, we recommend the following fields at a minimum are populated; - Card network / scheme - Card entry method - Card token

It should also be noted that it is expected that the payment application keeps track of what happened in this stage and acts accordingly in the processing stage later.

Generic scenarios

The GenericStageModel provides the input request and a mechanism to send back a response. In terms of how to parse the request and create a response for any given type, please see the Flow Types page which details all the supported types.

A generic flow can be set to be processed in the background either via the flow configuration or via the request data. These flows are executed separately to (and possibly in parallel to) the normal foreground flows and may not require any user interaction. Your application may not launch any UI in a background flow.

Flow types such as reversals are common candidates for background processing.

Handling events

As of v2.1.0, the stage models allow flow services to subscribe to events.

FPS will send events to your flow service under various circumstances, such as - Acceptance or rejection of a response your flow service sent - To indicate that your flow service must finish processing - To indicate that you should resume/restart your user interface

All defined events and associated documentation can be found here. In addition, any data keys to read out data from the FlowEvent can be found here.

Subscribe to events

model.getEvents().subscribe(event -> {
            switch (event.getType()) {
                case FINISH_IMMEDIATELY:
                    // Stop all processing - FPS is no longer accepting a response
                    break;
                case RESUME_USER_INTERFACE:
                    // Resume or restart any UI you may have launched previously
                    break;
                case RESPONSE_ACCEPTED:
                    // Response was accepted, hooray!
                    break;
                case RESPONSE_REJECTED:
                    String rejectReason = event.getData().getStringValue(REJECTED_REASON);
                    // Response was rejected with reason above, :(
                    break;
                default:
                    // Unknown event
                    break;
            }
        });

The base class for all stage models provides a getEvents() method that clients can use to subscribe to events as per below.

Handling FINISH_IMMEDIATELY

Under certain scenarios, FPS will ask your flow service to finish up immediately. At this stage, it is too late to send a response.

This would happen if your flow service times out, or if the merchant chooses to skip your application or cancels the whole flow.

As this is driven by the merchant either being inactive for a long period or explicitly choosing to progress the flow, it is assumed that any existing interactions that has been done up to this point can be discarded.

Handling RESUME_USER_INTERFACE

If you have chosen to not send back a response and finish up if your activity is stopped/destroyed, it is possible you will receive this event if the merchant is trying to resume the flow.

How this event is handled is very much up to the implementation of your flow service. For simple cases (like the samples) where no state is kept in the activities themselves, the activity can simply be restarted.

For more complex scenarios where there are multiple views/pages/steps in the activity, we would recommend that you instead send back a response when your activity is stopped/destroyed. If that is not feasible, then you must ensure that you store state outside of the activity context so that the activity can resume from where it left off when restarted. This can add a fair amount of complexity, which is why we are generally advising against it.

Handling response outcome

Your response will either be accepted or rejected by FPS. If you have augmented the flow data in some way and the response is rejected, it indicates that your application have violated some of the augmentation rules. Note that you are not able to perform any further actions with AppFlow at this stage and should as such not launch any user interface for the outcome.

As per the code samples above, you can retrieve the reason for the rejection from the event data. This will be a human readable message outlining the reason for the rejection. It is recommended you log/capture/upload this information for troubleshooting and analysis purposes.

Some examples of why a response may be rejected are; - Action performed has not been marked in PaymentFlowServiceInfo - such as updating amounts without calling withCanUpdateAmounts(true) - Amounts are invalid - such as paid amounts being zero, or exceeding the requested amounts - Trying to apply discounts to a non-existing basket

Receipts

Receipts can be printed directly from within your payment application if desired. However, it is recommended instead that the printing of the receipt is handled by a separate and specific receipt printing application. This means that the print handling code is not mixed up with your payment application and is instead separated out. This has the benefit of allowing the flow to handle the printing which, in turn provides the flexibility to use any printer on any device.

The payment application should ensure that all the required information is added to the TransactionResponse references so that a receipt printing flow app can use the data later to print to a receipt. The addReference(String key, T... values) or addReferences(AdditionalData fromReferences) methods can be used to add any type of reference data your payment application produces. In particular the payment application should ensure that it adds references indicating any acquirer/bank specific details that may be a requirement to show on the receipt. For example it is usually required that the receipt show an acquirer specific merchant and terminal id.

It is also usually required that a payment application adds card specific details such as EMV parameters etc. These should be added by the payment application into the additional data object of the Card object associated with the TransactionResponse.

Application guidelines

The following guidelines should be followed when you implement your application to work well with AppFlow and to provide a seamless and consistent experience for the merchant.

ProGuard

If you are using proguard to obfuscate your application you will need to ensure that the AEVI classes are not processed as AppFlow makes use of JSON serialisation and deserialisation for API models.

-dontwarn com.aevi**
-keep class com.aevi** { *; }

This can be done simply by including the following in your ProGuard file

Permissions

All communication between applications in a flow is sent as JSON which means the data between applications can be sent over any channel. By default the Android implementation of AppFlow will use the Android Messenger to send and receive messages. This means message sizes are limited to the size set by the Android Binder limit (usually max 1 MB). To avoid this issue AppFlow can be configured (via a configuration provider) to send messages over a Websockets instead.

Whether AppFlow uses Android Messenger or Websockets is down to the runtime configuration of FPS.

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>

As Websockets are network based, all applications developed for AppFlow must request these permissions in the manifest.

Application lifecycle

Due to the service-based architecture, any activities called throughout the flow are all created in separate tasks. See the Android docs for more info on tasks. All your activities will be started with the Intent.FLAG_ACTIVITY_NEW_TASK flag set.

This means there is no way of leaving a flow "stack" and coming back to it later in a traditional Android sense.

FPS does provide certain controls to the merchant to resume/skip/cancel applications and it is important that you implement your applications in a way that plays well with these controls.

It is up to you how to manage user interactions (back / home) and lifecycle events like onStop() and onDestroy() based on what suits your application use case best. There are however a few things to consider.

As mentioned above, FPS does give merchants the option to resume or skip an application via notification controls. This means that if the user presses back or home from your application, and you don't send back an API response due to this, your app may be receive an event asking it to resume the UI.

If your application is performing sensitive and crucial processing of some form, it is recommended that you take the following approach; - Override onBackPressed() in the activity and show a message if merchant tries to press it during this processing - Ensure the processing itself is carried out independent of the activity - i.e in a service separate from the API service (which may be created for each request), or in a separate thread in the API service. Use the activity only as a way to interact with the merchant, not to process anything important or keep important state. - Send back a response and wrap up all processing upon an onStopped() callback

If your application does not perform any form of processing and doesn't keep state, the simplest approach is to not do anything as part of being stopped or destroyed, and simply restart the activity upon resume events.

Manifest flags

<activity
  ...
  android:excludeFromRecents="true"
  android:resizeableActivity="false"/>

All activities that are launched inside a flow must define these flags inside the activity tag.

This will ensure the activities leave no trace once they are finished, which is important for the flow. In addition, it stops merchants from accidentally splitting the screen into multiple activities when using the application.

Animations / Transitions

In order to make AppFlow as smooth as possible, it is recommended that any flow service activities disable enter/exit animations/transitions. Any activity started via the stage models will already have the Intent.FLAG_ACTIVITY_NO_ANIMATION flag set, but for exit animations the activities need to call overridePendingTransaction(0, 0) after calling finish() (or ideally, overriding finish() and doing it there).

Next steps

At this point you we hope you have a good understanding of how AppFlow works and have a basic integration completed.

Problems? Questions?

If you are having any problems or questions, please check out our support pages.

Learn more about AppFlow

You can explore the full AppFlow documentation here.