Payment terminal TIMAPI.DLL

General Help regarding HMG, Compilation, Linking, Samples

Moderator: Rathinagiri

Georg_BA
Posts: 108
Joined: Fri Apr 07, 2017 5:31 pm
DBs Used: DBF

Re: Payment terminal TIMAPI.DLL

Post by Georg_BA »

Hello Serge
I don't know how to incorporate it into HMG.
I am open to any solution.
I was thinking about whether to solve it by switching to MiniGUI, I assume that it uses libraries of the "lib" type.
The source code in C language is available and I thought it would not be a problem to create a library of type "a".
The post has over 900 views, but I don't see any interest except for you, Edward, Daniel, Jimmy (AUGE_OHR). I've gotten used to n-questions within HMG going unanswered. I know there are many smart people here who have devoted a lot of their time to HMG, I'm just afraid they have left this forum.
Question for Grigory (Filatov), is it possible to use the supplied libraries in MiniGUI

Thanks everyone for your help.

Best regards, Georg
edk
Posts: 999
Joined: Thu Oct 16, 2014 11:35 am
Location: Poland

Re: Payment terminal TIMAPI.DLL

Post by edk »

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
EFT.png (224.82 KiB) Viewed 45556 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.
User avatar
serge_girard
Posts: 3309
Joined: Sun Nov 25, 2012 2:44 pm
DBs Used: 1 MySQL - MariaDB
2 DBF
Location: Belgium
Contact:

Re: Payment terminal TIMAPI.DLL

Post by serge_girard »

Great Job Edward!
There's nothing you can do that can't be done...
edk
Posts: 999
Joined: Thu Oct 16, 2014 11:35 am
Location: Poland

Re: Payment terminal TIMAPI.DLL

Post by edk »

Some improvements, some new events:

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 Label Display
        ROW    10
        COL    520
        WIDTH  250
        VALUE "DISPLAY"
    END Label
    
    DEFINE RICHEDITBOX EFTDisplay_
        ROW    50
        COL    520
        WIDTH  250
        HEIGHT 170
        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 Label Printer
        ROW    230
        COL    520
        WIDTH  250
        VALUE "PRINTER"
    END Label
    
    DEFINE RICHEDITBOX EFTPrinter_
        ROW    260
        COL    520
        WIDTH  250
        HEIGHT 170
        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 

cPosId := "POS1234"
cUserID := "0"
oXMLFile := objXML ( '' )
hStatus := { "CardReaderStatus" => "", "TransactionStatus" => "", "ConnectionStatus" => "", "ManagementStatus" => "", "ReceiptInformation" => "", "TerminalID" => "" }

addlog ( ">> Initialization, Connect" )
socket := TIM_init ( ipadres, ipport, nTimeOut ) 

IF !hb_InetIsSocket ( socket )
    addlog ( "Connection failed" )
    Return
ENDIF

TIM_RecvAll ( socket )

addlog ( ">> Get Feature Request" )

IF !TIM_Send ( socket, TIM_GetFeatureRequest ( nSequenceNumber ) ) 
    addlog ("Failed to send Feature Request request")
    return
ENDIF

TIM_RecvAll ( socket )

cTerminalID := hStatus [ "TerminalID" ]

msginfo ( "Next step" )

addlog ( ">> Login" )

nSequenceNumber++

IF !TIM_Send ( socket, TIM_Login ( nSequenceNumber, cPosId ) ) 
    addlog ("Failed to send login request")
    return
ENDIF

TIM_RecvAll ( socket )

msginfo ( "Next step" )

addlog ( ">> Get Application Information" )

nSequenceNumber++

IF !TIM_Send ( socket, TIM_GetApplicationInformation ( nSequenceNumber ) ) 
    addlog ("Failed to send Application Information request")
    return
ENDIF

TIM_RecvAll ( socket )

msginfo ( "Next step" )

addlog ( ">> Put System Information" )

nSequenceNumber++

IF !TIM_Send ( socket, TIM_PutSystemInformation ( nSequenceNumber ) ) 
    addlog ("Failed to send System Information")
    return
ENDIF

TIM_RecvAll ( socket )

