en | fi

Poplatek

Poplatek JSONPOS API (2018-08-16)

1 Overview

Poplatek JSONPOS API allows a point-of-sale system (POS) to integrate with the Poplatek payment terminal (PT). This document defines the protocol used between POS and PT.

1.1 Protocol stack

Layers of the protocol stack (bottom up) are:

  • Stream layer: (1) a plain TCP connection, (2) a Bluetooth RFCOMM connection, or (3) a USB serial connection between POS and PT. For TCP, POS acts as the initiator and connects to TCP port 10001 of the terminal.

  • Framing layer: there are two alternatives to message framing: (1) a text based length+message+newline protocol, and (2) WebSocket text messages. Framing and messages are character based and easy to diagnose manually.

  • JSONRPC layer: JSONRPC methods (request/response) and notifications (request only) are used to implement the payment protocol and connection maintenance such as keepalives. These are described in this document.

1.2 General implementation principles

  • Ignore any keys not needed for message processing. Tolerate missing keys unless they are truly mandatory (such as required transaction identifiers).

  • JSON object keys have no guaranteed ordering. Don't rely on a specific key order (e.g. the order given in examples).

  • Use error string codes (such as TRANSACTION_NOT_FOUND) as a programmatic identifier for error types. However, avoid depending on specific errors unless really necessary, as the codes may change over time. Specific error codes may be guaranteed for special situations, such as TRANSACTION_NOT_FOUND when Check cannot locate a matching transaction.

  • Ensure JSONRPC id fields sent are unique for each connection, and preferably unique over all connections. The examples in this document use fixed values but actual requests must have unique values.

  • Use error display_message optional parameter to display error messages for the cashier. The messages may be translated to the cashier_language language. If this field is not present, use the main level "message" field instead.

  • Be careful when reading data from a socket: there is no guarantee that TCP read() calls return data that matches JSONRPC transport boundaries. A peer should work correctly even if a JSONRPC message is received in one-byte pieces. Specific error cases to look out for:

    • A read() may return a partial message, including a partial length field.

    • A read() may return multiple messages at once.

  • Avoid dependencies on message timing. There are significant timing differences between devices and software versions. There is no guarantee that a specific message (such as a purchase reponse) is available immediately in a specific place in code.

  • Because the protocol is asynchronous, do not rely on a specific response message arriving at a specific place in code. It is always possible that an unrelated notification or keepalive message arrives instead.

  • Currency is always required with amount.

  • PosMessages are intended to be displayed to the salesperson as such and not intended for concluding the state of the transaction. Availability and sequence of the PosMessages vary by payment methods, language and timing.

