Unfortunately, I have not received any documentation from WorldLine so far. But:
- I was able to reverse engineer the algorithm to calculate the correct SIXml protocol magic number.
- listening to TCP packets I was able to extract some syntaxes of SIXml.
- I can establish a connection with a payment terminal (simulator)
- Get status feature
- login to the terminal
- read information about the application
- read information about the card inserted into the payment terminal.
I have prepared sources for communication regarding the above features.
Code: Select all
#include "hmg.ch"
Function Main
nTimeOut := 300
ipadres := '127.0.0.1'
ipport := 7784
nSequenceNumber := 0
DEFINE WINDOW main AT 0 , 0 WIDTH 800 HEIGHT 500 TITLE "ECR/POS - EFT" MAIN
DEFINE MAIN MENU
DEFINE POPUP "Test"
MENUITEM "Run test" ACTION test()
END POPUP
END MENU
DEFINE RICHEDITBOX log_
ROW 10
COL 10
WIDTH 500
HEIGHT 420
VALUE ""
TOOLTIP ""
FONTBOLD .F.
FONTITALIC .F.
FONTUNDERLINE .F.
FONTSTRIKEOUT .F.
HELPID Nil
TABSTOP .T.
VISIBLE .T.
READONLY .T.
END RICHEDITBOX
DEFINE RICHEDITBOX EFTDisplay_
ROW 10
COL 520
WIDTH 250
HEIGHT 200
VALUE ""
TOOLTIP ""
FONTNAME "Courier"
FONTSIZE 14
FONTBOLD .F.
FONTITALIC .F.
FONTUNDERLINE .F.
FONTSTRIKEOUT .F.
HELPID Nil
TABSTOP .T.
VISIBLE .T.
READONLY .T.
END RICHEDITBOX
DEFINE RICHEDITBOX EFTPrinter_
ROW 230
COL 520
WIDTH 250
HEIGHT 200
VALUE ""
TOOLTIP ""
FONTNAME "Courier"
FONTSIZE 14
FONTBOLD .F.
FONTITALIC .F.
FONTUNDERLINE .F.
FONTSTRIKEOUT .F.
HELPID Nil
TABSTOP .T.
VISIBLE .T.
READONLY .T.
END RICHEDITBOX
END WINDOW
CENTER WINDOW main
ACTIVATE WINDOW main
RETURN
Procedure test
addlog ( ">> Initialization" )
socket := TIM_init ( ipadres, ipport, nTimeOut )
IF !hb_InetIsSocket ( socket )
addlog ( "Connection failed" )
Return
ENDIF
addlog ( ">> Get Feature Request" )
IF !TIM_Send ( socket, TIM_GetFeatureRequest ( nSequenceNumber ) )
addlog ("Failed to send Feature Request request")
return
ENDIF
cResp := TIM_Recv ( socket, 10 /* nTimeOut */ )
strfile ( cResp, "resp1.xml" )
oXMLFile := objXML ( cResp )
DisplayEFTMessage ( oXMLFile )
hStatus := GetStatusInfo ( oXMLFile )
LogStatusInfo ( hStatus )
IF hStatus [ "CardReaderStatus" ] == "CardInserted"
//show info about card
LogCardInfo ( oXMLFile )
ENDIF
cTerminalID := hStatus [ "TerminalID" ]
msginfo ( "Next step" )
addlog ( ">> Login" )
cPosId := "POS1234"
nSequenceNumber++
IF !TIM_Send ( socket, TIM_Login ( nSequenceNumber, cPosId ) )
addlog ("Failed to send login request")
return
ENDIF
cResp := TIM_Recv ( socket, 3 /* nTimeOut */ )
strfile ( cResp, "resp2.xml" )
oXMLFile := objXMLLoad ( oXMLFile, cResp )
DisplayEFTMessage ( oXMLFile )
addlog ( "Result Code: " + oXMLFile:documentElement:selectNodes( "//sixml:Response" ):item(0):getAttribute( "ResultCode" ) )
msginfo ( "Next step" )
addlog ( ">> Get Application Information" )
nSequenceNumber++
IF !TIM_Send ( socket, TIM_GetApplicationInformation ( nSequenceNumber ) )
addlog ("Failed to send Application Information request")
return
ENDIF
cResp := TIM_Recv ( socket, 10 /* nTimeOut */ )
strfile ( cResp, "resp3.xml" )
oXMLFile := objXMLLoad ( oXMLFile, cResp )
DisplayEFTMessage ( oXMLFile )
hStatus := GetStatusInfo ( oXMLFile )
LogStatusInfo ( hStatus )
IF hStatus [ "CardReaderStatus" ] == "CardInserted"
//show info about card
LogCardInfo ( oXMLFile )
ENDIF
msginfo ( "Next step" )
addlog ( ">> Close" )
TIM_Close ( socket )
RETURN
**********************************************
Function TIM_init ( cIPadres, nIPport, nTimeOut )
Local socket, nTCPIPErr
Local nTry := 1, nMaxTry := 10
IF ! hb_inetInit()
addlog ( "Inet could not be initialized!" )
RETURN Nil
ENDIF
socket := hb_inetCreate()
hb_inetTimeout( socket, nTimeOut )
DO WHILE nTry <= nMaxTry
nTry ++
hb_inetConnect( cIPadres, nIPport, socket )
nTCPIPErr := hb_inetErrorCode( socket )
IF nTCPIPErr = 0
EXIT
ENDIF
Inkey(.05)
ENDDO
IF nTCPIPErr # 0
addlog ( "Connection error. Error number:" + hb_valToStr ( nTCPIPErr ) + " " + hb_valToStr( hb_inetErrorDesc( socket ) ) )
hb_inetClose( socket )
RETURN Nil
ENDIF
RETURN socket
********************************************************
FUNCTION TIM_GetFeatureRequest ( nSequenceNumber )
Local cSIXml := '<?xml version="1.0" encoding="UTF-8"?>' + ;
'<sixml:Request xmlns:sixml="http://www.worldline.com/" ' + ;
'FunctionGroup="Status" ' + ;
'Function="FeatureRequest" ' + ;
'SequenceNumber="' + AllTrim( Str( nSequenceNumber ) ) + '"/>'
RETURN cSIXml
********************************************************
FUNCTION TIM_GetApplicationInformation ( nSequenceNumber )
Local cSIXml := '<?xml version="1.0" encoding="UTF-8"?>' + ;
'<sixml:Request xmlns:sixml="http://www.worldline.com/" ' + ;
'FunctionGroup="Status" ' + ;
'Function="ApplicationInformation" ' + ;
'SequenceNumber="' + AllTrim( Str( nSequenceNumber ) ) + '"/>'
RETURN cSIXml
********************************************************
FUNCTION TIM_Login ( nSequenceNumber, cPosID )
Local cSIXml := '<?xml version="1.0" encoding="UTF-8"?>' + ;
'<sixml:Request xmlns:sixml="http://www.worldline.com/" ' + ;
'FunctionGroup="Admin" ' + ;
'Function="Login" ' + ;
'SequenceNumber="' + AllTrim( Str( nSequenceNumber ) ) + '">' + ;
'<sixml:PosId>' + cPosID + '</sixml:PosId>' + ;
'<sixml:IntegratorId>0</sixml:IntegratorId>' +;
'<sixml:ProtocolOptionList>' +;
'<sixml:ProtocolOption OptionType="sixml:ProtocolLevel">3</sixml:ProtocolOption>' +;
'<sixml:ProtocolOption OptionType="sixml:Guides">1</sixml:ProtocolOption>' +;
'<sixml:ProtocolOption OptionType="sixml:AutoCommit">0</sixml:ProtocolOption>' +;
'</sixml:ProtocolOptionList>' + ;
'<sixml:PrintOptionList>'+ ;
'<sixml:PrintOptions Recipient="Merchant" PrintFormat="Normal" PrintWidth="40" PrintFlags="0"/>' +;
'<sixml:PrintOptions Recipient="Cardholder" PrintFormat="Normal" PrintWidth="40" PrintFlags="0"/>' +;
'</sixml:PrintOptionList><sixml:ManufacturerFlags>0</sixml:ManufacturerFlags></sixml:Request>'
RETURN cSIXml
********************************************************
FUNCTION TIM_Send ( socket, cPakiet )
Local nTry := 1, nMaxTry := 3, xSIXml
IF !hb_InetIsSocket ( socket )
addlog ( "Invalid socket" )
Return .F.
ENDIF
cPakiet := TIM_Magic_numer ( cPakiet ) + cPakiet
strfile ( cPakiet, "pakiet.txt" )
DO WHILE nTry <= nMaxTry
hb_inetFD( socket )
IF hb_inetSendAll( socket, cPakiet ) == Len ( cPakiet )
RETURN .T.
ENDIF
//podejmij kolejna próbę
nTry ++
ENDDO
RETURN .F.
**************************************************************
FUNCTION TIM_Recv ( socket, nTimeOut )
Local cBuf, nBuf, nStartClock := Seconds(), cResponse := ""
Default nTimeOut := 10
IF !hb_InetIsSocket ( socket )
addlog ( "Invalid socket" )
Return ""
ENDIF
DO While .T.
nLength := TIM_isRespSIXml ( socket )
cBuf := SPACE ( nLength )
nBuf := hb_inetRecvAll( socket, @cBuf, Len( cBuf ) )
cResponse += Left ( cBuf, nBuf )
IF !Empty( cResponse )
RETURN cResponse
ENDIF
IF Seconds() - nStartClock >= nTimeOut .And. nBuf == -1 //osiągnięto TimeOut
addlog ( "Timeout has been reached waiting for a response." )
RETURN cResponse
ENDIF
ENDDO
RETURN
******************************************************************
FUNCTION TIM_Close ( socket )
hb_inetClose( socket )
hb_inetCleanup()
RETURN
**************************************************************
FUNCTION TIM_isRespSIXml ( socket, nTimeOut )
Local cBuf, nBuf, nStartClock := Seconds(), cResponse := ""
Local nSIXmlMagicLen := 9
Local cPrefix := "SIXml"
Local nTransportLayerVersion := 0x02
Local nReceiveChannel := 0x01
Local nRemainder, nInteger, nDivider := 256
Local nLength := 1024
Default nTimeOut := 3
IF !hb_InetIsSocket ( socket )
addlog ( "Invalid socket" )
Return Nil
ENDIF
DO While .T.
cBuf := " " //SPACE ( Len ( nSIXmlMagicLen ) )
nBuf := hb_inetRecvAll( socket, @cBuf, Len( cBuf ) )
cResponse += Left ( cBuf, nBuf )
IF Len ( cResponse ) == nSIXmlMagicLen
IF Left ( cResponse, Len ( cPrefix ) ) == cPrefix .And. ;
SubStr ( cResponse, Len ( cPrefix ) + 1, 1 ) == Chr ( nTransportLayerVersion ) .And. ;
Right ( cResponse, 1 ) == Chr ( nReceiveChannel ) //jest odpowiedz SIXmlMagicHeader
nInteger := Asc ( SubStr ( cResponse, Len ( cPrefix ) + 2, 1 ) )
nRemainder := Asc ( SubStr ( cResponse, Len ( cPrefix ) + 3, 1 ) )
nLength := (( nInteger * nDivider ) + nRemainder ) - 1
Return nLength
ENDIF
ENDIF
IF Seconds() - nStartClock >= nTimeOut //osiągnięto TimeOut
addlog ( "Timeout has been reached waiting for a response." )
RETURN nLength
ENDIF
ENDDO
RETURN nLength
******************************************************************
FUNCTION TIM_Magic_numer ( cPakiet )
Local cPrefix := "SIXml"
Local nTransportLayerVersion := 0x02
Local nReceiveChannel := 0x01
Local nRemainder, nInteger, nDivider := 256
Local nLength := Len ( cPakiet ) + 1
nInteger := INT ( nLength / nDivider )
nRemainder := ( nLength % nDivider )
RETURN cPrefix + Chr ( nTransportLayerVersion ) + Chr ( nInteger ) + Chr ( nRemainder ) + Chr ( nReceiveChannel )
******************************************************************************
Static Function objXML ( cXMLString )
Local oMyErr, oXMLFile := Win_OleCreateObject( "Msxml2.DOMDocument.3.0" )
oXMLFile:async := .F.
oXMLFile:setProperty("SelectionLanguage", "XSLPattern") // https://www.codemag.com/article/0102051/XSL-Patterns
Return objXMLLoad ( oXMLFile, cXMLString )
**************************************************************
Static Function objXMLLoad ( oXMLFile, cXMLString )
Local oMyErr
oXMLFile:loadXML ( cXMLString )
IF oXMLFile:parseError:errorCode != 0
oMyErr := oXMLFile:parseError
addlog ("You have error " + oMyErr:reason)
RETURN Nil
ENDIF
RETURN oXMLFile
*****************************************************************
Function DisplayEFTMessage ( oXMLFile )
Local oNodeList := oXMLFile:documentElement:selectNodes( "//sixml:DisplayLine" )
Local oOneElement
IF oNodeList:length > 0 //Show the received message
Main.EFTDisplay_.Value := ""
FOR EACH oOneElement IN oNodeList
main.EFTDisplay_.AddText ( LEN( main.EFTDisplay_.Value) ) := oOneElement:text + CRLF
NEXT
ENDIF
RETURN
***********************************
Procedure LogStatusInfo ( hStatus )
addlog ( "Card Reader Status: " + hStatus [ "CardReaderStatus" ] )
addlog ( "Transaction Status: " + hStatus [ "TransactionStatus" ] )
addlog ( "Connection Status: " + hStatus [ "ConnectionStatus" ] )
addlog ( "Management Status: " + hStatus [ "ManagementStatus" ] )
addlog ( "Receipt Information: " + hStatus [ "ReceiptInformation" ] )
addlog ( "Terminal ID: " + hStatus [ "TerminalID" ] )
RETURN Nil
***********************************
Function GetStatusInfo ( oXMLFile )
Local hStatus := { "CardReaderStatus" => oXMLFile:documentElement:selectNodes( "//sixml:CardReaderStatus" ):item(0):text, ;
"TransactionStatus" => oXMLFile:documentElement:selectNodes( "//sixml:TransactionStatus" ):item(0):text, ;
"ConnectionStatus" => oXMLFile:documentElement:selectNodes( "//sixml:ConnectionStatus" ):item(0):text, ;
"ManagementStatus" => oXMLFile:documentElement:selectNodes( "//sixml:ManagementStatus" ):item(0):text, ;
"ReceiptInformation" => oXMLFile:documentElement:selectNodes( "//sixml:ReceiptInformation" ):item(0):text, ;
"TerminalID" => oXMLFile:documentElement:selectNodes( "//sixml:TerminalId" ):item(0):text }
RETURN hStatus
*************************************
Procedure LogCardInfo ( oXMLFile )
Local oCardData := oXMLFile:documentElement:selectNodes( "//sixml:CardData" ):item(0):attributes
Local oOneAttribute := nil
IF oCardData:length > 0
addlog ( Space ( 5 ) + "Information about of inserted card:" )
FOR EACH oOneAttribute IN oCardData
addlog ( Space ( 10 ) + oOneAttribute:name + ": " + oOneAttribute:text )
NEXT
addlog ( Space ( 10 ) + "Language: " + oXMLFile:documentElement:selectNodes( "//sixml:CardData/sixml:Language" ):item(0):text )
ENDIF
RETURN
*******************
FUNCTION addlog( cTxt )
main.log_.AddText ( LEN( main.log_.Value) ) := cTxt + CRLF
DO events
RETURN nil
I saved the data sent to the terminal in raw form, I did not have time to analyze the meaning of individual variables, except for those whose interpretation of their meaning did not raise any doubts. I focused on getting the correct communication between HMG and the payment terminal simulator.

- EFT.png (224.82 KiB) Viewed 45883 times
The R.E. process is quite a tedious job, if you are interested I can send you the methodology I use, you can try to take the project further.