msginfo ( "Next step" )

addlog ( ">> Activate" )

nSequenceNumber++

IF !TIM_Send ( socket, TIM_Activate ( nSequenceNumber, cUserID ) ) 
    addlog ("Failed to send Activate request")
    return
ENDIF

TIM_RecvAll ( socket )

msginfo ( "Next step" )

addlog ( ">> DeActivate" )

nSequenceNumber++

IF !TIM_Send ( socket, TIM_DeActivate ( nSequenceNumber ) ) 
    addlog ("Failed to send Deactivate request")
    return
ENDIF

TIM_RecvAll ( socket )

msginfo ( "Next step" )

addlog ( ">> Logout" )

nSequenceNumber++

IF !TIM_Send ( socket, TIM_Logout ( nSequenceNumber ) ) 
    addlog ("Failed to send logout request")
    return
ENDIF

TIM_RecvAll ( socket )

msginfo ( "Next step" )


addlog ( ">> Close" )

Main.EFTDisplay_.Value :=  ""

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_PutSystemInformation ( nSequenceNumber )
Local cSIXml := '<?xml version="1.0" encoding="UTF-8"?>' + ;
                '<sixml:Request xmlns:sixml="http://www.worldline.com/" ' + ;
                'FunctionGroup="Status" ' + ;
                'Function="SystemInformation" ' + ;
                'SequenceNumber="' + AllTrim( Str( nSequenceNumber ) ) + '">' + ;
                '<sixml:EcrData>' + ;
                '<sixml:EcrInfo EcrInfoType="EftAPI" ' + ;
                'EcrInfoName="HMG TIM API" ' + ;
                'EcrInfoManufacturerName="HMG community" ' + ;
                'EcrInfoVers="3.20.0-2513" ' + ;
                'EcrInfoArchitecture="harbour"/>' + ;
                '<sixml:EcrInfo EcrInfoType="Os" ' + ;
                'EcrInfoName="Microsoft Windows NT 6.2.9200.0" ' + ;
                'EcrInfoVers="6.2.9200.0" ' + ;
                'EcrInfoArchitecture="Win32NT"/>' + ;
                '</sixml:EcrData></sixml:Request>'
                
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_Logout ( nSequenceNumber, cPosID )
Local cSIXml := '<?xml version="1.0" encoding="UTF-8"?>' + ;
                '<sixml:Request xmlns:sixml="http://www.worldline.com/" ' + ;
                'FunctionGroup="Admin" ' + ;
                'Function="Logout" ' + ;
                'SequenceNumber="'  + AllTrim( Str( nSequenceNumber ) ) + '"/>'
RETURN cSIXml
********************************************************
FUNCTION TIM_Activate ( nSequenceNumber, cUserID )
Local cSIXml := '<?xml version="1.0" encoding="UTF-8"?>' + ;
                '<sixml:Request xmlns:sixml="http://www.worldline.com/" ' + ;
                'FunctionGroup="Admin" ' + ;
                'Function="Activate" ' + ;
                'SequenceNumber="'  + AllTrim( Str( nSequenceNumber ) ) + '">' + ;
                '<sixml:PosId>' + cPosID + '</sixml:PosId>' + ;
                '<sixml:UsrId>' + cUserID + '</sixml:UsrId></sixml:Request>'
RETURN cSIXml

********************************************************
FUNCTION TIM_DeActivate ( nSequenceNumber )
Local cSIXml := '<?xml version="1.0" encoding="UTF-8"?>' + ;
                '<sixml:Request xmlns:sixml="http://www.worldline.com/" ' + ;
                'FunctionGroup="Admin" ' + ;
                'Function="Deactivate" ' + ;
                'SequenceNumber="'  + AllTrim( Str( nSequenceNumber ) ) + '"/>'
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 := "", hResponse, nLength
Default nTimeOut := 10

IF !hb_InetIsSocket ( socket )
    addlog ( "Invalid socket" )
    Return ""
ENDIF