1.3 Example message formatting

  • The method descriptions in this document are pretty-printed JSON messages which include the JSONRPC fields (such as method) but exclude the JSONRPC transport framing (length, colon, and trailing newline, or WebSocket). Newlines are added for clarity. Long strings like receipt data are truncated in the examples.

  • Javascript comments (// and / comment /) are used to clarify fields. Comments are not valid JSON and must not appear in actual on-the-wire JSON.

  • The terminal includes an informative response_to field in method responses which is omitted from the examples. The response_to field must not be processed programmatically.

  • The JSONRPC transport framing is not included in the method examples, but is always present for requests, responses, and notifications.

  • The id fields in the examples are fixed, but an actual implementation must use unique id for each request.

The actual on-the-wire JSON format is not pretty printed. Key ordering varies between messages. Comments are not allowed as they are not valid JSON.

2 JSONRPC transport summary

2.1 Available transport interfaces

Interface Description
TCP Plain, unencrypted TCP connection. POS connects to terminal port 10001. Supports both text-based transport and WebSocket, with autodetection.
Bluetooth RFCOMM SPm20 only: RFCOMM channel 1, can be discovered via Bluetooth SDP as UUID 00001101-0000-1000-8000-00805F9B34FB. Requires _Sync handshake.
USB serial SPm20 only (software version 18.5.0 or higher): USB serial. Ensure transparent line discipline (terminal uses and expects LF line endings). Requires _Sync handshake.

2.2 Text-based transport

The transport uses a plain TCP, RFCOMM, or USB serial connection which carries JSONRPC 2.0 framed messages:

  • Hex encoded 8-digit length field followed by a colon (:).

  • JSON-serialized pure ASCII JSONRPC message (non-ASCII codepoints must be \uNNNN escaped).

  • Newline (0x0a), not included in the length field.

Example (newline is not shown):

00000055:{"jsonrpc":"2.0","method":"ExampleMethod",
"params":{"argument":"value"},"id":"req-1"}

Here's a Javascript example of forming a transport framed message:

// JSON data must be encoded in ASCII which also means character and byte
// length will match.
function jsonStringifyAscii(val) {
    return JSON.stringify(val).replace(/[\u007f-\uffff]/g, function (x) {
        return '\u' + ('0000' + x.charCodeAt(0).toString(16)).substr(-4);
    });
}

var id_counter = 0;
var obj = {
    jsonrpc: '2.0',
    method: 'ExampleMethod',
    params: { argument: 'value', nonascii: 'foo\u1234bar' },
    id: 'req-' + (++id_counter)
};

var jsonData = jsonStringifyAscii(obj);
var frame = ('00000000' + jsonData.length.toString(16)).substr(-8) +
            ':' + jsonData + '\n';

// Write 'frame' (which is pure ASCII) to the socket.
console.log(frame);

The result is:

0000006f:{"jsonrpc":"2.0","method":"ExampleMethod",
"params":{"argument":"value","nonascii":"foo\u1234bar"},"id":"req-1"}

Messages on the wire are typically one-line packed JSON, but examples below and throughout this document are pretty printed for readability.

A JSONRPC request has the form:

// 'method' identifies method to be invoked
// 'params' is an object containing method arguments
// 'id' is a unique string chosen by the requester (must be unique within
//     a single connection, preferably across all connections)

XXXXXXXX:{
    "jsonrpc": "2.0",
    "method": "MethodName",
    "params": {...},
    "id": "req-N"
}

If the request succeeds, the corresponding reply has the form:

// 'result' is an object containing method results

XXXXXXXX:{
    "jsonrpc": "2.0",
    "result": {...},
    "id": "req-N"
}

If the request fails, the corresonding error has the form:

// 'code' is an integer error code which is ignored
// 'message' provides a short error description (one line)
// 'string_code' provides a string-based error code, e.g. CARD_REMOVED
// 'details' provides an optional traceback or other error details

XXXXXXXX:{
    "jsonrpc": "2.0",
    "error": {
        "code": 1,
        "message": "card removed",
        "data": {
            "string_code": "CARD_REMOVED",
            "details": "CARD_REMOVED: card removed\n\ttraceback...\n\t[...]"
        }
    },
    "id": "req-N"
}

A JSONRPC notification is similar to a method request but lacks an "id" field and doesn't get any reply.

XXXXXXXX:{
    "jsonrpc": "2.0",
    "method": "MethodName",
    "params": {...}
}

The JSONRPC transport defines a few special methods and notifications for connection management:

  • _Keepalive: request for connection keepalive, respond with empty object, mandatory to implement.

    • However, at present the terminal is lenient and allows a POS to ignore _Keepalive requests: if the POS never responds to any keepalive requests only one request is sent and its timeout is silently ignored. This is only for backwards compatibility, new implementations must always respond to keepalive requests.
  • _Info: notification about an informative event, no action required, optional.

  • _Error: notification about an error, no action required, optional.

  • _CloseReason: notification about connection being closed, no action required, optional.

  • _Sync: (re)synchronization of a Bluetooth RFCOMM stream or USB serial stream, see Bluetooth RFCOMM section for details. Mandatory when using RFCOMM or USB serial.

The JSONRPC transport is described in more detail in "Common JSON/RPC transport". The _Sync command used with RFCOMM is described in a separate section below.

The method descriptions below include JSONRPC fields but omit the framing.

2.3 Local WebSocket transport

An alternative to the XXXXXXXX:{...}\n framing is WebSocket:

  • WebSocket is autodetected from the same port, 10001. Request URI path is /jsonpos so connection URI is ws://myterminal.local:10001/jsonpos.

  • WebSocket subprotocol is jsonrpc2.0.

  • The terminal only supports plain TCP (ws://), not SSL (wss://). As a result, a browser page connecting to the terminal must be served using plain HTTP (not HTTPS) because most browsers don't allow a plain TCP WebSocket connection from a "secure page".

  • JSONRPC messages are carried as JSON-encoded UTF-8 WebSocket Text messages (opcode 1). WebSocket Binary messages (opcode 2) are not used.

  • Terminal doesn't currently send WebSocket Ping messages as they are redundant with _Keepalive. POS must still support them as part of WebSocket. Terminal will respond to Ping messages if sent by POS.

The code examples below are illustrative only.

To connect to the terminal from a web page:

var websock = new WebSocket('ws://myterminal.local:10001/jsonpos',
                            [ 'jsonrpc2.0' ]);
websock.addEventListener('message', function (event) {
    // event.data is a string with the JSONRPC message; process it.
});
// Other events like 'open', 'close', 'error' are available and should
// be handled.

To send a message to the terminal (once connected):

websock.send(JSON.stringify({
    jsonrpc: '2.0',
    method: 'Purchase',
    id: getRequestId(),
    params: {
        // ...
    }
}));

To close the connection:

websock.close(1000, 'closed by POS');

WebSocket only provides a simple JSON message transport. Request/response dispatch must be implemented on top of the raw JSON messaging, just as with the length-message-newline framing.

2.4 Remote WebSocket transport

WebSocket can also be used to connect to the terminal via a server endpoint. This works similarly to a local WebSocket connection but uses TLS (wss://) and HTTP Basic authentication. The WebSocket URI path includes a terminal ID (12345 in the example below):

// Development:
var websock = new WebSocket(
  'wss://user:pass@api.sandbox.poplatek.com/api/v2/terminal/12345/jsonpos',
  [ 'jsonrpc2.0' ]
);

// Production:
var websock = new WebSocket(
  'wss://user:pass@api.poplatek.com/api/v2/terminal/12345/jsonpos',
  [ 'jsonrpc2.0' ]
);

The target terminal is identified using a terminal ID. If multiple terminals are online with the same terminal ID, there is no guarantee which terminal is chosen for the connection.

There are minor differences to local WebSocket connections:

  • An API key is not required: the HTTP authentication provides authorization.

3 API key

Most POS-initiated JSONPOS methods need an api_key authenticator given in the request params object. An API key is not needed for transport methods and notifications (such as _Keepalive and _CloseReason), or methods explicitly indicated as being unauthenticated (such as Status).

API keys:

  • Development: "api_key": "e086554a-9a35-4bbf-9f99-a8a7cd7d1dcc"

  • Production: request an API key from developers@poplatek.fi

4 External data

Some requests like Purchase and Refund may include an external_data field containing free form data that the POS wants to transfer to the payment server for some specific use. Intended usage is for POS specific identifiers, receipt data, etc. Limitations:

  • JSON encoded byte length (shortest encoding without indent etc) must be 50kB (50 1024 bytes) or less. Byte length is computed from the UTF-8 encoded form of the encoded JSON data.

  • JSON array/object nesting level must be at most 10, with the level including the externaldata object itself. For example, the following would have a depth of 2: { "values": [ 1, 2, 3 ] }.

  • Keys and string values must be valid UTF-8, codepoints in U+D800 to U+DFFF (surrogates) must not be used. Invalid UTF-8 is rejected. Recommendation is to use only [0-9a-zA-Z] in keys.

5 Identifier spaces

5.1 Shared

5.2 Messages sent by PT

  • transaction_id: 20-digit number sequence used to identify a transaction for e.g. cancel or refund. PT provides in purchase response, also included in receipt data. The format is opaque in the protocol and currently consist of a 12-digit filing code followed by 8-digit logical terminal ID (on receipts there is a space between the two groups).

  • transaction_unique_id: Poplatek internal unique identifier for transaction. Identifies a transaction uniquely in Poplatek payment service. Only used for diagnostics and manual investigation.

5.3 Messages sent by POS

  • receipt_id: Numeric identifier used by POS to identify a receipt. One receipt may contain multiple purchases.

  • sequence_id: Numeric identifier used by POS to identify a transaction uniquely when receipt ID and amount match. Needed only when a receipt contains multiple purchases. Correct currency must always be used.

6 Offline transactions

Payment terminal supports network offline transactions. During the transaction processing if offline is attempted the payment terminal will send a PosMessage stating that the transaction is about to be performed offline. For example like the message below:

{
    "method": "PosMessage",
    "jsonrpc": "2.0",
    "params": {
        "message": "Ongelma verkkoyhteydess\u00e4.\
        Katevarmennuksen vaativat kortit eiv\u00e4t toimi."
    }
}

In the transaction response messages, the payment terminal adds a "offline" boolean field indicating whether the transaction was attempted in offline or online.

7 Methods provided by PT

7.1 TerminalInfo

Request PT software version information. This request doesn't require an API key.

Request:

{
    "jsonrpc": "2.0",
    "method": "TerminalInfo",
    "id": "pos-1",
    "params": {
        // No specific required fields, but any build, software version,
        // or device identification information can be included here.
    }
}

Response:

{
    "jsonrpc": "2.0",
    "id": "pos-1",
    "result": {
        "hardware_id": "00081927799c",
        "terminal_id": 90300006,
        "version": "14.8.3",
        "revision": "717e9c1",

        // Printer field present only if printer available through API,
        // depending on HW and SW configuration.
        "printer": {
            "color": "bw",       // black and white
                                 // reserved: "greyscale", "color"
            "max_height": 1000,  // pixels
            "max_width": 385     // pixels
        },

        // Terminal model name.
        "model_name": "SPm20",

        // Both sales location and terminal may have names
        // defined in terminal management tools and API.
        // These can be shown to user or cross checked in
        // POS implementation to make sure that the POS
        // is connected to correct terminal running correct
        // configuration.
        "name": "Cashier 2", // Terminal configured name
        "sales_location_name": "Ye Olde Shoppe", // Sales location name

        // Terminal may add arbitrary additional identifiers here.
        // These identifiers may be added and removed without notice.

        // Some terminals may indicate a link speed hint (bytes/second)
        // which is useful for rate limiting.  Currently provided by
        // SPm20 for RFCOMM network proxy use.
        "link_speed": 9000
    }
}

Current model names:

Name Description
YOMANI Worldline YOMANI ML or YOMANI XR
YOXIMO Worldline YOXIMO
XENOA-ECO Worldline XENOA ECO
VALINA Worldline VALINA
SPm20 Spire SPm20

7.2 Status

Request PT status. This request doesn't require an API key.

Request:

{
    "jsonrpc": "2.0",
    "method": "Status",
    "id": "pos-1",
    "params": {
        // No specific required fields.
    }
}

The response contains the same fields as a StatusEvent notify, see StatusEvent for details.

7.3 Purchase

Request for 123,45 EUR purchase:

{
    "jsonrpc": "2.0",
    "method": "Purchase",
    "id": "pos-1",
    "params": {
        "api_key": "e086554a-9a35-4bbf-9f99-a8a7cd7d1dcc",
        "cashier_language": "en",

        // POS identifiers.
        "receipt_id": 123,                  // mandatory
        "sequence_id": 234,                 // optional

        // Amount and currency.  If amount is missing, only read and
        // return card data (loyalty, tokens).  Currency is mandatory
        // when amount (or cashback_amount) is present.
        "amount": 12345,
        "cashback_amount": 500,  // optional: used only for cashback
                                 // transactions, may not exceed amount
        "currency": "EUR",

        // Bypass PIN: optional, defaults to false.
        "bypass_pin": false,

        // Preferred receipt width in characters: optional, e.g. 40.
        // The receipt may contain less characters per line than
        // requested but not more apart from a trailing newline.
        "preferred_receipt_text_width": 40,

        // Forced authorization: ignored at present, terminal always
        // forces authorization.
        "forced_authorization": true,

        // Optional features, enabled only on selected terminals.

        // Option to stop processing on card insert/swipe/tap. Terminal stops
        // processing and queries POS how to proceed with CardInfo request.
        "stop_on_card_info": false,

        // Optional list of loyalty programs.  If card presented is one
        // of the listed loyalty programs, terminal stops processing and
        // queries POS how to proceed with an AppInfo request.
        "stop_on_loyalty": [ "s_etu" ],

        // Stop on application info, for all cards. Optional, default to false.
        "stop_on_app_info": true,

        // If present (and true), the request does not time out.
        // After first card interaction the transaction may time out.
        "no_timeout": false,

        // Optional free form external data object.
        "external_data": {
            "name": "John Doe",
            "shift": {
                "number": 123
            }
        }
    }
}

Success response:

{
    "jsonrpc": "2.0",
    "id": "pos-1",
    "result": {
        "offline": false,             // optional for online transactions,
                                      // default to false
        "amount": 12345,
        "currency": "EUR",
        "transaction_id": "14101500003890300006",
        "transaction_unique_id": "70ca6015-8466-4678-9557-beb87ae390bd",
        "bypass_pin": false,
        "forced_authorization": false,
        "card_reading_method": "chip",
        "payment_method": "credit",
        "debit_credit_certain": true,
        "response_text": "Approved",
        "response_code": "00",

        // Card number, masked for salesperson.
        "pan_masked_for_clerk": "527591**3684",

        // Card number, masked for use in customer receipts.
        "pan_masked_for_customer": "**3684",

        // Application name for receipt.
        "card_name": "Debit MasterCard",

        // Transaction time for receipt
        "transaction_time": "160517115724",

        // Receipt text.
        "merchant_receipt": {
            "signature_required": false,
            "id_check_required": false,
            "text": "Selite: Veloitus 1,07 EUR\nKortti: VISA\nNumero:..."
        },
        "customer_receipt": {
            "text": "Selite: Veloitus 1,07 EUR\nKortti: VISA\nNumero:..."
        },

        // Merchant number for the settlement contract selected for
        // the payment card application.
        "merchant_number": "09388984",

        // List of valid schemes for the selected contract.
        "contract_valid_for_schemes": [
            "VI",
            "MC"
        ],

        // If tokens enabled: current tokens, for storing.
        "store_token": "PIT1234567890123456789",

        // If tokens enabled: all tokens to look up for this
        // application, also contains expiring tokens.
        "lookup_tokens": ["PIT1234567890123456789", "PIT1644567490123456989"],

        // In case tokenization has failed.
        "tokenization_error_code": "INTERNAL_ERROR",
        "tokenization_error_description": "Error: INTERNAL_ERROR: Token...",
        "tokenization_error_details": "Error: INTERNAL_ERROR: Token..."
    }
}

Error response:

{
    "jsonrpc": "2.0",
    "id": "pos-1",
    "error": {
        "code": 1,
        "message": "card removed during emv pin entry operation",
        "data": {
            "string_code": "CARD_REMOVED",
            "display_message": "...",
            "details": "Error: CARD_REMOVED: card removed during pin entry...",

            "offline": false,
            "amount": 12345,
            "currency": "EUR",
            "transaction_id": "",
            "transaction_unique_id": "70ca6015-8466-4678-9557-beb87ae390bd",
            "bypass_pin": false,
            "forced_authorization": false,
            "card_reading_method": "chip",
            "payment_method": "credit",
            "debit_credit_certain": true,
            "response_text": "Declined",
            "response_code": "06",
            "pan_masked_for_clerk": "527591**3684",
            "pan_masked_for_customer": "**3684",
            "card_name": "Debit MasterCard",
            "transaction_time": "160517115724",
            "merchant_receipt": {
                "signature_required": false,
                "id_check_required": false,
                "text": "Selite: Ei veloitusta 1,07 EUR\nKortti: VISA\nNumero:..."
            },
            "customer_receipt": {
                "text": "Selite: Ei veloitusta 1,07 EUR\nKortti: VISA\nNumero:..."
            },

            // See success example.
            "store_token": "PIT1234567890123456789",
            "lookup_tokens": ["PIT1234567890123456789", "PIT1644567490123456989"],

            // In case tokenization has failed.
            "tokenization_error_code": "INTERNAL_ERROR",
            "tokenization_error_description": "Error: INTERNAL_ERROR: Token...",
            "tokenization_error_details": "Error: INTERNAL_ERROR: Token..."
        }
    }
}

Depending on the error type, error receipt data, masked PAN, card_name and transaction_time may or may not be present. For example, if purchase request is missing the amount field the result might be for example:

{
    "jsonrpc": "2.0",
    "id": "pos-1",
    "error": {
        "code": 1,
        "message": "/app/lib/usecase/jsonpos_base.lua:77: assertion failed!",
        "data": {
            "string_code": "UNKNOWN",
            "display_message": "...",
            "details": "Error: UNKNOWN: /app/lib/usecase/jsonpos_base.lua:77:..."
        }
    }
}

7.4 Refund

Request example:

{
    "jsonrpc": "2.0",
    "method": "Refund",
    "id": "pos-1",
    "params": {
        "api_key": "e086554a-9a35-4bbf-9f99-a8a7cd7d1dcc",
        "cashier_language": "en",

        // POS identifiers.
        "receipt_id": 124,  // mandatory, for Refund (does not match orig Purchase)
        "sequence_id": 235, // optional, for Refund (does not match orig Purchase)

        // Mandatory amount, up to original Purchase amount.
        // Currency is mandatory and must match Purchase.
        "amount": 45,
        "currency": "EUR",

        // Optional transaction ID, must match Purchase if present.
        "transaction_id": "14101500003890300006",

        // Optional receipt width, see Purchase description.
        "preferred_receipt_text_width": 40,

        // Optional free form external data object.
        "external_data": {
            "name": "John Doe",
            "shift": {
              "number": 123
            }
        }
    }
}

Response, succesfully refunded:

{
    "jsonrpc": "2.0",
    "id": "pos-1",
    "result": {
        "offline": false,
        "amount": 12345,
        "currency": "EUR",
        "transaction_id": "14120200000190300005",
        "transaction_unique_id": "b06681ac-5243-4998-837b-0f648913a662",
        "card_reading_method": "chip",
        "payment_method": "debit",
        "debit_credit_certain": true,
        "response_code": "00",
        "response_text": "Approved",
        "pan_masked_for_clerk": "527591**3684",
        "pan_masked_for_customer": "**3684",
        "card_name": "Debit MasterCard",
        "transaction_time": "160517115724",

        "customer_receipt": {
            "clerk_signature_required": true,
            "text": "...",
        },
        "merchant_receipt": {
            "id_check_required": false,
            "signature_required": false,
            "text": "..."
        },

        "merchant_number": "09388984",
        "contract_valid_for_schemes": [
          "VI",
          "MC"
        ]
    }
}

Response, corresponding purchase not found:

{
    "id": "pos-1",
    "jsonrpc": "2.0",
    "error": {
        "code": 1,
        "message": "original protocol transaction not found",
        "data": {
            "offline": false,
            "amount": 345,
            "currency": "EUR",
            "transaction_id": "",
            "transaction_unique_id": "b06681ac-5243-4998-837b-0f648913a662",
            "card_reading_method": "chip",
            "payment_method": "debit",
            "debit_credit_certain": true,
            "response_code": "06",
            "response_text": "Declined",

            "customer_receipt": {
                "clerk_signature_required": false,
                "text": "..."
            },
            "details": "Error: PROTOCOL_TRANSACTION_UNKNOWN...",
            "merchant_receipt": {
                "id_check_required": false,
                "signature_required": false,
                "text": "...",
            },
            "string_code": "PROTOCOL_TRANSACTION_UNKNOWN",
            "display_message": "...",
        }
    }
}

7.5 Cancel

Cancellation causes the authorization of a previous purchase to be cancelled and avoids debiting the purchase. If the purchase has already been settled, cancellation will fail.

Offline cancel is currently not supported.

Request to cancel a previous purchase; amount, currency, and transaction_id must all match:

{
    "jsonrpc": "2.0",
    "method": "Cancel",
    "id": "pos-1",
    "params": {
        "api_key": "e086554a-9a35-4bbf-9f99-a8a7cd7d1dcc",
        "cashier_language": "en",

        // Mandatory amount and currency.
        "amount": 12345,
        "currency": "EUR",

        // Mandatory transaction ID, from Purchase response
        // or receipt.
        "transaction_id": "14101500003890300006"
    }
}

Response when transaction is found, not yet cancelled, and was successfully cancelled:

{
    "id": "pos-1",
    "jsonrpc": "2.0",
    "result": {
        "offline": false,
        "result": true
    }
}

Response when transaction is found but is already cancelled:

{
    "id": "pos-1",
    "jsonrpc": "2.0",
    "result": {
        "result": true,
        "already_cancelled": true
    }
}

Response in other error cases (e.g. transaction_id is not valid) uses normal error mechanism:

{
    "id": "pos-1",
    "jsonrpc": "2.0",
    "error": {
        "code": 1,
        "message": "protocol transaction not found",
        "data": {
            "string_code": "PROTOCOL_TRANSACTION_UNKNOWN",
            "display_message": "...",
            "details": "Error: PROTOCOL_TRANSACTION_UNKNOWN: protocol..."
        }
    }
}

7.6 Abort

Abort an ongoing operation (Purchase or Cancel):

{
    "jsonrpc": "2.0",
    "method": "Abort",
    "id": "pos-1",
    "params": {
        "api_key": "e086554a-9a35-4bbf-9f99-a8a7cd7d1dcc",

        // Optional field to disable cardholder abort notification if set to
        // true.
        "silent": false
    }
}

If an operation is ongoing, responds when the operation has been successfully terminated (POS can issue a new operation right after Abort response arrives):

{
    "id": "pos-1",
    "jsonrpc": "2.0",
    "result": {
        // no specific results
    }
}

If no operation is active but a card is in the card reader, PT will prompt for card removal and respond with the following when card has been removed:

{
    "id": "pos-1",
    "jsonrpc": "2.0",
    "result": {
        // no specific results
    }
}

If no operation is active and no card is present a NO_ACTIVE_TRANSACTION error is sent in response:

{
    "id": "pos-1",
    "jsonrpc": "2.0",
    "error": {
        "code": 1,
        "message": "no active transaction",
        "data": {
            "string_code": "NO_ACTIVE_TRANSACTION",
            "display_message": "...",
            "details": "Error: NO_ACTIVE_TRANSACTION: no active transaction..."
        }
    }
}

Finally, some internal errors may happen (as with any request):

{
    "id": "pos-1",
    "jsonrpc": "2.0",
    "error": {
        "code": 1,
        "message": "no open transaction",
        "data": {
            "string_code": "UNKNOWN",
            "display_message": "...",
            "details": "Error: UNKNOWN: internal error..."
        }
    }
}

7.7 Check

Check the status of an on-going or already completed transaction made with this particular PT. The transaction is searched from a short transaction history maintained by the terminal (e.g. 10 entries). Amount, currency, receipt ID, and sequence ID are used to make sure the correct transaction is matched. (transaction_id is not used by Check because it is not available when a transaction is possibly pending.)

Request example:

{
    "jsonrpc": "2.0",
    "method": "Check",
    "id": "pos-1",
    "params": {
        "api_key": "e086554a-9a35-4bbf-9f99-a8a7cd7d1dcc",

        "amount": 12345,           // mandatory
        "currency": "EUR",         // mandatory
        "receipt_id": 123,         // mandatory
        "sequence_id": 234         // recommended
    }
}

If sequence number is not given, the latest transaction where other fields match is selected.

If the purchase is still active:

{
    "id": "pos-1",
    "jsonrpc": "2.0",
    "result": {
        "terminal_processing": true
    }
}

If the purchase has completed, the response is the same that a Purchase result would be, e.g. here the purchase was declined:

{
    "id": "pos-1",
    "jsonrpc": "2.0",
    "error": {
        "code": 1,
        "message": "/usr/local/lib/lua/5.1/future.lua:121: timed out...",
        "data": {
            "string_code": "UNKNOWN",
            "display_message": "...",
            "details": "Error: UNKNOWN: /usr/local/lib/lua/5.1/future.lua:121:...",

            "offline": false,
            "amount": 107,
            "currency": "EUR",
            "transaction_id": "",
            "transaction_unique_id": "8c688b80-a563-4d0f-bf73-9fb78a051d83",
            "bypass_pin": false,
            "forced_authorization": false,
            "payment_method": "credit",
            "debit_credit_certain": false,
            "response_code": "06",
            "response_text": "Declined",

            "merchant_receipt": {
                "signature_required": false,
                "id_check_required": false,
                "text": "Selite: Ei veloitusta 1,07 EUR\nKortti: \n..."
            },
            "customer_receipt": {
                "text": "Selite: Ei veloitusta 1,07 EUR\nKortti: \n..."
            }
            "merchant_number": "09388984",
            "contract_valid_for_schemes": [
              "VI",
              "MC"
            ]
        }
    }
}

If the purchase cannot be located at all, the error code TRANSACTION_NOT_FOUND is used:

{
    "id": "pos-1",
    "jsonrpc": "2.0",
    "error": {
        "code": 1,
        "message": "transaction not found",
        "data": {
            "string_code": "TRANSACTION_NOT_FOUND",
            "display_message": "...",
            "details": "Error: TRANSACTION_NOT_FOUND: transaction not found..."
        }
    }
}

As with other requests, other errors can also occur.

7.8 Print

Request the terminal to print the given bitmap. The maximum size of bitmap is given in TerminalInfo response (e.g. width 385, height 1000).

Longer prints may be done with several calls, with eject set to false on all but last fragment.

Request example:

{
    "jsonrpc": "2.0",
    "method": "Print",
    "id": "pos-1",
    "params": {
        "api_key": "e086554a-9a35-4bbf-9f99-a8a7cd7d1dcc",

        // Feed paper after printing image.
        // Optional, default = true.
        "eject" : true,

        // Image data.
        "image" : {
            // Format of data, currently "png".
            "format": "png",

            // Data as a base-64 encoded PNG bitmap.
            "data": "iVBORw0KGgoAAAANSUhEUgAAAW0AAADICAIAAACsxSecAAAACXBIWXMAAA\
            sTAAALEwEAmpwYAAAAB3RJTUUH3woPBx8Rn8o0RAAAABl0RVh0Q29tbWVudABDcmVhd\
            GVkIHdpdGggR0lNUFeBDhcAAAHUSURBVHja7dSxDQAgDAPBhP13NiOkQEJC3NWuXHwV\
            AAAAAAAAAAAAAAAAAAAA8KoeF0ncBF9noodQLB8Bh3QE0BFARwAdAXQEQEcAHQF0BNA\
            RAB0BdATQEUBHAHQE0BFARwAdAdARQEcAHQF0BEBHAB0BdATQEQAdAXQE0BFARwB0BN\
            ARQEcAHQHQEUBHAB0BdARARwAdAXQE0BEAHQF0BNARQEcAdATQEUBHAB0B0BFARwAdA\
            XQEQEcAHQF0BNARAB0BdATQEUBHAHQE0BFARwAdAdARQEcAHQF0BEBHAB0BdATQEQAd\
            AXQE0BFARwB0BNARQEcAHQHQEUBHAB0BdARARwAdAXQE0BEAHQF0BNARQEcAdATQEUB\
            HAB0B0BFARwAdAXQEQEcAHQF0BNARAB0BdATQEUBHAHQE0BFARwAdAdARQEcAHQF0BE\
            BHAB0BdATQEQAdAXQE0BFARwB0BNARQEcAHQHQEUBHAB0BdATQEQAdAXQE0BFARwB0B\
            NARQEcAHQHQEUBHAB0BdARARwAdAXQE0BEAHQF0BNARQEcAdATQEUBHAB0B0BFARwAd\
            AXQEQEcAHQF0BNARAB0BdAQAAAAAAAAAAAAAAAAAAAAAuGMDAjUEVLwTKcgAAAAASUV\
            ORK5CYII="
        }
    }
}

Success response: print job has been successfully queued.

{
    "response_to": "Print",
    "jsonrpc": "2.0",
    "id": "pos-1",
    "result": {
        "result": true
    }
}

7.9 DisplayScreen

Request the terminal to display a screen. Abort method can be used to dismiss the screen. For generic screens see "Screen display with DisplayScreen method".

{
    "jsonrpc": "2.0",
    "method": "DisplayScreen",
    "id": "pos-1",
    "params": {
        "api_key": "e086554a-9a35-4bbf-9f99-a8a7cd7d1dcc",
        "cashier_language": "en",

        // Arguments for the screen.
        "scr_args": {
            // Mandatory screen ID, must be white listed in payment
            // terminal parameters to be shown.
            "scr_id" : "my_screen"

            // Additional screen specific arguments may be included.
        }
    }
}

Success response: Input screen successfully displayed, user chose the "accept" action and a screen related value (e.g. entry field) was "111":

{
    "response_to": "DisplayScreen",
    "jsonrpc": "2.0",
    "id": "pos-1",
    "result": {
        "action": "accept",
        "value": "111"
    }
}

Success response: Input screen successfully displayed and user canceled:

{
    "response_to": "DisplayScreen",
    "jsonrpc": "2.0",
    "id": "pos-1",
    "result": {
        "action": "cancel"
    }
}

Error response: screen not white listed, BAD_CONFIG error is returned (also other errors possible):

{
    "id": "pos-1",
    "jsonrpc": "2.0",
    "error": {
        "code": 1,
        "message": "scr_id not white listed",
        "data": {
            "string_code": "BAD_CONFIG",
            "display_message": "...",
            "details": "Error: BAD_CONFIG: scr_id not white listed..."
        }
    }
}

DisplayScreen is generally not available during transaction processing. DisplayScreen is, however, allowed during CardInfo and AppInfo requests to, for example, request user input. It is also possible to display notifications without waiting for DisplayScreen response. In this case a screen later displayed by the terminal will cause the previous DisplayScreen to fail with a "screen replaced" error.

For example:

{
    "jsonrpc": "2.0",
    "method": "DisplayScreen",
    "id": "pos-1",
    "params": {
        "api_key": "e086554a-9a35-4bbf-9f99-a8a7cd7d1dcc",
        "cashier_language": "en",
        "scr_id": "generic_info",
        "scr_args": {
          "text1": "Customer identified.",
          "text2": "Payment with",
          "text3": "loyalty discount"
        },
        // Minimum time (sec) before screen may be replaced with another screen.
        "min_time": 2
    }
}

7.10 Beep

Request terminal to beep using the default "attention" beep. Beep capabilities are terminal dependent, for example on SPm20 the default attention beep is (currently) a series of three short beeps (beep-beep-beep). There's no control over the beep frequency, duration, or volume yet.

Request example:

{
    "jsonrpc": "2.0",
    "method": "Beep",
    "id": "pos-1",
    "params": {
        "api_key": "e086554a-9a35-4bbf-9f99-a8a7cd7d1dcc"
    }
}

Success response:

{
    "response_to": "Beep",
    "jsonrpc": "2.0",
    "id": "pos-1",
    "result": {
        // no result fields
    }
}

7.11 Reboot

Request terminal to reboot as soon as possible, with optional reason string. The method responds immediately, but the reboot may be delayed e.g. if a Purchase is in progress.

Request example:

{
    "jsonrpc": "2.0",
    "method": "Reboot",
    "id": "pos-1",
    "params": {
        "api_key": "e086554a-9a35-4bbf-9f99-a8a7cd7d1dcc",

        // Optional reason.
        "reason": "POS force reboot button clicked"
    }
}

Success response:

{
    "response_to": "Reboot",
    "jsonrpc": "2.0",
    "id": "pos-1",
    "result": {
        // no result fields
    }
}

7.12 NetworkStart

Indicate that POS supports network connection proxying and requests network connections to be handled via the POS. Connection proxying can be stopped using NetworkStop and is automatically stopped if the JSONPOS connection is lost.

API key is not required.

Request example:

{
    "jsonrpc": "2.0",
    "method": "NetworkStart",
    "id": "pos-1",
    "params": {
        // no parameters
    }
}

Success response:

{
    "response_to": "NetworkStart",
    "jsonrpc": "2.0",
    "id": "pos-1",
    "result": {
        // no result fields
    }
}

7.13 NetworkStop

Indicate that previously started network proxying should stop. This won't affect ongoing connections opened if network proxying was previously allowed. Network proxying stops automatically if the JSONPOS connection is lost. This method is typically not needed in practice.

API key is not required.

Request example:

{
    "jsonrpc": "2.0",
    "method": "NetworkStop",
    "id": "pos-1",
    "params": {
        // no parameters
    }
}

Success response:

{
    "response_to": "NetworkStop",
    "jsonrpc": "2.0",
    "id": "pos-1",
    "result": {
        // no result fields
    }
}

7.14 NetworkDisconnected

Sent by POS to indicate that a connection has been disconnected, either by an explicit request from PT or because a remote peer closed the connection.

The POS must not send a NetworkDisconnected if there's pending data not yet transferred to the terminal. Data may easily queue up due to rate limiting of an RFCOMM connection, and sending a NetworkDisconnected too early causes some data delivered later to be ignored by the terminal.

To ensure terminal state related to a connection is robustly released, the POS is allowed to send this method multiple times. The terminal may either ignore a duplicate NetworkDisconnected or send back an error indicating the connection ID is no longer recognized (which the POS must then ignore).

API key is not required.

Request example:

{
    "jsonrpc": "2.0",
    "method": "NetworkDisconnected",
    "id": "pos-1",
    "params": {
        "connection_id": 12,

        // Optional reason, e.g. "requested by pt",
        // "remote peer closed connection", etc.
        "reason": "free form reason"
    }
}

Success response:

{
    "response_to": "NetworkDisconnected",
    "jsonrpc": "2.0",
    "id": "pos-1",
    "result": {
        // no result fields
    }
}

7.15 Data

Plain TCP data received from the Internet for a certain logical connection. The data property contains base-64 encoded data. The base-64 format has no spaces or newlines, but does have padding characters (=).

Recommended maximum data size per request is 1024 raw bytes which expands to about 1.4kB of JSONPOS data (size limit must not be assumed by receiver). Sending of Data notifys must be rate limited when using RFCOMM to ensure that e.g. _Keepalive monitoring won't be disturbed. Fairness across connections must be guaranteed e.g. by sending data for active connections in a round-robin fashion.

If the connection ID is unknown the Data notify must be ignored. If the connection ID is known but the connection state doesn't allow data traffic at present (e.g. connection is not yet ready) the data notify should be dropped and the proxied connection should be aborted (if not already disconnected).

To minimize message size:

  • The Data method is a notification (not a request). The method name is short on purpose (Data instead of NetworkData).

  • The connection_id field is named id for Data.

  • API key is not required for Data.

Request (notify) example:

{
    "method": "Data",
    "jsonrpc": "2.0",
    "params": {
        "id": 12,
        "data": "<base64 bytes>"
    }
}

The terminal is required to process Data notifys in sequence to ensure they're processed in the order POS sends them.

7.16 Test

Run a development-time test identified by test_id. Individual tests may be added and removed without notice. Tests are documented separately.

{
    "jsonrpc": "2.0",
    "method": "Test",
    "id": "pos-1",
    "params": {
        "api_key": "e086554a-9a35-4bbf-9f99-a8a7cd7d1dcc",

        // Test identifier is mandatory, other parameters may be required
        // depending on the test.
        "test_id": "large_file_download"
    }
}

Success response:

{
    "response_to": "Test",
    "jsonrpc": "2.0",
    "id": "pos-1",
    "result": {
        // Result fields vary.
    }
}

8 Methods provided by POS

8.1 Reconnect

Sent when the terminal wants to close the JSONPOS connection. When received, POS should finish pending operation(s) such as purchases or refunds, not initiate new requests, and close the connection as soon as possible. POS can then initiate a new connection. An optional reason field indicates a diagnostic reason which can be logged but must not be used programmatically.

If POS doesn't implement the method, an error must be sent back. The terminal will then close the connection using a best effort approach with no specific guarantees, e.g. when no operations are active.

Example:

{
    "jsonrpc": "2.0",
    "method": "Reconnect",
    "id": "pos-1",
    "params": {
        "reason": "installing updates"
    }
}

8.2 CardInfo

Message sent to provide card info available after card insert/swipe/tap before cardholder application selection. Sent if stop_on_card_info flag was enabled for purchase.

{
    "jsonrpc": "2.0",
    "method": "CardInfo",
    "id": "pos-1",
    "params": {
        // POS identifiers.
        "receipt_id": 123,   // mandatory, same as in Purchase message
        "sequence_id": 234,  // optional, same as in Purchase message

        // Card info.
        "card_reading_method": "chip",

        // Language used to display cardholder screens
        "cardholder_language": "es",

        // Non-payment card data, such as magnetic stripe data
        // for non-payment cards, or detected loyalty data.
        "non_payment_data": [], // list of entries, see chapter below

        // If loyalty enabled: type of loyalty program detected on
        // card (or missing if not loyalty detected):
        "loyalty_type": "s_etu",

        // If loyalty enabled: loyalty program identifier (if any),
        // format depends on loyalty program.
        "loyalty_id": "12345",

        // For chip cards: If token generation for all applications enabled,
        // contains masked PAN and token information for each application,
        // otherwise not present.
        // For magstripe and contactless: If token generation enabled,
        // masked PAN and token information of the selected contactless application or
        // the magstripe PAN token.
        "card_applications": [
          {
            "pan_masked_for_clerk": "527591**3684",
            "pan_masked_for_customer": "**3684",
            "lookup_tokens": ["PIT1234567890123456789", "PIT1644567490123456989"],
            "store_token": "PIT7644567890123456788"

            // In case tokenization has failed.
            "tokenization_error_code": "INTERNAL_ERROR",
            "tokenization_error_description": "Error: INTERNAL_ERROR: Token...",
            "tokenization_error_details": "Error: INTERNAL_ERROR: Token..."
          },
          {
            "pan_masked_for_clerk": "527591**8721",
            "pan_masked_for_customer": "**8721",
            "lookup_tokens": ["PIT5432167890123456789", "PIT4567567490123456989"],
            "store_token": "PIT9874567890123456788"

            // In case tokenization has failed.
            "tokenization_error_code": "INTERNAL_ERROR",
            "tokenization_error_description": "Error: INTERNAL_ERROR: Token...",
            "tokenization_error_details": "Error: INTERNAL_ERROR: Token..."
          }
        ]
    }
}

Response:

{
    "response_to": "CardInfo",
    "jsonrpc": "2.0",
    "id": "pos-1",
    "result": {
        // Mandatory action:
        //  - "continue": continue the purchase
        //  - "change_amount": change the amount for this purchase
        //  - "end": stop the purchase
        // Error response, invalid response, or timeout handled
        // as 'end'.
        "action": "continue",

        // Updated amount and currency, present if and only if
        // action is "change_amount".  "cashback_amount" is optional
        // and may not exceed amount.
        "amount": 12345,
        "cashback_amount": 500,
        "currency": "EUR"
    }
}

8.3 AppInfo

Message sent to provide card info available after application selection. Send if stop_on_loyalty flag was enabled for purchase, and a loyalty program was detected or if stop_on_appinfo flag was enabled.

{
    "jsonrpc": "2.0",
    "method": "AppInfo",
    "id": "pos-1",
    "params": {
        // POS identifiers.
        "receipt_id": 123,   // mandatory, same as in Purchase message
        "sequence_id": 234,  // optional, same as in Purchase message

        // Card info.
        "card_reading_method": "chip",

        // Language used to display cardholder screens
        "cardholder_language": "es",

        // Application info (see Purchase for details).
        "pan_masked_for_clerk": "527591**3684",
        "pan_masked_for_customer": "*3684",
        "card_name": "Debit MasterCard",

        // Non-payment card data, such as magnetic stripe data
        // for non-payment cards, or detected loyalty data.
        "non_payment_data": [], // list of entries, see chapter below

        // If loyalty enabled: type of loyalty program detected on
        // card (or missing if not loyalty detected):
        "loyalty_type": "s_etu",

        // If loyalty enabled: loyalty program identifier (if any),
        // format depends on loyalty program.
        "loyalty_id": "12345",

        // If tokens enabled: token information, see Purchase.
        "store_token": "PIT1234567890123456789",
        "lookup_tokens": ["PIT1234567890123456789", "PIT1644567490123456989"],

        // In case tokenization has failed.
        "tokenization_error_code": "INTERNAL_ERROR",
        "tokenization_error_description": "Error: INTERNAL_ERROR: Token...",
        "tokenization_error_details": "Error: INTERNAL_ERROR: Token..."
    }
}

Response:

{
    "response_to": "AppInfo",
    "jsonrpc": "2.0",
    "id": "pos-1",
    "result": {
        // Mandatory action:
        //  - "continue": continue the purchase
        //  - "change_amount": change the amount for this purchase
        //  - "end": stop the purchase
        // Error response, invalid response, or timeout handled
        // as 'end'.
        "action": "continue",

        // Updated amount and currency, present if and only if
        // action is "change_amount".  "cashback_amount" is optional
        // and may not exceed amount.
        "amount": 12345,
        "cashback_amount": 500,
        "currency": "EUR"
    }
}

8.4 PosMessage

A PosMessage notification is sent by PT as the purchase sequence proceeds so that POS (sales person) can easily see what action is expected of the user. The notification contains a human readable message which can require e.g. a card to be inserted. The message is intended for a salesperson. Ignore in unattended environments.

PosMessage is human readable text only. The specific message may change, use different languages, and MUST NOT be parsed programmatically e.g. to detect transaction state as any such logic would be fragile.

Request (notify) example:

{
    "method": "PosMessage",
    "jsonrpc": "2.0",
    "params": {
        "message": "Laita/ved\u00e4 kortti"
    }
}

Note that the request has no id field because this is a notification message which needs no response.

8.5 StatusEvent

A notification about status changes in the terminal. Message is delivered when value for some status field changes. All status transitions are NOT guaranteed to generate a new notification.

Unless explicitly mentioned, status keys/values may change in terminal versions. All POS integration to status fields should be soft in nature, i.e. tolerate missing fields and changes in field types or values.

{
    "method": "StatusEvent",
    "jsonrpc": "2.0",
    "params": {
        // Indicates whether PT is ready for starting a transaction.
        "ready_for_transaction": true,

        // Indicates whether PSP connection is available or not.
        // The value gets updated with some delay when there is
        // a network outage or a service break.
        "psp_connection_available": true,

        // Status of an ongoing transaction.
        "transaction_status": "PROCESSING",

        // Update status fields.  Update status is one of: CHECKING,
        // DOWNLOADING, or PENDING; progress (percentage) and ETA (seconds)
        // fields are valid in DOWNLOADING state.  If no update check is in
        // progress, the fields are absent.
        "update_status": "DOWNLOADING",
        "update_progress": 63,
        "update_eta": 203.71,

        // Battery info (currently for SPm20).
        "battery_percentage": 94,
        "battery_charging": true,
        "plugged_in": true

        // Terminal may add arbitrary additional keys in future
        // versions.
    }
}

Field transaction_status is available if there is an on-going transaction. Currently possible values are:

Status Description
PROCESSING Terminal is processing the transaction. For example, chip communication or authorization is going on.
WAIT_CARD_IN Terminal is waiting for cardholder to present a card.
WAIT_CARD_OUT Terminal is waiting for cardholder to remove the card from the terminal.
WAIT_POS Terminal is waiting for a POS response, e.g. to a CardInfo or an AppInfo request.

Other transaction_status values may be added in the future. The POS should treat any unrecognized new codes the same as PROCESSING. This status field may be used for example user guidance but must not be used for determining if transaction has been completed (use Purchase or Check response instead).

8.6 NetworkConnect

Request for a new plain TCP connection using a hostname and a port.

Terminal provides a numeric connection ID which associates all messages and notifys related to the connection with one another. The connection ID is an arbitrary (not necessarily sequential) number; the POS should make no assumptions about the connection ID except that it is currently guaranteed to be an unsigned 32-bit value. The terminal may reuse a connection number once all state related to it has been cleared.

Because the client chooses the connection ID, the client can send a NetworkDisconnect to abort a pending connection attempt before a NetworkConnect reply has been sent. The POS must reject with error any network related method calls for a connection ID it has no state for.

The POS must impose a reasonable timeout for the connection attempt (e.g. 10 seconds) so that a NetworkConnect eventually gets a response. The terminal drives retries on its own, so it's enough for the POS to try the connection once using e.g. a native socket connect() call.

There may be multiple active connections at the same time. The connections are identified and kept separate using the connection ID.

NOTE: All established connections must be released if the underlying JSONRPC transport is closed for any reason. This includes _Sync requests.

Request example:

{
    "method": "NetworkConnect",
    "jsonrpc": "2.0",
    "id": "pt-1",
    "params": {
        "connection_id": 12,
        "host": "mpp-syte-mvj.dev.poplatek.fi",
        "port": 443
    }
}

Success response:

{
    "response_to": "NetworkConnect",
    "jsonrpc": "2.0",
    "id": "pt-1",
    "result": {
        "api_key": "e086554a-9a35-4bbf-9f99-a8a7cd7d1dcc"
    }
}

If the connection ID is already in use an error must be returned. Connection errors (timeouts, etc) are indicated as errors (no specific error codes are in use at present). Connection error descriptions should be as descriptive as possible to help diagnosis.

8.7 NetworkDisconnect

Request for an existing connection to be closed. Can also be sent while a NetworkConnect is pending and should abort the connection attempt in a reasonable time (not necessarily immediately).

The terminal may send extra NetworkDisconnect requests to ensure the remote state of a connection has been cleaned up. If the connection ID related to such a request is no longer recognized by the POS, an error should be returned (the terminal will ignore the error).

Request example:

{
    "method": "NetworkDisconnect",
    "jsonrpc": "2.0",
    "id": "pt-1",
    "params": {
        "connection_id": 12,
        "reason": "terminal rebooting"  // optional reason
    }
}

Success response:

{
    "response_to": "NetworkDisconnect",
    "jsonrpc": "2.0",
    "id": "pt-1",
    "result": {
        "api_key": "e086554a-9a35-4bbf-9f99-a8a7cd7d1dcc"
    }
}

Note that POS must send a NetworkDisconnected always when the connection is closed, regardless of whether the disconnection is requested by the remote peer (TCP FIN) or by the terminal using NetworkDisconnect.

8.8 Data

Plain TCP data to be sent to the Internet for a certain logical connection. The data property contains base-64 encoded data. The base-64 format has no spaces or newlines, but does have padding characters (=).

Recommended maximum data size per request is 1024 raw bytes which expands to about 1.4kB of JSONPOS data (size limit must not be assumed by receiver). Sending of Data notifys must be rate limited when using RFCOMM to ensure that e.g. _Keepalive monitoring won't be disturbed. Fairness across connections must be guaranteed e.g. by sending data for active connections in a round-robin fashion.

Request (notify) example:

{
    "method": "Data",
    "jsonrpc": "2.0",
    "params": {
        "id": 12,
        "data": "<base64 bytes>"
    }
}

POS is required to process Data notifys in sequence to ensure they're sent out in the order the terminal sends them.

9 Screen display with DisplayScreen method

DisplayScreen allows displaying specific screens on terminal. Examples below describe the available generic screens. JSONPOS access to screens must be enabled in the terminal configuration.

9.1 Display text ("generic_info")

Displays up to 4 lines of text on terminal screen.

Sample request:

{
    "jsonrpc": "2.0",
    "method": "DisplayScreen",
    "id": "pos-1",
    "params": {
        "api_key": "e086554a-9a35-4bbf-9f99-a8a7cd7d1dcc",
        "scr_args": {
            "scr_id": "generic_info",
            "scr_args": {
                "text1": "A quick brown",
                "text2": "fox jumped",
                "text3": "over lazy dog."
            }
        }
    }
}

Info screen is dismissed with JSONPOS Abort method.

9.2 Display a menu ("generic_menu")

Allows user to select from up to 9 menu items.

Sample request:

{
    "jsonrpc": "2.0",
    "method": "DisplayScreen",
    "id": "pos-1",
    "params": {
        "api_key": "e086554a-9a35-4bbf-9f99-a8a7cd7d1dcc",
        "scr_args": {
            "scr_id": "generic_menu",
            "scr_args": {
                "item1_enabled": 1,
                "item2_enabled": 1,
                "item3_enabled": 1,
                "item1_text": "Apple",
                "item2_text": "Orange",
                "item3_text": "Banana"
            }
        }
    }
}

Sample response with item2 selected:

{
    "response_to": "DisplayScreen",
    "jsonrpc": "2.0",
    "id": "pos-1",
    "result": {
        "action": "item2",
        "value": "item2"
    }
}

Sample response with user canceling:

{
    "response_to": "DisplayScreen",
    "jsonrpc": "2.0",
    "id": "pos-1",
    "result": {
        "action": "cancel"
    }
}

9.3 Display an amount request ("generic_enter_sum")

Sample request:

{
    "jsonrpc": "2.0",
    "method": "DisplayScreen",
    "params": {
        "api_key": "e086554a-9a35-4bbf-9f99-a8a7cd7d1dcc",
        "cashier_language": "it",
        "scr_args": {
            "scr_id": "generic_enter_sum",
            "scr_args": {
                "currency": "EUR"
            }
        }
    }
}

Sample response with amount 133 entered:

{
    "response_to": "DisplayScreen",
    "jsonrpc": "2.0",
    "id": "id-1518602294-2441644",
    "result": {
        "value": "133",
        "action": "accept"
    }
}

Sample response with user canceling:

{
    "response_to": "DisplayScreen",
    "jsonrpc": "2.0",
    "id": "id-1518602276-1779674",
    "result": {
        "value": "",
        "action": "cancel"
    }
}

Supported currencies vary per device:

  • SPm20 supports a full set of currencies (starting from version 18.4.0)

  • Other devices support only EUR (up to version 18.3.0) or EUR, GBP, SEK, NOK, DKK (starting from version 18.4.0)

9.4 Request clerk number ("generic_clerk_id_entry")

Allows to request clerk number from user.

Sample request:

{
    "jsonrpc": "2.0",
    "method": "DisplayScreen",
    "id": "pos-1",
    "params": {
        "api_key": "e086554a-9a35-4bbf-9f99-a8a7cd7d1dcc",
        "scr_args": {
            "scr_id": "generic_clerk_id_entry",
            "scr_args": {}
        }
    }
}

Sample response after user has accepted input:

{
    "response_to": "DisplayScreen",
    "jsonrpc": "2.0",
    "id": "pos-1",
    "result": {
        "action": "accept",
        "value": "123456"
    }
}

Sample response after user has canceled input:

{
    "response_to": "DisplayScreen",
    "jsonrpc": "2.0",
    "id": "pos-1",
    "result": {
        "action": "cancel",
        "value": ""
    }
}

9.5 Request user selection ("generic_select")

Allows to request user to select from up to 3 alternatives. Screen includes image that is invisible by default and should be replaced by an image preloaded to Poplatek backend.

Sample request:

{
    "jsonrpc": "2.0",
    "method": "DisplayScreen",
    "id": "pos-1",
    "params": {
        "api_key": "e086554a-9a35-4bbf-9f99-a8a7cd7d1dcc",
        "scr_args": {
            "scr_id": "generic_select",
            "scr_args": {
                "image_replacements": {
                    "image": "generic_select_smileys"
                }
            }
        }
    }
}

Sample response after user has selected leftmost function key:

{
    "response_to": "DisplayScreen",
    "jsonrpc": "2.0",
    "id": "pos-1",
    "result": {
        "action": "choice1"
    }
}

9.6 Request user to input an integer ("generic_3_digit_input")

Allows to request user to input an integer. All terminals accept at lest 3 digits but the number of digits is not necessarily limited to 3 digits. Screen includes a title that can be set in screen parameters.

Sample request:

{
    "jsonrpc": "2.0",
    "method": "DisplayScreen",
    "id": "pos-1",
    "params": {
        "api_key": "e086554a-9a35-4bbf-9f99-a8a7cd7d1dcc",
        "scr_args": {
            "scr_id": "generic_3_digit_input",
            "scr_args": {
                "title": "Table number"
            }
        }
    }
}

Sample response after user has accepted the input with green key:

{
    "response_to": "DisplayScreen",
    "jsonrpc": "2.0",
    "id": "pos-1",
    "result": {
        "action": "accept",
        "value": "123"
    }
}

10 Non-payment-data

Non-payment-data is given out on CardInfo and AppInfo method requests, if requested by the terminal. The information given is dependent on the terminal configuration and card used.

Non-payment-data contains a list of data elements, each of which contains information read from the card. These can be e.g. raw magstripe tracks, MIFARE tags, or information related to detected loyalty programs. As an example, raw magstripe data may be given on whitelisted non-payment cards, to enable POS applications to use these from their own purposes.

The following table illustrates some supported non-payment data examples:

Description Example
Raw magstripe track 1
{
    "type": "raw",
    "source": "track1",
    "track_data": "..."
}
Raw magstripe track 2
{
    "type": "raw",
    "source": "track2",
    "track_data": "..."
}
Raw magstripe track 3
{
    "type": "raw",
    "source": "track3",
    "track_data": "..."
}
MIFARE tag
{
    "type": "uid",
    "source": "mifare",
    "uid": "804d153a3d6404"
}
Loyalty from chip
{
    "loyalty_type": "finnair_plus",
    "member_id": "608589925",
    "source": "chip",
    "cardholder_level": "04",
    "partner_id": "00",
    "aid": "A0000000041010",
    "type": "loyalty"
}
Loyalty from magnetic stripe
{
    "loyalty_type": "finnair_plus",
    "member_id": "608589925",
    "source": "track2",
    "cardholder_level": "004",
    "partner_id": "110501",
    "type": "loyalty"
}

Device specific limitations:

  • SPm20 does not support reading of track 3 (hardware limitation).

11 Network proxy

11.1 Overview

Some terminal types don't have TCP/IP connectivity of their own and may only have (for example) an RFCOMM serial interface JSONPOS allows such terminals to get Internet access via the JSONPOS network proxy feature. The related methods are described above, with basic method usage as follows:

  • If RFCOMM or USB serial is used, the POS uses _Sync to bring the JSONPOS connection into synchronized state.

  • The POS sends NetworkStart to enable connection proxying.

  • When the terminal needs a new connection, it sends out NetworkConnect.

  • If the connection is successful, the terminal and the POS exchange connection related data segments using overhead minimized Data notifications. Data transfer must be rated limited.

  • The terminal may request the connection to be closed using NetworkDisconnect. The connection may also be closed by the remote Internet peer.

  • When a disconnect is pending, the POS delivers all pending data to the terminal and then sends a NetworkDisconnected request to indicate the connection is finished from the POS point of view.

Each connection is identified using a numeric connection ID. Multiple connections may exist in various states simultaneously. If the JSONPOS connection is lost (which includes a _Sync based resynchronization) all network connections related to the lost connection should be closed by both ends automatically.

See the individual method descriptions for details.

11.2 Rate limiting

Because RFCOMM links have a low throughput, data transfers must be rate limited to ensure robust operation of the JSONPOS connection. If rate limiting is not done, it's possible for keepalive requests fail due to data messages consuming all RFCOMM bandwidth, which affects terminal reliability.

Two rate limit algorithms are recommended:

  • Impose a write rate limit for the RFCOMM serial stream as a whole, for example 10kB/sec. This limit is device specific and necessary to ensure too much RFCOMM data is not queued up which may cause a connection to fail or to react too slowly to e.g. keepalives.

  • Impose a write rate limit for Data messages, ensuring that Data messages (for all proxied connections combined) in their encoded form use significantly less bandwidth than the RFCOMM write rate limit. For example, assuming a 1.4x expansion, which roughly accounts for JSONRPC framing and base-64 encoding of data, a good Data message limit might be 5kB/s of raw data. That would expand to about 7kB/sec of JSONRPC data, leaving 3kB/sec free for other JSONPOS requests.

Each sending peer must ensure fairness across active proxied connections so that data for each connection is sent even when one or more connections have a large transfer backlog. The easiest approach to ensure this is to use a round-robin algorithm and send rate limited data for each active connection in turn.

Without fairness guarantees it's possible for a large file download to starve the PSP server connection (which uses keepalives) causing a PSP connection drop.

11.3 Other notes

  • There's currently no specific support for half-open TCP connections.

12 Bluetooth RFCOMM

JSONPOS can be used over Bluetooth RFCOMM. Main differences to TCP:

  • Bluetooth pairing, service discovery, and RFCOMM channels are device specific.

  • The POS uses a _Sync transport-level request to initialize and resynchronize a JSONRPC transport connection.

  • Connections delimited by _Sync are considered separate logical JSONRPC connections but share the same RFCOMM link:

    • Each logical JSONRPC connection has a separate request/reply ID space. The same ID can be reused in a new connection and is unrelated to any previous connections.

    • Response messages must never be sent to a different logical JSONRPC connection. When receiving a _Sync, pending requests in previous connections must be terminated with an error, as no reply will ever be sent or received for them anymore.

  • The terminal sends frequent _Keepalive requests to the POS when using Bluetooth. Responding to _Keepalive is mandatory.

The network proxy methods are independent of Bluetooth; they can also be used with TCP, and RFCOMM can be used with and without network proxy methods.

The connection synchronization process is driven by the POS. The POS should synchronize (1) when it initially connects to the terminal, (2) when there's a framing parse error or any other reliable error indication, and (3) when _Keepalive based monitoring fails.

The (re)synchronization process should be as follows:

  • Read and consume data until no data has been received for e.g. 1-5 seconds. This flushes out any data related to previous connections.

  • Send a properly framed _Sync request with a unique "id" field, for example:

    0000003e:{"jsonrpc":"2.0","method":"_Sync","id":"sync-123","params":{}}<LF>
  • Read and parse a properly framed _Sync reply from the connection, e.g.:

    0000002c:{"jsonrpc":"2.0","id":"sync-123","reply":{}}<LF>

    An actual reply may, like usual, have additional fields like a response_to top level field and arbitrary keys in the reply object.

    If the reply parses correctly, be careful to verify that its id field matches that of the latest _Sync request rather than an old one. The id field values should be unique and can be derived e.g. from a timestamp (for example: "sync-" + Date.now() in Javascript).

  • If _Sync reply parsed correctly the JSONRPC connection is now functional. The new connection replaces any previous connections, and any pending requests sent via an older connection should be considered failed; the terminal won't be responding to them. In effect a _Sync is treated the same as a TCP disconnect followed by a reconnect.

  • If a valid _Sync reply is not received for e.g. 1-2 seconds, consider the synchronization attempt to have failed, and retry from the start.

  • NOTE: The _Sync method should only be used with RFCOMM, its behavior is unspecified for TCP.

13 USB serial

JSONPOS can be used over USB serial. Behavior is similar to RFCOMM, i.e. a _Sync handshake is required to (re)synchronize the connection state.

14 EMV transaction time and reference number from POS

This feature is enabled only in selected installations. If enabled, the POS may set transaction time and reference number to be used for the transaction. Usually these are generated by the terminal.

When enabled, following fields must be included in all Purchase and Refund methods (these fields are not listed in sections for those methods).

Field name Example Format
"transaction_time" "161012140619" "YYMMDDHHMMSS" (local time)
"reference_number" "161012123253" "YYMMDDNNNNNN"

Transaction time must by within 5 minutes of the time used by the terminal.

First six digits of the reference number must match the the same digits of transaction time. The last six digits must be between 100000-899999.

All transaction reference numbers must be unique for a single payment terminal contract.

15 Deprecated features

  • The following common fields in POS messages are not used by the terminal and were removed from the specification: timestamp, pos_id, sale_location_code, cashier_code, protocol_version. POS specific identifiers can be associated with a transaction using the external_data field.

  • The following common fields in PT messages were removed from the specification: timestamp, pt_hardware_id, pt_logical_id, pt_version, pt_revision, protocol_version.

  • The cashier_language field send by POS is only processed by specific methods (e.g. Purchase, Refund, Cancel, DisplayScreen). The field does not need to be included in any other messages.

  • The VersionInfo method has been renamed to TerminalInfo.

  • Status response details field has been deprecated. details.pt_name has been moved to TerminalInfo response name field. details.sales_location_name have been moved to TerminalInfo response field of the same name.

  • DisplayScreen generic_enter_sum_eur has been deprecated, use generic_enter_sum with currency argument set to EUR instead.

Version: 2881a646