Multi pairing

Product Overview

Multi Pairing is a feature that will allow the operator to pair many EFTPOS terminals to one Point of Sale software to perform many transactions at any given time. In the traditional method of 1 EFTPOS terminal communicates to 1 point of sale software, multi terminal allows the points of sale to communicate to many eftpos terminals at any given time. How this will work is, each EFTPOS device can only perform a single transaction at one time, by one point of sale software but the POS has the ability to speak to another terminal and create a transaction request.

How Multi pairing could benefit the operator, is the ability to maintain all the terminals from one 'Master' account/device, and 'slave' devices processing transactions to any terminal that is connected to. If a 'slave' account is shared amongst many operators, then multiple transactions could be performed at any given time.

How does it work?

Multi pairing works by creating multiple instances of the SPI library attached to a Unique ID. The Unique ID created will store a number of values such as the serial number, device address and secrets. With these values stored under a unique ID, it will act as a 'profile' to direct a request to, and a place to store the Status and states for the specific device. When we need to create another pairing, we would need to create a second 'profile' and store all the new values under, this can be achieved by using a singleton pattern to create multiple instances of the object, but trigger a single instantiation.

Pre-requisite

  • Standard pairing and connection to the EFTPOS terminal
  • Multiple EFTPOS Terminals

Changes

In order to support Multi-terminal mode, we first need to create SpiServices as a class, using a Singleton Pattern for the instance. What this will do, is it will allow many instances of the SpiService object to be created (spiClient), but will only allow a single instantiate.

SpiService class
  
  const spiService = new SpiServices();

We export the class instance rather than the class itself.

export { spiService as default};

Instance ID

When we have created the service, the service should be affiliated with an Instance ID. The instance ID should be unique in a way that it can be easily identifiable. We highly recommend using a UUID as the Instance ID (compared to the Serial number or other values), as the UUID value is the same, has a very low possibility.

We can start the pairing process and capture the subscribed instances and values to the Instance ID created. For example:

🚧

Instance ID Example

The following example is an example and should not be used as the value. mx51 has supplied this example to assist developers and demonstrate what is required in order to maintain Multi-Pairing feature.

acquirerCode: "gko"
deviceAddress: "202-206-301.z1.dvcs.gko.mspenv.io"
id: "8a5e2dcc-bfa0-469f-95d7-eec778b08453"
posId: "TESTGKO101"
secrets: {
    EncKey: "ea3cdfba094af985142e57d5e91d8d23f8507d72dd0b4268a21445d94d651f51"
    HmacKey: "0f93186d9451f5ce3725fff62a7764f00f2bab34a3de84dedc94bd42dc241ba4"
}
serialNumber: "202-206-301"
testMode: false

🚧

Secrets

In order to maintain the pairing to a device, the secrets should be stored locally within the application or from the database. The intention is to retrieve the secrets to activate the connection between POS and terminal.