DO While .T.

    hResponse := TIM_isRespSIXml ( socket )
    
    nLength := hResponse [ "ResposeLength" ]
    
    IF hResponse [ "IsSIXmlHeader" ] := .F.
        cResponse += hResponse [ "ReceivedString" ]
    ENDIF

    cBuf := SPACE ( nLength )
    nBuf := hb_inetRecvAll( socket, @cBuf, Len( cBuf ) )
    cResponse += Left ( cBuf, nBuf )

    IF !Empty( cResponse ) .OR. nBuf == -1
        RETURN cResponse
    ENDIF
    
   
    IF Seconds() - nStartClock >= nTimeOut    //osiągnięto TimeOut
        addlog ( "Recv 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 { "ResposeLength" => nLength, "ReceivedString" => cResponse, "IsSIXmlHeader" => .T. }
        ENDIF
        Return { "ResposeLength" => nLength, "ReceivedString" => cResponse, "IsSIXmlHeader" => .F. }
    ENDIF
    
    IF nBuf = -1
        Return { "ResposeLength" => nLength, "ReceivedString" => cResponse, "IsSIXmlHeader" => .F. }
    ENDIF
    
    IF Seconds() - nStartClock >= nTimeOut    //osiągnięto TimeOut
        addlog ( "Magic number Timeout has been reached waiting for a response." )
        Return { "ResposeLength" => nLength, "ReceivedString" => cResponse, "IsSIXmlHeader" => .F. }
    ENDIF

ENDDO

RETURN

******************************************************************
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
IF Empty ( cXMLString )
    Return oXMLFile
ENDIF
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 )
IF oXMLFile:documentElement:selectNodes( "//sixml:CardReaderStatus" ):length > 0
    hStatus [ "CardReaderStatus" ] := oXMLFile:documentElement:selectNodes( "//sixml:CardReaderStatus" ):item(0):text
ENDIF
IF oXMLFile:documentElement:selectNodes( "//sixml:TransactionStatus" ):length > 0
    hStatus [ "TransactionStatus" ] := oXMLFile:documentElement:selectNodes( "//sixml:TransactionStatus" ):item(0):text
ENDIF
IF oXMLFile:documentElement:selectNodes( "//sixml:ConnectionStatus" ):length > 0
    hStatus [ "ConnectionStatus" ] := oXMLFile:documentElement:selectNodes( "//sixml:ConnectionStatus" ):item(0):text
ENDIF
IF oXMLFile:documentElement:selectNodes( "//sixml:ManagementStatus" ):length > 0
    hStatus [ "ManagementStatus" ] := oXMLFile:documentElement:selectNodes( "//sixml:ManagementStatus" ):item(0):text
ENDIF
IF oXMLFile:documentElement:selectNodes( "//sixml:ReceiptInformation" ):length > 0
    hStatus [ "ReceiptInformation" ] := oXMLFile:documentElement:selectNodes( "//sixml:ReceiptInformation" ):item(0):text
ENDIF
IF oXMLFile:documentElement:selectNodes( "//sixml:TerminalId" ):length > 0
    hStatus [ "TerminalID" ] := oXMLFile:documentElement:selectNodes( "//sixml:TerminalId" ):item(0):text
ENDIF
RETURN hStatus
*************************************
Procedure LogCardInfo ( oXMLFile )
Local oCardData := oXMLFile:documentElement:selectNodes( "//sixml:CardData" ):item(0):attributes
Local oOneAttribute := nil
Local oLanguage := oXMLFile:documentElement:selectNodes( "//sixml:CardData/sixml:Language" )

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
    IF oLanguage:length > 0 
        addlog ( Space ( 10 ) + "Language: " + oLanguage:item(0):text )
    ENDIF
ENDIF
RETURN
*******************
Procedure LogResponseInfo ( oXMLFile )
Local oResponse := oXMLFile:documentElement:selectNodes( "//sixml:Response" ):item(0):attributes
Local oOneAttribute := nil

IF oResponse:length > 0     
    addlog ( Space ( 5 ) + "Response:" )
    FOR EACH oOneAttribute IN oResponse
        addlog ( Space ( 10 ) + oOneAttribute:name + ": " + oOneAttribute:text )
    NEXT
