Implementing Payment Apps - AEVI-AppFlow/pos-android-sdk GitHub Wiki

This page provides details on how to write payment applications for AppFlow via integrating with the Payment Flow Service API.

In order to develop a payment app, it is important to understand the various steps a payment goes through from initiation to completion and what data is passed between them at the various stages.

This guide will, together with JavaDocs and sample apps, provide you with a complete overview of this.

References

For API docs, please see Javadocs.

Please see the Payment Service Sample as an example of how to use the API as a payment application.

Pre-requisites

It is assumed that the Overview has already been read.

Recommended reading, in order;

You may also find these useful, but not mandatory for payment app development.

Components

There are two relevant components for implementing a payment application of any kind.

  • A content provider informing FPS and clients app what your application supports
  • One (or more) service(s) processing financial requests and optionally supporting a separate payment card reading stage

Exposing service info

In order to let the system and other applications know what your payment application supports, you need to implement and register a subclass of BasePaymentFlowServiceInfoProvider. This is a base class that will require you to implement the getPaymentFlowServiceInfo() method. This method is called by the system upon app installation or when your app notifies FPS that the info has changed.

The PaymentFlowServiceInfo is constructed using a builder as outlined in the sample code.

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(<default currency>)
                .withMerchants(<List of Merchant objects>)
                .withManualEntrySupport(<whether or not MOTO is supported>)
                .withSupportsAccessibilityMode(<whether or not you support visually impaired>)
                .build(getContext());
    }
}

The following fields are mandatory for payment applications

  • Vendor
  • Display name
  • Supported currencies
  • Default currency
  • Can pay amounts with associated payment methods (If not set, attempts to pay will be rejected)

In addition, these are optional but recommended where possible

  • withSupportedFlowTypes() - this is optional, but strongly recommended for payment apps. If you do not set this, your service will be called for all possible types whether your app supports it or not. If you do specify supported types explicitly, FPS will make sure your service is only called for types you support.

You then need to expose this provider in the manifest. The main service further down will define how to find this provider.

Expose the provider in the manifest

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

In order to allow your service to invalidate outdated information, you can call BasePaymentFlowServiceInfoProvider.notifyServiceInfoChange() statically and FPS will invalidate any cached configuration data for your service and call your provider to get the new information.

Services to process requests

There are three entry points into a payment application

  • TransactionRequest for the PAYMENT_CARD_READING stage (optional)
  • TransactionRequest for the TRANSACTION_PROCESSING stage (mandatory)
  • Request for a GENERIC stage (optional)

TRANSACTION_PROCESSING is the only mandatory stage to implement as without it, a payment application would not be of any use. During this stage a payment application is typically interacting with card readers and a host or gateway, to determining the outcome of the transaction.

The PAYMENT_CARD_READING stage (described in detail here) is optional, but recommended wherever it is technically possible to implement.

If the card has been read in the payment card reading step, card reading should ideally not be performed again during transaction processing, but this is not always achievable for certain presentation methods such as contactless. It is up to the payment application to manage this internally.

Non-payment requests, like reversals and tokenisation, are processed in the GENERIC stage. Whereas it is optional for the payment application to support this stage, in practice it will be required in most cases to support the various common types of requests.

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

PaymentService

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
    }

}

And to expose this in the manifest

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

Implementation

Transaction processing

Transaction processing is the most important stage of a flow where the payment application determines the outcome of the flow / transaction.

Handle the request

The request 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 Request 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().

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.

Important - the amounts structure in the response is very important. If the transaction was approved with the full amounts charged, you should use the request amounts (from TransactionRequest) to pass in via the approve(Amounts processedAmounts, String... paymentMethod) method in the builder. The reasons for this is to ensure integrity of the amounts structure. If the payment application response amounts use a different structure for the processed amounts (for example, sets the total approved as base and ignores the additional amounts), it will be difficult for other AppFlow applications to match additional amounts (such as a donation) and see whether they were processed correctly. It also creates issues for split flows (split bill).

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.

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;

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

In addition, it is recommended that any other relevant information the payment application may have about a transaction is added to the transaction references via the withReference() method. See TransactionResponse References for details. You may also set any custom and bespoke references your payment application and/or host/gateway may have defined.

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

Only applicable if your payment application supports a separate card reading step.

Handling the request

Most of the information provided in the request section for transaction processing applies here as well.

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 response here is managed via GenericStageModel. In terms of how to parse the request and create a response for any given type, please see the Request Types page which details all the supported types.

Other considerations

Parsing amounts

Zero base amount value

Due to the ability for flow services to pay off amounts, it is possible that the TransactionRequest amounts in TRANSACTION_PROCESSING stage will have a base amount value of zero. The stage will never be called if the total if zero, but if the base amount is zero and there are additional amounts left to pay (such as tip or fees), then your payment application will still be called and must be able to handle this scenario.

Amounts structure

It is important to understand that there may be additional amounts that are not directly relevant to the host/gateway your payment application communicates with. Take the following Amounts breakdown as an example,

  • baseAmount: 1000
  • tip: 200
  • cashback: 200
  • charityDonation: 50
  • = total of 1450

If your host/gateway supports sending tip and cashback separate to the base amount, but not the charityDonation then your payment application must make sure to append that amount (and others like it) to the base amount. Otherwise the individual fields in the host message won't match up with the total.

You can easily construct a base like this via the Amounts.getTotalExcludingAmounts(String... identifiers). See Dealing with amounts for code samples.

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

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