1. Documentation
  2. AppFlow - Value Added Applications
  3. API

API Overview

The Payment Flow Service API enables applications to get called at various stages in a flow and read and/or augment flow data. We refer to these applications as flow services, of which there are two types - value added applications and payment applications.

There are a few basic requirements a flow service must fulfill in order to be accepted by FPS.

  • Implement a content provider and expose a PaymentFlowServiceInfo model with relevant app information
  • Implement one or multiple services which will be called in a flow
  • Interact with one or multiple stage models to read and augment data
  • Expose relevant components in the application manifest

Determine stages

At the heart of AppFlow are flows, which consist of one to many stages. An understanding of this is required in order to identify how to set up your application for AppFlow.

First of, make sure you have read the Introduction and associated pages to gain an understanding of how AppFlow operates.

Now it is hopefully clear what a flow is, what stages there are and what these stages provides. Before we move onto implementation, you must decide when it is relevant for your application to get called.

Determine relevant flows

The Flow Types page outlines the various functions AEVI has defined for AppFlow.

Your application will need to define what flow types are supported and from that, what flow stages to be called for.

Is is important to understand that there are two factors that will impact for what flows your application gets called:

  • The flows defined via the AppFlow Configuration Provider (in the case of the bundle, this is the AppFlow Settings application)
  • What flow types your application reports as supported

Some types of applications will only ever be relevant for a particular type of flows. An example is loyalty, which is applicable when a customer is purchasing goods or services. These applications should define the exact set of supported flow types to ensure they are only called for the relevant functions (like sale).

Other types of applications, such as for receipt handling or feedback/ratings, are not generally restricted to a particular set of functions or flows. For these applications, it is better to not define supported flow types, which means they can be called for any flow. Instead, what flows they are called for is entirely down to the flow configurations for the particular environment/device.

Determine relevant stages by function

Once you have decided what flow types are relevant, you need to select for what stages in those flows your application should be called.

Most value added applications will fit into one or several of the payment stages for standard financial transactions.

Below is a quick outline of what stages are relevant for particular functions that AppFlow supports.

Function Stages
Reacting to outcomes POST_TRANSACTION (per customer), POST_FLOW (per flow), POST_GENERIC
Adding additional amounts PRE_TRANSACTION, POST_CARD_READING, PRE_FLOW (rarely)
Adding basket items PRE_TRANSACTION, POST_CARD_READING, PRE_FLOW (rarely)
Paying amounts PRE_TRANSACTION, POST_CARD_READING
Applying discounts PRE_TRANSACTION, POST_CARD_READING
Add/update customer details PRE_TRANSACTION, POST_CARD_READING, PRE_FLOW (rarely)
Add custom/bespoke data PRE_TRANSACTION, POST_CARD_READING, PRE_FLOW (rarely)
Custom request GENERIC
POS status updates STATUS_UPDATE

Determine relevant stages by use case

This section outlines some common use cases for value added applications and offer advice on how to approach this in AppFlow.

Loyalty

A loyalty application typically provides three main functions:

  • Applying discounts from customer earned loyalty points or rewards
  • Assigning newly earned points/rewards to a customer
  • Allowing new customers to sign up to the loyalty scheme

Loyalty is typically only relevant for the sale flow, but depending on industry/context, possibly also;

  • motoSale
  • preAuthorisation
  • preAuthCompletion

Applying discounts, has to be done prior to the payment app performing its processing of the payment. Therefore, your application should support the POST_CARD_READING stage, as at that time all the data to help identify the customer will be available. This can be customer data from the POS app, card token from the card reading stage, or the loyalty app may provide its own mechanism to identify a customer. If card tokens are not relevant, PRE_TRANSACTION is an equally good candidate.

To apply discounts, you should make use of the PreTransactionModel where you have two options depending on how your loyalty scheme works.

  • Pay off all or a portion of the requested amounts
  • Apply discount to basket items

In the first case, you should use the setAmountsPaid() method, and in the latter case make use of the addItemsToExistingBasket() where you would add a discount line item with a negative amount to the main basket.

