Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

As example, we will be demonstrating a Sale to POI payment request and validate the input request on the sale terminal. Note that required functions were added in ASL and is available in ASL-2.8.10a.aar.

...

  1. payment request sent to POI terminal from Sale terminal.

...

  1. We chose to use Merchant confirmation of a partially approved transaction as example to validate the input request (Merchant confirmation of a partially approved transaction ).

  1. When it is 's time for the merchant to confirm partial transaction approval, the Point of Interaction (POI) will send an input request to the sale sales terminal. Here is the code that satisfies fulfills this condition.requirement:

Code Block
languagejava
    public boolean merchantConfirmPartiallyApprovedTransaction(DisplayParams displayParams, byte[] bytes) {
        Log.d(LOG_TAG, "merchantConfirmPartiallyApprovedTransaction");

        ArkosContext arkosContext = new ArkosContext(bytes);
        String amount = CurrencyFormat.format(arkosContext.getAmount());
        String requestedAmount = CurrencyFormat.format(arkosContext.getRequestedAmount());
        String leftToPay = CurrencyFormat.format(arkosContext.getLeftToPay());

        String prompt = getString(R.string.confirm_partial_approval);
        return PaymentSystem.getRetailHandler().SendInputRequest(prompt);
    }

  1. Typically you simply need to add a SendInputRequest() method similar to the one below in the RetailHandlerclass which can be found in the \Arkos\workspaces\android-demo-fixed\app\src\main\java\ca\amadis\arkospay\RetailHandler.java file:

Code Block
languagejava
    public synchronized boolean SendInputRequest(String display) {
        if (bConnected) {
            Log.d(LOG_TAG, "POI input request: " + display);
            InputResponse resp = retailModule.requestInput(OutgoingInputRequest.BasicConfirmation(display, 60));
            if(resp.wrap().getResult().equals("Success")){
                return resp.wrap().getConfirmation();
            }
        }
        return false;
    }

The getResult() function checks if result is a success. A success means we were able to receive a response.

As a reminder, the requestInput() function used in the SendInputRequest() method (above) has been added in ASL and is available in ASL-2.8.10a.aar.

You can refer to AndroidAlso add the following extra import statements to that same \Arkos\workspaces\android-demo-fixed project or the RetailHandler class as example.

...

\app\src\main\java\ca\amadis\arkospay\RetailHandler.java file.

Code Block
import ca.amadis.asl.retail.event.ErrorEvent;
import ca.amadis.asl.retail.request.InputRequest;
import ca.amadis.asl.retail.request.InputResponse;
import ca.amadis.asl.retail.request.OutgoingInputRequest;
import ca.amadis.asl.tlv.TlvTree;

public class RetailHandler extends Thread {

    private final String LOG_TAG = "Retail";

    private final MainActivity activity;
    final private int port = 2824;
    private NexoRetailModule retailModule;
    private boolean bConnected = false; // A Retail client is connected
    private boolean bSuspended = false; // Retail actions are suspended

    private NexoEvent pendingEvent = null;

    public NexoEvent getEvent() {
        return pendingEvent;
    }

    public interface RequestListener {
        void onRequest(NexoEventType type);
    }

    private RequestListener mRequestListener = null;

    public void setOnRequest(RequestListener listener) {
        mRequestListener = listener;
    }


    public RetailHandler(MainActivity activity) {
        setName("RetailActions");
        this.activity = activity;
        start();
    }

    @Override
    public void run() {

        setPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
        retailModule = NexoRetailModule.StartServer(port);
        NexoRetailModule.EnableLogging();

        Log.d(LOG_TAG, "Server listening on Port " + port);
        while (true) {
            NexoEvent event = retailModule.poll();
            if (event != null && event.eventType != NexoEventType.None) {
                processEvent(event);
            }
        }
    }

    public synchronized void Pause() {

        if (!bSuspended && bConnected) {
            retailModule.signal(NexoSignal.Busy);
            Log.v(LOG_TAG, "Polling suspended");
        }
        bSuspended = true;
    }

    public synchronized void Resume() {

        if (bSuspended && bConnected) {
            retailModule.signal(NexoSignal.Available);
            Log.v(LOG_TAG, "Polling resumed");
        }
        if (pendingEvent != null) {
            ReplyFailure("Aborted");
        }
        bSuspended = false;
    }

    public synchronized void ReplyFailure(String reason) {
        if (pendingEvent != null) {
            String eventName = pendingEvent.eventType.toString();
            Log.e("Retail", eventName + " Failure: " + reason);
            if (pendingEvent.eventType == NexoEventType.InputRequest) {
                retailModule.respondInputFailure(pendingEvent);
            } else {
                retailModule.respondBasicFailure(pendingEvent, reason);
            }
            pendingEvent = null;
        }
    }

