en | fi

Poplatek

JSONPOS integration guide (2018-03-27)

1 Introduction

This document summarizes how to implement a POS using JSONPOS to maximize robustness and automatic recovery in error situations. For details of the specific JSONPOS requests, see the JSONPOS protocol specification.

2 Integration checklist

2.1 Transport handling

  • Check that _CloseReason is sent before closing a connection when possible.

  • Check that POS responds to _Keepalive requests in all situations (including pending Purchase or other request).

  • Check that POS monitors the connection using _Keepalive reliably, see discussion below. Keepalive monitoring is the most important connection reliability mechanism because it covers all stack layers in one mechanism.

  • Check that POS correctly parses fragmented JSONRPC frames. An easy torture test is to simulate a situation where each transport read call returns only one byte of data. Everything must work correctly even in this case.

  • Check that POS correctly parses multiple JSONRPC frames from one transport layer read() call.

  • Check that POS correctly sends and receives non-ASCII Unicode characters.

  • Check that POS always responds to inbound requests at some point. Terminal assumes that if keepalives are functional, every request will be eventually processed to completion (success or error). For example, if the terminal sends a NetworkConnect request, POS must always respond with success/error or error eventually (even in connection timeouts etc).

2.2 Transaction reliability

  • See transaction reliability notes below.

2.3 Network proxy (if implemented by POS)

  • Trigger three parallel Test methods with test_id: "large_file_download" and ensure that the downloads complete without the JSONPOS connection being dropped due to e.g. timeout, and that normal purchase use cases work reasonably well even when downloads are in progress. This exercises network proxy rate limiting and fairness.

  • When remote peer closes a TCP connection, ensure that any pending POS-to-terminal Data notifies are sent to completion before sending a NetworkDisconnected to the terminal.

3 JSONPOS Test method

The JSONPOS protocol has a Test method intended for development time stress testing of the POS implementation. A test can be initiated by sending a test request:

{
    "jsonrpc": "2.0",
    "method": "Test",
    "id": "xxx",  // replace with dynamic ID
    "params": {
        "api_key": "xxx", // replace with actual API key
        "test_id": "xxx"  // replace with actual test ID
        // possible additional test parameters
    }
}

The available test IDs may change arbitrarily between terminal software releases. Current tests include:

test_id Description
large_file_download Download a ~1MB file and verify it downloads correctly.

4 Transport connection monitoring

4.1 JSONRPC _Keepalive is the primary mechanism

The JSONRPC transport connection should be primarily monitored using periodic JSONRPC _Keepalive requests initiated by the POS. This is preferable over lower level mechanisms like TCP/IP pinging or Bluetooth RFCOMM monitoring because it ensures end-to-end functionality of the whole connection stack with a single mechanism.

The _Keepalive mechanism allows each peer to monitor the other peer independently. Each peer can freely select the interval when keepalive requests are sent; the other peer is only required to respond to incoming keepalive requests promptly (even when processing some longer term request like a network connection or a purchase). The POS is thus free to e.g.:

  • Use a longer keepalive interval in idle mode and a shorter one when a purchase or operation is active, or about to start.

  • Send additional keepalive requests to ensure connection functionality just before some critical points in code. For example, POS may send a keepalive request just before starting a transaction.

4.2 Other monitoring mechanisms

The POS should use other mechanisms for connection status monitoring where available, as such mechanisms may be faster than _Keepalive based monitoring. For example:

  • If a TCP connection is closed, the transport is certainly lost and recovery can begin immediately. There's no reason to wait for _Keepalive timeout.

  • If RFCOMM is used and the Bluetooth stack provides status information indicating that the RFCOMM connection was lost, recovery can similarly begin immediately.

5 Recovering from transport connection drop

5.1 Pending requests must be terminated with error

When a transport connection drop is detected, the POS should first ensure all pending requests which were initiated over the lost connection are terminated. Conceptually each such request should fail with an error, just as if the terminal had sent an error reply. If the terminal sent a _CloseReason, it may be associated with the terminated pending requests as a possible cause.

It's important for nothing to remain pending if a connection is lost, as there will never be a reply from the terminal to the pending requests, even after establishing a new connection (requests are bound to a specific connection).

5.2 Reinitializing the connection

Once pending requests have been dealt with, the POS should reconnect to the terminal. The specific mechanism depends on the transport in question (TCP/IP or RFCOMM).

For RFCOMM, it is important to:

  • Close current RFCOMM connection (if not already closed) and reconnect to the terminal.

  • Before sending a _Sync, read and ignore data from the RFCOMM connection for several seconds; 5 seconds is a good starting point. Sometimes data belonging to an earlier connection will trickle out a reconnected RFCOMM connection because RFCOMM can in many cases resume a previous connection.

  • Send a _Sync and wait for a response for several seconds. It's important to use a unique request ID for each _Sync, and ensure that the response received matches the request ID. This ensures that the sync response is not an old one. If no sync response is received in a reasonable time, say 5 seconds, fail the connection attempt and retry.

  • If the process fails at any point, disconnect RFCOMM and retry from start. It's a good idea to reconnect the RFCOMM as far as the Bluetooth stack is concerned, so that any hanging connection situations are resolved as reliably as possible.