For assigning points and/or signing up new customers, POST_TRANSACTION should be used. At this stage, the outcome of the transaction is known and the payment app may have provided a card token as well as part of the TRANSACTION_PROCESSING.

Tipping

If a POS application does not provide tipping as a function, an application may choose to offer this separately.

Tipping is mainly relevant for the sale and preAuthCompletion flows.

The PRE_TRANSACTION stage is the most appropriate choice for general flows, but where it is not available, PRE_FLOW is also an option.

To apply a tip, see the setAdditionalAmount() or setAdditionalAmountAsBaseFraction() methods in the stage models.

Receipts

AppFlow enables handling of receipts to be managed by a value added application rather than the payment application or POS application. This is useful for receipt services that allow customers to gather all their receipts in a single location. It is also encouraged for more traditional receipt handling (like printing), as it means integration with printers / printing APIs can be done in a single application as opposed to embedding this functionality in the POS/payment application.

Receipt handling is typically not restricted to a specific set of flows, which is why we would recommend that your application do not report any flow types so that it can be called for any flow.

Receipt handling applications should be called in the POST_TRANSACTION stage, when the transaction outcome is known.

Feedback / Ratings

Applications can be called at the end of a transaction, allowing acquirers/merchants to ask for customer feedback, typically via some rating system.

What type of flows this should be called for will be down to the acquirers, meaning it is best if your application does not report any supported flow types.

For payment flows, if feedback is desired per customer, then POST_TRANSACTION is the relevant stage, or if feedback is desired for the overall payment, then POST_FLOW is more appropriate.

For generic flows, POST_GENERIC is the stage to look for.

Ads / Promotions

A value added application can provide ads and promotions that can be read and shown by other applications in the flow. As an example, a receipt application might add it to the receipt.

Analytics / Reporting

It is possible for a value added application to gather and report information and statistics around transactions. As this would deal with potentially sensitive customer data, this would for the most case require a special acquirer application(s).

As these applications are typically run for all flows, flow types should be left empty.

These applications can run asynchronously in the POST_FLOW stage, meaning that they can accept the PaymentResponse data, and let the flow finish whilst the service processes the data in the background. This is to ensure that such activities do not delay the flow, which would have a negative impact on the sale experience.

In addition, these types of applications would also be suitable for the POST_GENERIC stage for the generic flows.

Splitting Bills

A split bill application allows customers, typically in a restaurant/cafe setting, to split a table bill amongst them. A Payment will be broken down into a Transaction per customer, where the split application dictates the amounts / basket items for each transaction. The Payment will be completed once the total requested amounts have been met, or if it is cancelled.

There is a specific designed stage for split applications - SPLIT, which is called repeatedly until the Payment is completed.

Exposing custom data or function

A value added service can expose custom data or a function (with associated user interface) via a generic flow. This would allow POS apps and other initiating apps to seamlessly interact with your application without a need for bespoke integration.

Some examples for this may be:

  • Allowing an app to query for data, like customer loyalty point balance or inventory details
  • Allowing merchants/customers to interact with your application for specific functions like updating customer details or updating inventory

In both these cases, a generic flow would be used and the application would be called in the GENERIC stage. It is then up to the application whether it simply returns back requested data directly, or if it launches a user interface for direct interaction.

To facilitate this, your flow service must also define and report a custom request type via the PaymentFlowServiceInfo, for which FPS will generate an ad-hoc flow.

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 flow 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.

Defining the service entry point

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

There are two main options;

  • Extend a base service class and have full control over if/when UI is launched and how to handle events, etc
  • Use a pre-defined service that simply proxies any request straight to an activity of your choice

As as simple rule - if all your AppFlow implementation will be UI-based, then ActivityProxyService is the best place to start. If on the other hand you have scenarios where you don't require user interface, or need to execute asynchronously, extending BasePaymentFlowService gives you more flexibility.

You will see that each method has a model passed to it as a parameter. This is covered in detail on the next page.

Extending BasePaymentFlowService

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

protected void onPreFlow(PreFlowModel model) {}

An example of such a method is,

public class ValueAddedService extends BasePaymentFlowService {

