1. Documentation
  2. AppFlow - Payment 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

Exposing application 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 payment application.

This is done by extending BasePaymentFlowServiceInfoProvider, as the sample code below shows

public class PaymentServiceInfoProvider extends BasePaymentFlowServiceInfoProvider {
  @Override
  protected PaymentFlowServiceInfo getPaymentFlowServiceInfo() {
    return new PaymentFlowServiceInfoBuilder()
      .withVendor("TheVendorName")
      .withDisplayName("Name of your application for UI presentation")
      .withCanPayAmounts(true, <supportedPaymentMethods>)
      .withSupportedFlowTypes(FlowTypes.SALE, FlowTypes.REFUND, ...)
      .withCustomRequestTypes(<Any custom types you may support>)
      .withSupportedCurrencies(<list of supported currencies>)
      .withDefaultCurrency(<optional default currency>)
      .withMerchants(<Optional list of Merchant objects>)
      .withManualEntrySupport(<whether or not MOTO is supported>)
      .withSupportsAccessibilityMode(<whether or not you support visually impaired>)
      .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;
  }
}

Returning PaymentFlowServiceInfo

See PaymentFlowServiceInfoBuilder for complete details of what can be set and the implementation in the Payment Service Sample.

The following data must be set for payment applications;

  • withVendor() - The company/organisation providing the service
  • withDisplayName() - The name of the application to show to merchants/customers
  • withCanPayAmounts() - true together with what payment methods are supported
  • withSupportedFlowTypes() - Define what flow types (payment functions) are supported (such as sale or refund). (details further down)

In addition, it is strongly recommended that the following data is set if possible;

  • withSupportedCurrencies() - Restrict what currencies are supported (default is all currencies)
  • withDefaultCurrency() - Set the default currency
  • withManualEntrySupport() - Whether or not manual entry / MOTO is supported
  • withSupportsAccessibilityMode() - Whether or not there is support for visually impaired users

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

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

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

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.