BatteryLevelChanged: (event) => spiClient._log.log('event: ', event)
Config: t
EnabledPrintMerchantCopy: false
EnabledPromptForCustomerCopyOnEftpos: false
EnabledSignatureFlowOnEftpos: false
PrintMerchantCopy: false
PromptForCustomerCopyOnEftpos: false
SignatureFlowOnEftpos: false
[[Prototype]]: Object
CurrentDeviceStatus: t
DeviceAddressResponseCode: "SUCCESS"
LastUpdated: "2022-07-19T01:41:35Z"
ResponseMessage: null
ResponseStatusDescription: ""
UseSecureWebSockets: true
fqdn: "321-433-179.z1.sandbox.apdvcs.net"
ip: null
Address: (...)
[[Prototype]]: Object
CurrentFlow: "Idle"
CurrentPairingFlowState: null
CurrentTxFlowState: null
PrintingResponse: (event) => spiClient._log.log('event: ', event)
TerminalConfigurationResponse: (event) => spiClient._log.log('event: ', event)
TerminalStatusResponse: (event) => spiClient._log.log('event: ', event)
TransactionUpdateMessage: (event) => {…}
_autoAddressResolutionEnabled: true
_checkOnTxFrequency: 20000
_conn: t
Address: "wss://321-433-179.z1.sandbox.apdvcs.net?posId=TESTVEND222"
Connected: true
SpiProtocol: "spi.2.9.0"
State: "Connected"
_conectionTimeout: 10
_connectionTimeout: null
_spi: t
BatteryLevelChanged: (event) => spiClient._log.log('event: ', event)
Config: t {PrintMerchantCopy: false, PromptForCustomerCopyOnEftpos: false, SignatureFlowOnEftpos: false, EnabledPrintMerchantCopy: false, EnabledPromptForCustomerCopyOnEftpos: false, …}
CurrentDeviceStatus: t {UseSecureWebSockets: true, ip: null, fqdn: '321-433-179.z1.sandbox.apdvcs.net', LastUpdated: '2022-07-19T01:41:35Z', DeviceAddressResponseCode: 'SUCCESS', …}
CurrentFlow: "Idle"
CurrentPairingFlowState: null
CurrentTxFlowState: null
PrintingResponse: (event) => spiClient._log.log('event: ', event)
TerminalConfigurationResponse: (event) => spiClient._log.log('event: ', event)
TerminalStatusResponse: (event) => spiClient._log.log('event: ', event)
TransactionUpdateMessage: (event) => {…}
_autoAddressResolutionEnabled: true
_checkOnTxFrequency: 20000
_conn: t {Address: 'wss://321-433-179.z1.sandbox.apdvcs.net?posId=TESTVEND222', Connected: true, State: 'Connected', SpiProtocol: 'spi.2.9.0', _spi: t, …}
_currentStatus: "PairedConnected"
_deviceApiKey: "BurgerPosDeviceAPIKey"
_eftposAddress: "wss://321-433-179.z1.sandbox.apdvcs.net"
_eventBus: EventTarget {}
_forceSecureWebSockets: true
_hasSetInfo: true
_inTestMode: true
_libraryLanguage: "js"
_log: console {debug: ƒ, error: ƒ, info: ƒ, log: ƒ, warn: ƒ, …}
_maxSecretsRetryAttempts: 4
_maxWaitForCancelTx: 10000
_missedPongsCount: 0
_missedPongsToDisconnect: 2
_mostRecentLoginResponse: null
_mostRecentPingSent: t {Id: 'ping1', EventName: 'ping', Data: null, DateTimeStamp: '2022-07-19T01:42:14.939Z', PosCounter: 18070, …}
_mostRecentPingSentTime: 1658194934940
_mostRecentPongReceived: t {Id: 'ping1', EventName: 'pong', Data: undefined, DateTimeStamp: '2022-07-19T11:42:04.979', PosCounter: '', …}
_periodicPingThread: 32
_pingFrequency: 18000
_pongTimeout: 5000
_posId: "TESTVEND222"
_posVendorId: "Vend"
_posVersion: "1.0.0"
_previousSecrets: null
_readyToTransact: null
_regexItemsForEftposAddress: /^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}(\:[0-9]{1,5})?$/
_regexItemsForFqdnEftposAddress: /^[a-zA-Z0-9\.-]+$/
_regexItemsForPosId: /^[a-zA-Z0-9]*$/
_retriesBeforePairing: 3
_retriesBeforeResolvingDeviceAddress: 3
_retriesSinceLastDeviceAddressResolution: 0
_retriesSinceLastPairing: 0
_secrets: {EncKey: 'a4ae452b6c8d9a543fdeeffbf2b35aa4d92201c18f763e641be39b2b27a23723', HmacKey: '22e3e2d0f68295d0fbb32af2501edacdad7a768b0526bb4affbf542e44ae6bc3'}
_secretsRetryAttempts: 0
_serialNumber: "321-433-179"
_sleepBeforeReconnectMs: 3000
_spiMessageStamp: t {PosId: 'TESTVEND222', Secrets: {…}, ConnId: 'C36T4565299R3823258', PosCounter: 18073, min: 100, …}
_spiceVersion: null
_tenantCode: "wbc"
_terminalModel: "VX690"
_transactionMonitoringThread: 65
_transactionReport: t {PosVendorId: 'Vend', PosVersion: '1.0.0', LibraryLanguage: 'js', LibraryVersion: '2.9.2', PosRefId: null, …}
_txMonitorCheckFrequency: 1000
CurrentStatus: (...)
[[Prototype]]: Object
_ws: WebSocket {url: 'wss://321-433-179.z1.sandbox.apdvcs.net/?posId=TESTVEND222', readyState: 1, bufferedAmount: 0, onopen: ƒ, onerror: ƒ, …}
[[Prototype]]: Object
_currentStatus: "PairedConnected"
_deviceApiKey: "BurgerPosDeviceAPIKey"
_eftposAddress: "wss://321-433-179.z1.sandbox.apdvcs.net"
_eventBus: EventTarget {}
_forceSecureWebSockets: true
_hasSetInfo: true
_inTestMode: true
_libraryLanguage: "js"
_log: console
assert: ƒ assert()
clear: ƒ clear()
context: ƒ context()
count: ƒ count()
countReset: ƒ countReset()
debug: ƒ debug()
dir: ƒ dir()
dirxml: ƒ dirxml()
error: ƒ error()
group: ƒ group()
groupCollapsed: ƒ groupCollapsed()
groupEnd: ƒ groupEnd()
info: ƒ info()
log: ƒ log()
memory: MemoryInfo {totalJSHeapSize: 10000000, usedJSHeapSize: 10000000, jsHeapSizeLimit: 3760000000}
profile: ƒ profile()
profileEnd: ƒ profileEnd()
table: ƒ table()
time: ƒ time()
timeEnd: ƒ timeEnd()
timeLog: ƒ timeLog()
timeStamp: ƒ timeStamp()
trace: ƒ trace()
warn: ƒ warn()
Symbol(Symbol.toStringTag): "Object"
[[Prototype]]: Object
_maxSecretsRetryAttempts: 4
_maxWaitForCancelTx: 10000
_missedPongsCount: 0
_missedPongsToDisconnect: 2
_mostRecentLoginResponse: null
_mostRecentPingSent: t
ConnId: ""
Data: null
DateTimeStamp: "2022-07-19T01:42:14.939Z"
DecryptedJson: "{\"message\":{\"id\":\"ping1\",\"event\":\"ping\",\"data\":null,\"datetime\":\"2022-07-19T01:42:14.939Z\",\"pos_counter\":18070,\"pos_id\":\"TESTVEND222\",\"conn_id\":\"\"}}"
EventName: "ping"
Id: "ping1"
IncommingHmac: ""
PosCounter: 18070
PosId: "TESTVEND222"
_needsEncryption: true
[[Prototype]]: Object
_mostRecentPingSentTime: 1658194934940
_mostRecentPongReceived: t
ConnId: "C36T4565299R3823258"
Data: undefined
DateTimeStamp: "2022-07-19T11:42:04.979"
DecryptedJson: "{\"message\":{\"conn_id\":\"C36T4565299R3823258\",\"datetime\":\"2022-07-19T11:42:04.979\",\"event\":\"pong\",\"id\":\"ping1\"}}"
EventName: "pong"
Id: "ping1"
IncomingHmac: "2545A09732B250A63455245BAC7D777EB99A0C3F4E43DF16CF8191DF15F8159B"
IncommingHmac: ""
PosCounter: ""
PosId: undefined
_needsEncryption: true
[[Prototype]]: Object
_periodicPingThread: 32
_pingFrequency: 18000
_pongTimeout: 5000
_posId: "TESTVEND222"
_posVendorId: "Vend"
_posVersion: "1.0.0"
_previousSecrets: null
_readyToTransact: null
_regexItemsForEftposAddress: /^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}(\:[0-9]{1,5})?$/
_regexItemsForFqdnEftposAddress: /^[a-zA-Z0-9\.-]+$/
_regexItemsForPosId: /^[a-zA-Z0-9]*$/
_retriesBeforePairing: 3
_retriesBeforeResolvingDeviceAddress: 3
_retriesSinceLastDeviceAddressResolution: 0
_retriesSinceLastPairing: 0
_secrets:
EncKey: "a4ae452b6c8d9a543fdeeffbf2b35aa4d92201c18f763e641be39b2b27a23723"
HmacKey: "22e3e2d0f68295d0fbb32af2501edacdad7a768b0526bb4affbf542e44ae6bc3"
[[Prototype]]: Object
_secretsRetryAttempts: 0
_serialNumber: "321-433-179"
_sleepBeforeReconnectMs: 3000
_spiMessageStamp: t
ConnId: "C36T4565299R3823258"
PosCounter: 18074
PosId: "TESTVEND222"
Secrets: {EncKey: 'a4ae452b6c8d9a543fdeeffbf2b35aa4d92201c18f763e641be39b2b27a23723', HmacKey: '22e3e2d0f68295d0fbb32af2501edacdad7a768b0526bb4affbf542e44ae6bc3'}
max: 99999
min: 100
[[Prototype]]: Object
_spiceVersion: null
_tenantCode: "wbc"
_terminalModel: "VX690"
_transactionMonitoringThread: 39
_transactionReport: t
CurrentFlow: null
CurrentStatus: null
CurrentTxFlowState: null
DurationMs: null
Event: null
LibraryLanguage: "js"
LibraryVersion: "2.9.2"
PosRefId: null
PosVendorId: "Vend"
PosVersion: "1.0.0"
SerialNumber: "321-433-179"
TxEndTime: null
TxResult: null
TxStartTime: null
TxType: null
[[Prototype]]: Object
_txMonitorCheckFrequency: 1000

Next Steps

When this is all stored to the same instance ID, the application can target a single instance ID/terminal to carry out an action and update the status and Spi object.