ENDIF
RETURN
*******************
FUNCTION addlog( cTxt )
main.log_.AddText ( LEN( main.log_.Value) ) := cTxt + CRLF
DO events
RETURN nil
***********************************************
FUNCTION TIM_RecvAll ( socket )
Local cResp 
STATIC nCount := 1

DO WHILE .T.

    cResp := TIM_Recv ( socket, 3 /* nTimeOut */ )
    IF Empty ( cResp )
        EXIT
    ENDIF
 
    strfile ( cResp, "resp" + strzero ( nCount, 3) + ".xml" )

    oXMLFile := objXMLLoad ( oXMLFile, cResp )
    
    IF oXMLFile:documentElement:selectNodes( "//sixml:Notification" ):length > 0
        hStatus := GetStatusInfo ( oXMLFile )
        LogStatusInfo ( hStatus )
        IF hStatus [ "CardReaderStatus" ] == "CardInserted"
            //show info about card
            LogCardInfo ( oXMLFile )
        ENDIF
    ENDIF
    
    IF oXMLFile:documentElement:selectNodes( "//sixml:DisplayContent" ):length > 0
        DisplayEFTMessage ( oXMLFile )
    ENDIF

    IF oXMLFile:documentElement:selectNodes( "//sixml:Response" ):length > 0
        LogResponseInfo ( oXMLFile )
    ENDIF

    IF oXMLFile:documentElement:selectNodes( "//sixml:ReceiptHeader" ):length > 0
        PrintEFTReceipt ( oXMLFile:documentElement:selectNodes( "//sixml:ReceiptHeader" ) )
    ENDIF
    
    IF oXMLFile:documentElement:selectNodes( "//sixml:PrintData/sixml:Receipt" ):length > 0
        PrintEFTReceipt ( oXMLFile:documentElement:selectNodes( "//sixml:PrintData/sixml:Receipt" ) )
    ENDIF

    nCount ++
ENDDO
RETURN
*****************************************************************
Function PrintEFTReceipt ( oNodeList /* oXMLFile */ )
//Local oNodeList := oXMLFile:documentElement:selectNodes( "//sixml:ReceiptHeader" )
Local oOneElement
IF oNodeList:length > 0     //Print the received receipt
    main.EFTPrinter_.AddText ( LEN( main.EFTPrinter_.Value) ) := CRLF
    FOR EACH oOneElement IN oNodeList
        main.EFTPrinter_.AddText ( LEN( main.EFTPrinter_.Value) ) := oOneElement:text + CRLF
    NEXT
ENDIF
RETURN
Georg_BA
Posts: 108
Joined: Fri Apr 07, 2017 5:31 pm
DBs Used: DBF

Re: Payment terminal TIMAPI.DLL

Post by Georg_BA »

Hi Edward.

Thank you very much for your help.
Great job.
I'm still on the road today. I will test tomorrow and let you know the result.

Best regards, Georg
User avatar
danielmaximiliano
Posts: 2625
Joined: Fri Apr 09, 2010 4:53 pm
Location: Argentina
Contact:

Re: Payment terminal TIMAPI.DLL

Post by danielmaximiliano »