  @Override
  protected void onPreTransaction(PreTransactionModel model) {
      model.processInActivity(this, PreTransactionActivity.class);
  }

  @Override
  protected void onGeneric(GenericStageModel model) {
       Response response;
       switch (model.getRequest().getRequestType()) {
            case "...":
                break;
            default:
                response = new Response(model.getRequest(), false, "Unsupported flow type");
                break;
        }
        model.sendResponse(response);
  }
}

Here is an example of a basic implementation that handles pre-transaction and generic. It illustrates how some stages can be delegated to activities, and others to be handled from the service context itself.

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

This is defined in the manifest as follow,

In order for your application to be called at a specific stage it must expose the appropriate action intent-filter as shown above. A full list of these can be found here

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.

<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>

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.

<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>

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.

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".

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

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;

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.

Background flows

A flow can be set to be processed in the background. These flows are executed separately to (and possibly in parallel to) the normal foreground flows. Applications may not launch any UI in a background flow and the requests are submitted to a request queue which are processed one at a time. Status update flows are always processed in the background, and the generic flows can be set to background either via the flow configuration or in the request created by the initiating application.

Status updates

To handle these updates, either extend the BaseStatusUpdateService or override onStatusUpdate() method in a BasePaymentFlowService implementation. 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.

Asynchronous request handling

An application 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 or delay the processing completion.

Any application 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 application if and when the service is stopped as described in a section below.

Note that this is only allowed from a service, you may not keep any activities running after sending response or skip.

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.

Stages summary

Base intent action: com.aevi.sdk.flow.action.

Stage Intent Action Postfix Stage model
PRE_FLOW PROCESS_PRE_FLOW PreFlowModel
SPLIT PROCESS_SPLIT SplitModel
PRE_TRANSACTION PROCESS_PRE_TRANSACTION PreTransactionModel
POST_CARD_READING PROCESS_POST_CARD_READING PreTransactionModel
POST_TRANSACTION PROCESS_POST_TRANSACTION PostTransactionModel
POST_FLOW PROCESS_POST_FLOW PostFlowModel
GENERIC PROCESS_GENERIC GenericModel
POST_GENERIC PROCESS_POST_GENERIC PostGenericModel
STATUS_UPDATE PROCESS_STATUS_UPDATE StatusUpdateModel

Interacting stage models

A stage model is the main point of interaction for your application when it comes to reading data, augmenting data, sending response, etc. Each stage has its own tailored model that provides input and output functions relevant for that stage. The one exception to the rule is POST_CARD_READING, which uses the same model as PRE_TRANSACTION.

The stage model is either passed to you via a callback method, or you have to construct it from your service/activity, depending on what approach you use for defining your entry points.

All the stage models provide a similar set of functions,

  • Initialise from a service context via fromService()
  • Initialise from an activity context via fromActivity()
  • Get the request (input data) for this stage
  • A number of augmentation functions
  • Add entries to audit log via addAuditEntry()
  • Subscribe to flow service events via getEvents()
  • A method to send the response
  • Start an activity to handle the request via processInActivity() (from a service context)
  • Send a message an activity started via processInActivity from a service context
  • Listen to activity lifecycle events from an activity started via processInActivity()

In addition, the stage models set 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.

You can also review the different activities in the Flow Service Sample which all interact with the stage models for the respective stages.

Retrieving input data

All models have a getter for retrieving the input data relevant for that stage:

  • PreFlowModel - getPayment()
  • PreTransactionModel - getTransactionRequest()
  • PostTransactionModel - getTransactionSummary()
  • PostFlowModel - getPaymentResponse()
  • GenericStageModel - getRequest()
  • PostGenericStageModel - getResponse()

Sending response

Each model has at a minimum a sendResponse() method and likely a skip() or finish() method.

Augmenting data

Depending on what function your application is offering and for what stages it will be called, there are different augmentation options. This section will break down all the augmentation options (for payment flows specifically) and for what stages and functions they may be useful. The next section will look at specific use cases and advise on how to model your application for that use case.

Note that we do not cover PRE_FLOW here as it is a special case. Contact AEVI for details.