In rare situations it may be that some operating system or application state in the POS or the terminal prevents a transport connection from forming. For example, maybe a Bluetooth driver is stuck and fails to process any data despite repeated connection attempts. The POS can recover from this situation by:

  • Keeping track of the number of failed consecutive connection attempts.

  • If the count is high enough, reboot the POS and then resume reconnecting.

  • At present there's no automatic reboot in the terminal from lack of Bluetooth connectivity, because there's no expectation that a Bluetooth connection is constantly maintained. The POS UI might suggest for the user to reboot the terminal manually.

5.3 RFCOMM pairing

The RFCOMM Bluetooth pairing process is quite complicated and may sometimes fail. It should be easy to retry pairing from the POS UI.

Once paired, there are no known issues for pairing state to be lost.

6 JSONRPC parsing reliability

6.1 JSONRPC message fragmentation

When parsing JSONRPC framed message it's critical to avoid any assumptions about TCP/RFCOMM transport read calls returning complete messages or even complete message fields. In particular:

  • When reading the length prefix (8 hex digits) of a JSONRPC message, there are no guarantees that it arrives in one read call.

  • When doing a read(), the POS may receive a partial message, but may also receive multiple complete messages in addition to a possible partial one. All of the completes messages must be processed before waiting for new data to arrive.

These are important to be correctly implemented because the corner cases like partial length field or multiple messages arriving in one read() are rare. They do happen, though, and if handled incorrectly, this leads to very difficult-to-diagnose problems. What's worse, an update in the terminal software may affect how JSONRPC messages are fragmented across TCP/RFCOMM reads which may then trigger a production issue in the POS integration.

One good "torture test" is to use a debug build where the POS reads incoming JSONRPC data one byte at a time. The JSONRPC message parsing and processing should work flawlessly even in this case.

6.2 JSON issues

The messages are in JSON format which is well documented. If a custom JSON parser is used, it's important to stress test it. Particular issues that should be tested:

  • Handling of non-ASCII string data. Most messages are pure ASCII so failure to handle non-ASCII correctly may lead to odd production issues.

  • At present non-ASCII characters are escaped in the JSONRPC layer so that the resulting encoded message is ASCII only. This may change in the future, and implementing UTF-8 parsing is recommended.

7 Transaction reliability

7.1 Pending requests and Purchase/Check

When the JSONRPC connection is lost, the POS has no way of knowing what happened to pending requests:

  • The terminal may not have received the request at all.

  • The terminal may have received the request, but failed to process it because it detected that the transport connection was lost.

  • The terminal may have received the request, processed it, and sent a reply, but the reply was lost before it received the POS.

For Purchases it is critical to ensure that the POS can reliably figure out what happened to any initiated Purchase. To support this goal, JSONPOS provides a Check method which allows the POS to check what happened to a previously initiated Purchase. The Check request may be sent as many times as needed.

The Check response contains exactly the same data as the corresponding Purchase response, even across terminal reboots (the data is persisted to flash). The Check request can thus be used to complete any pending purchase reliably regardless of connection drops. Note that Check may need to be attempted multiple times in case the transaction is still on-going.

7.2 Reliable POS transaction processing

The best possible approach for POS is to recover from both terminal and POS reboots as follows:

  • When the POS is about to start a Purchase, allocate IDs for the Purchase and write them to POS persistent storage.

  • Initiate the Purchase with the persisted ID information.

  • If Purchase response is received, process the result and update the state in persistent storage to indicate the transaction is finished.

  • If the JSONRPC connection is lost, reconnect, and use Check request to complete the purchase. If Check result indicates transaction is still active, periodically resend Check until the transaction is over.

  • If the POS reboots, use the persisted state to detect that a Purchase is in progress and use Check request to figure out what happened. If the POS rebooted before the initial Purchase request reached the terminal, the terminal will indicate "transaction unknown" which is a reliable indication that no purchase was processed at all.

This processing should be manually tested for each connection drop, terminal reboot, POS reboot condition. As far as the JSONPOS protocol is concerned, it should be possible to process the transaction to completion reliably in every situation.

8 Network proxying

Network proxy support, i.e. NetworkStart, NetworkConnect, NetworkDisconnect, is used with Bluetooth RFCOMM. It's important for the proxying to be robust, because any bugs in proxying code lead to very difficult-to-diagnose problems that may come and go over time.

There's no specific technique to write robust proxying code, but general recommendations include:

  • Stress test parallel connections carefully. The terminal establishes multiple parallel connections in normal operation.

  • Ensure that the POS handles connection closure correctly. Specifically, the POS must deliver all pending data before sending a NetworkDisconnected. Otherwise the terminal will fail to receive some of the pending data which can lead to terminal communication failures.

  • Ensure that POS rate limiting mechanisms are robust. It's important that data traffic doesn't saturate the RFCOMM link; otherwise keepalive monitoring may fail which brings down all network connections.

  • Ensure that network connection state is terminated and any connections are closed if the JSONRPC transport is closed. Any open TCP connections cannot be continue across JSONRPC transport reconnection, so it's critical to always close the connections reliably to avoid e.g. leaking connection state.

Version: 7952e64