Hola Georg y Eduard
Recuerdo que para crear la libreria hbssl que esta en la carpeta contrib de debe tener los archivos .c fuente y .h (include)
ademas de los .dll y de esa manera crear la libreria libhbssl.
undefined reference to `HB_FUN_TA_TERMINAL_GET_TIM_API_VERSION'
se debe a que falta timapi.h que no la encuentro en ningun sitio :roll: y colocar dicho archivo a la carpeta Include de HMG
*´¨)
¸.·´¸.·*´¨) ¸.·*¨)
(¸.·´. (¸.·` *
.·`. Harbour/HMG : It's magic !
(¸.·``··*

Saludos / Regards
DaNiElMaXiMiLiAnO

Whatsapp. := +54901169026142
Telegram Name := DaNiElMaXiMiLiAnO
Georg_BA
Posts: 108
Joined: Fri Apr 07, 2017 5:31 pm
DBs Used: DBF

Re: Payment terminal TIMAPI.DLL

Post by Georg_BA »

Hello Daniel

path to the file timapi.h
\C\TimApi\Include\timapi\

Georg
Georg_BA
Posts: 108
Joined: Fri Apr 07, 2017 5:31 pm
DBs Used: DBF

Re: Payment terminal TIMAPI.DLL

Post by Georg_BA »

Hello everyone

Thanks again Edward.

My knowledge.
I tried to communicate with a real payment terminal. By default, the terminal has DHCP set for assigning an IP address. The assigned address can be displayed, but it can change after a power failure or disconnection. Communication should be done according to the terminal ID.
It does not detect a real connected payment card.
I don't know if it is necessary to submit a request for payment soon, then he should wait for the card to be inserted. I forced the example in ..TimAPI\DotNet\ExampleEcrSync\ExampleEcrSync.exe, it logs in after the payment request and waits for attachment (RC) or card insertion.

Best regards, Georg
edk
Posts: 999
Joined: Thu Oct 16, 2014 11:35 am
Location: Poland

Re: Payment terminal TIMAPI.DLL

Post by edk »

Hi.
I don't have access to a physical terminal, so I'm testing on a simulator.
I just use the client application "\TIMAPI_DotNet\DotNet\Examples\AdvancedEcr\AdvancedEcr.exe"

Here is my configuration file:
TIMTest.zip
(614 Bytes) Downloaded 521 times

When it comes to establishing a connection via Terminal ID, communication via the UDP protocol is required. Here's a screenshot of the log.
5.png
5.png (51.55 KiB) Viewed 45355 times
I do not know if such communication can be carried out from the harbor level, I will look for it on the web.
Can't force static IP on terminal?

Regarding downloading data from the card:

Here's my flow chart. first do Connect, then Login, then Activate.
1.png
1.png (218.33 KiB) Viewed 45355 times
Insert a virtual card in the simulator.
2.png
2.png (26.74 KiB) Viewed 45355 times
The terminal should send the card details and the status should change.
3.png
3.png (257.91 KiB) Viewed 45355 times
When it comes to reading data from the card, it reads them from the simulator, the virtual ones. Perhaps the physical cards do not return this data.
4.png
4.png (41.53 KiB) Viewed 45355 times
Check what logs are with the physical terminal and physical card.
edk
Posts: 999
Joined: Thu Oct 16, 2014 11:35 am
Location: Poland

Re: Payment terminal TIMAPI.DLL

Post by edk »

I think I managed to establish communication via UDP, at least it works on the terminal simulator.
Check this code and see if the physical terminal returns an ip address and port number. In the code, write the correct ID of the terminal.

Code: Select all

#include "hmg.ch"

Function Main

cTerminal_ID := '12345678'
nTimeOut := 300
cBroadcastAdress := '255.255.255.255'
nTerminal_UDP_port := 33334
nListening_UDP_port := 33335
cUDP_CRLF := hb_inetCRLF()
cUPD_RAW_Data := 'SIXml' + cUDP_CRLF + 'TerminalRequest' + cUDP_CRLF + 'TID: ' + cTerminal_ID + cUDP_CRLF + 'Connect: false' + cUDP_CRLF
cUPD_RCV_Data := Space ( 1024 )

IF ! hb_inetInit()
    MsgStop ( "Inet could not be initialized!" )
    RETURN Nil
ENDIF

//socket := hb_inetDGram( .T. )
socket := hb_inetDGramBind( nListening_UDP_port, , .T. /* lBroadcast */ )

hb_inetTimeout( socket, nTimeOut )

msgdebug ( 'hb_inetErrorCode', hb_inetErrorCode( socket ) )
//hb_inetFD( socket )

msgdebug ( 'UDP bytes sent"', hb_inetDGramSend( socket, cBroadcastAdress, nTerminal_UDP_port, cUPD_RAW_Data  ) )

msgdebug ( 'hb_inetErrorCode', hb_inetErrorCode( socket ) )

msgdebug ( 'received UDP bytes:', nRecv_bytes := hb_inetDGramRecv( socket, @cUPD_RCV_Data ), 'received UDP data:', hb_Atokens ( Left ( cUPD_RCV_Data, nRecv_bytes ), cUDP_CRLF )) 
Return
Does it return that SSL is off or on?
Post Reply