PreTransactionModel augmentations

The majority of the relevant augmentations happen before the TRANSACTION_PROCESSING stage (at what point a payment application determines the outcome of the transaction). After that point, all value added applications can do is add references. This section will cover the augmentation options for the PRE_TRANSACTION and POST_CARD_READING stages. For both these stages, any augmentations are done via the PreTransactionModel.

Adding additional amounts

There are various scenarios for which adding additional amounts is relevant, be it for tipping, fees, donations, etc. The PreTransactionModel contains two methods for doing this:

  • setAdditionalAmount(String identifier, long amount)
  • setAdditionalAmountAsBaseFraction(String identifier, float fraction)

The first method allows you to specify the amount value in its sub-unit form, whereas the second method allows you to add an amount as a fraction of the request base amount value. This is useful for a variety of cases where the amount is a percentage of the base amount, such as fees, tipping, etc.

In both cases, the amount is identified via a string identifier. See Additional Amounts for details.

Adding a basket

The flow initiation application may have provided a basket in the initial request. To provide sensible separation between what app/service added what items, value added applications can add new baskets, but not add more items to the existing baskets (discounts excepted). This can be done with the PreTransactionModel.addNewBasket(Basket basket) method.

This is the recommended approach to add any form of "upsells" or extras, as it adds visibility of what specifically has been added, allowing receipts apps to show it clearly on the receipts.

Adding/updating customer details

A value added application can either add or update customer details. If the initial Payment does not contain any customer data, the application can create a new Customer model and add to the transaction. If however a Customer model already exists, the application can add

  • Tokens
  • Customer details as additional data

Either case is done via PreTransactionModel.addOrUpdateCustomerDetails(Customer customer). If adding, the Customer parameter is created by your application. If updating, you use the Customer model from the request, update it and then pass it in here.

Paying amounts

A value added application can pay off a portion or all of the requested amounts. The most common scenario for this is via loyalty rewards or points. This can be done via PreTransactionModel.setAmountsPaid(Amounts amountsPaid, String paymentMethod). The amounts must not exceed requested amounts and the payment method must be set. See Payment Methods for defined options.

This method is recommended when the payment has no relation to any basket items. If however the payment is provided as discounts to certain items in the basket, like offering a free coffee, then the recommended approach is to apply discounts to baskets which is covered in the next section.

Applying discounts to baskets

If your application provides discounts or rewards based on basket items, then this function will allow your service to apply discounts as items in the same basket. As an example, if the basket contains an item "Latte" with a cost of $3.50, you can via this method add a "free coffee reward" that would look something like "Reward: Free Latte" with a negative amount of $-3.50 to negate the cost of the original item.

See PreTransactionModel.applyDiscountsToBasket(String basketId, List<BasketItem> basketItems, String paymentMethod) for more info.

Adding request data

Any arbitrary data that may be required or useful for other applications can be added via the PreTransactionModel.addRequestData(String key, T... values) method.

PostTransactionModel references

The PostTransactionModel offers a addReferences(String key, T... values) method to provide references back to the flow initiation app for the transaction.

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

Flow response listeners

Value added applications can optionally define response entry points that are called when the final flow responses for payment flows are available.

This is how initiation applications receive responses for the flows they initiate and it may also be useful for your application to review the outcome for some reason or have an opportunity to undo changes made in the flow upon errors, cancellations, etc.

As an example, if loyalty points were used and the transaction is later declined or cancelled, it would allow a loyalty app to refund those points back to the customer.

It is entirely at the discretion of each application if they can support this and if so, how it is performed.

Payment response service

public class PaymentResponseListenerService extends BasePaymentResponseListenerService {

  @Override
  protected void notifyResponse(PaymentResponse paymentResponse) {
     // Handle response
  }

  @Override
  protected void notifyError(String errorCode, String errorMessage) {
     // Map error code against one of `ErrorConstants`
  }
}

In order to receive payment flow outcomes, your application should implement the notifyResponse and notifyError methods as shown in the example for payment responses below.

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

The service should be registered in your manifest as normal and should include the correct intent filter as shown below.