    public synchronized void ReplySuccess() {
        if (pendingEvent != null) {
            String eventName = pendingEvent.eventType.toString();
            Log.i(LOG_TAG, eventName + " Success");
            retailModule.respondBasicSuccess(pendingEvent);
            pendingEvent = null;
        }
    }

    public synchronized void ReplyInputSuccess(int selected) {
        if (pendingEvent != null) {
            InputRequest inputEvent = (InputRequest) pendingEvent;
            String selectedStr = inputEvent.choices[selected];
            Log.i(LOG_TAG, "InputRequest Success " + selected + " = " + selectedStr);
            retailModule.respondInputSuccess(pendingEvent, selected);
            pendingEvent = null;
        }
    }

    public synchronized void ReplyPaymentResult(PaymentResultID result) {
        if (pendingEvent != null) {

            TlvTree reply = TlvTree.Empty();
            byte[] receiptData = ReceiptHandler.load(ReceiptHandler.Type.Transaction);
            reply.AddBinSerialized(0xFF01, receiptData);
            reply.AddChild(0xFF02); // TODO add merchant Data
            Log.i(LOG_TAG, "PaymentRequest send Result: " + result.toString());
            retailModule.respond(pendingEvent, reply.AsBytes());
            pendingEvent = null;
        }
    }

    public synchronized void SendPOIReplication(String display) {
        if (bConnected && pendingEvent != null) {
            if (pendingEvent.eventType == NexoEventType.PaymentRequest) {
                Log.d(LOG_TAG, "POI Replication: " + display);
                retailModule.requestDisplay(display, Qualification.POIReplication);
            }
        }
    }

    public synchronized boolean SendInputRequest(String display) {
        if (bConnected) {
            Log.d(LOG_TAG, "POI input request: " + display);
            InputResponse resp = retailModule.requestInput(OutgoingInputRequest.BasicConfirmation(display, 60));
            if(resp.wrap().getResult().equals("Success")){
                return resp.wrap().getConfirmation();
            }
        }
        return false;
    }
    private void processEventRequest(NexoEvent event) {

        NexoEventType type = event.eventType;

        if (mRequestListener == null) {
            Log.e(LOG_TAG, "Not any listener");
            retailModule.respondBasicFailure(event, "Not supported");
    
   }
        // Ignore Retail request when app is not in resumed state
        else if (activity.getLifecycle().getCurrentState() != Lifecycle.State.RESUMED) {
            Log.w("Retail", type.toString() + "Ignored : App Paused");
            retailModule.respondBasicFailure(event, "Paused");
        }
        // Process request
        else {
            if (pendingEvent != null) {
                Log.w(LOG_TAG, pendingEvent.eventType.toString() + "Aborted (new incoming event)");
                pendingEvent = null;
                ReplyFailure("New incoming event");
            }
            pendingEvent = event;
            activity.runOnUiThread(() -> mRequestListener.onRequest(type));
        }
    }

    private synchronized void processEvent(NexoEvent event) {

        Log.i(LOG_TAG, "Event " + event.eventType.toString());

        switch (event.eventType) {
            case Login:
                bConnected = true;
                if (bSuspended) {
                    retailModule.signal(NexoSignal.Busy);
                }
                break;

            case Logout:
            case Disconnection:
                bConnected = false;
                break;

            case DisplayRequest:
            case AdminRequest:
            case InputRequest:
            case PaymentRequest:
            case ReversalRequest:
                processEventRequest(event);
                break;

            case ErrorReport:
                ErrorEvent ee = (ErrorEvent) event;
                Log.e(LOG_TAG, "Error Report: " + ee.errorDescription);
                break;

            default:
                Log.e(LOG_TAG, "Unknown Event Type =" + event.eventType.toString());
                retailModule.respondBasicFailure(event, "Unknown event type");
                break;
        }
    }

}

...

An example of a response you can send with the confirmation can be found below:

Code Block
languagexml
<?xml version="1.0"?>
<SaleToPOIResponse>
	<MessageHeader MessageClass="Device" MessageCategory="Input" MessageType="Response" DeviceID="1374" SaleID="ArkosSales" POIID="PreCertificationAmadis1"/>
	<InputResponse>
		<InputResult Device="CashierInput" InfoQualify="Input">
			<Response Result="Success"/>
			<Input InputCommand="GetConfirmation">
			<ConfirmedFlag>true</ConfirmedFlag>
			</Input>
		</InputResult>
	</InputResponse>
</SaleToPOIResponse>