Jump to content
boris.nihil

Comunicate with POS terminal (Ingenico)

Recommended Posts

hi

i need to send some HEX to POS terminal, amount of money, and POS will then check if credit card if valid

and send me some answers..like this: (<STX><STX>message<ETX><LRC>)

 

ECR-->POS: <STX><STX>303030303030303030303031303030<FS>31303030<FS><FS>2B30<FS>313931<FS><FS><FS><FS><FS><FS><FS><FS><FS><FS><FS>3030<FS>31<FF><FS><FS><FS><FS><FS><FS><FS><FS><ETX>2C
POS-->ECR: <ACK>
POS-->ECR: <STX>32303030303030305542414349204B415254494355<ETX>2E
ECR-->POS: <ACK>

-------------------------------------------------------------------

 I try with Indy IdTCPClient1 but I dont receive nothing from POS.

Am i sending wrong code or I dont know how to receive answer ?

 

They say i need do send HEX string..

So i translate this HEX:

<STX><STX>303030303030303030303031303030<FS>31303030<FS><FS>2B30<FS>313931<FS><FS><FS><FS><FS><FS><FS><FS><FS><FS><FS>3030<FS>31<FF><FS><FS><FS><FS><FS><FS><FS><FS><ETX>2C

to

02023030303030303030303030313030301C313030301C1C2B301C3139311C1C1C1C1C1C1C1C1C1C1C30301C310C1C1C1C1C1C1C1C1C032C

and send it like

 

IdTCPClient1.IOHandler.WriteLn(edit7.Text, IndyTextEncoding_OSDefault);

timer1.Enabled := true;

 

This is how i wait for server ansver:

 

procedure TForm1.Timer1Timer(Sender: TObject);
var receivedtext:string;
begin
  with IdTCPClient1 do
  begin
    try
      if IOHandler.InputBufferIsEmpty then
      begin
        IOHandler.CheckForDataOnSource(0);
        IOHandler.CheckForDisconnect;
        if IOHandler.InputBufferIsEmpty then Exit;
      end;
      receivedtext := IOHandler.ReadLn;
    except
      Timer1.Enabled := False;
      Exit;
    end;
    if receivedtext <> '' then
      ShowMessage(receivedtext);
  end;
end;
 

 


 

Share this post


Link to post

That protocol looks more like a Serial cable protocol, than a TCP protocol?

 

What happens if you send it as binary bytes instead of text hex?
I.e. each hex value converted to a byte.

Share this post


Link to post
7 minutes ago, Lars Fosdal said:

That protocol looks more like a Serial cable protocol, than a TCP protocol?

no, it is connected to local network with fixed ip 192.168.1.23 and port 3000..

on POS screen its written:

LISTEN 192.168.1.23:3000

Share this post


Link to post
3 hours ago, boris.nihil said:

i need to send some HEX to POS terminal, amount of money, and POS will then check if credit card if valid

and send me some answers..like this: (<STX><STX>message<ETX><LRC>)

Do you need to send an actual *hex-encoded string* for the <message> (ie, '303030...', bytes $33 $30 $33 $30 $33 $30 ...), or do you need to send actual binary bytes ($30 $30 $30 ...)?  It makes a BIG difference.  Do you have documentation for this POS protocol that you can share?

3 hours ago, boris.nihil said:

I try with Indy IdTCPClient1 but I dont receive nothing from POS.

Am i sending wrong code or I dont know how to receive answer ?

Yes, you are using the wrong code.

 

On the send, you should not be using WriteLn() at all, since there is no CRLF in this protocol.  Use Write() instead.  But whether you should use Write(string) or Write(TIdBytes) depends on what you are actually expected to send.

 

On the receive, to use ReadLn() properly, you would need to specify ETX, #3, as the line terminator (or, you can use WaitFor(#3)), and then do a ReadByte() afterwards to read the checksum that follows the ETX.  Otherwise, you would need to call ReadByte() in a loop until you encounter ETX (or, you could copy the code from ReadLn()/WaitFor() and adapt it for bytes instead of strings, not that hard).

3 hours ago, boris.nihil said:

They say i need do send HEX string..

So i translate this HEX:

<STX><STX>303030303030303030303031303030<FS>31303030<FS><FS>2B30<FS>313931<FS><FS><FS><FS><FS><FS><FS><FS><FS><FS><FS>3030<FS>31<FF><FS><FS><FS><FS><FS><FS><FS><FS><ETX>2C

to

02023030303030303030303030313030301C313030301C1C2B301C3139311C1C1C1C1C1C1C1C1C1C1C30301C310C1C1C1C1C1C1C1C1C032C

That is wrong.  I have worked in the past with STX/ETX protocols and a few POS protocols, you definitely need to send the <STX>, <FS> and <ETX> as binary bytes, not as hex-encoded strings.  Try something more like this:

var s: string;
s := #2#2'303030303030303030303031303030'#28'31303030'#28#28'2B30'#28'313931'#28#28#28#28#28#28#28#28#28#28#28'3030'#28'31'#12#28#28#28#28#28#28#28#28#3'2C';
IdTCPClient1.IOHandler.Write(s, IndyTextEncoding_8bit);

Though I suspect the POS is actually expecting binary data for the message content, not hex-encoded strings, so try something more like this:

var s: string;
s := #2#2'000000000001000'#28'1000'#28#28'+0'#28'191'#28#28#28#28#28#28#28#28#28#28#28'00'#28'1'#12#28#28#28#28#28#28#28#28#3',';
IdTCPClient1.IOHandler.Write(s, IndyTextEncoding_8bit);
3 hours ago, boris.nihil said:

This is how i wait for server ansver:

That is fine, if you change the reading according to what I said above.  Though, if you send a request and are expecting an immediate reply, why use a timer at all?  Just read the reply immediately after sending the request.

Share this post


Link to post
3 hours ago, Lars Fosdal said:

That protocol looks more like a Serial cable protocol, than a TCP protocol?

There are POS systems that use serial-like protocols over TCP.

3 hours ago, Lars Fosdal said:

What happens if you send it as binary bytes instead of text hex?
I.e. each hex value converted to a byte.

That is what I'm thinking, too.

Share this post


Link to post

>>  Though I suspect the POS is actually expecting binary data for the message content, not hex-encoded strings, so try something more like this:

>>  s := #2#2'000000000001000'#28'1000'#28#28'+0'#28'191'#28#28#28#28#28#28#28#28#28#28#28'00'#28'1'#12#28#28#28#28#28#28#28#28#3',';
>>  IdTCPClient1.IOHandler.Write(s, IndyTextEncoding_8bit);

 

you were right, this works !

But now program hangs for a while waiting for some response or something..

How to receive ACK from POS terminal and other data from POS ?

I need to send ACK after every returned answer from POS..

 

ECR-->POS: <STX><STX>303030303030303030303031303030<FS>31303030<FS><FS>2B30<FS>313931<FS><FS><FS><FS><FS><FS><FS><FS><FS><FS><FS>3030<FS>31<FF><FS><FS><FS><FS><FS><FS><FS><FS><ETX>2C
POS-->ECR: <ACK>
POS-->ECR: <STX>32303030303030305542414349204B415254494355<ETX>2E
(insert CARD)

ECR-->POS: <ACK>
POS-->ECR: <STX>(CARD in process)

ECR-->POS: <ACK>
POS-->ECR: <STX>(wait)

ECR-->POS: <ACK>
POS-->ECR: <STX(aproved)

ECR-->POS: <ACK>

 

Maybe this code is not good ?

 

answer := IdTCPClient1.IOHandler.ReadLn();
showmessage(answer );

 

Here is Protocol what POS terminal use..

Untitled.png

Untitled1.png

Untitled3.png

Share this post


Link to post
15 hours ago, boris.nihil said:

you were right, this works !

But now program hangs for a while waiting for some response or something..

How to receive ACK from POS terminal and other data from POS ?

I already explained to you earlier why your use of the IOHandler's ReadLn() method is incorrect in this situation, and how to use ReadLn() (or WaitFor()) to read the POS's messages correctly. The ACK doesn't change what I said earlier. You would simply do an extra read with ACK, #6, as the line terminator instead. However, since the ACK is only 1 byte, the IOHandler's ReadByte() method would be more appropriate, especially if the POS can ever send a NAK instead of an ACK.

Quote

I need to send ACK after every returned answer from POS..

You can use the IOHandler's Write(Byte) method for that.

Quote

Maybe this code is not good ?

No, it is not sufficient by itself.

 

I am not at a computer right now to write you working code, it will have to wait until tomorrow.

 

Edited by Remy Lebeau

Share this post


Link to post

i tryed

  IdTCPClient1.IOHandler.InputBuffer.Clear;
  IdTCPClient1.IOHandler.Write(s + chr(strtoint(lrc)), IndyTextEncoding_8bit);

 

and then i activate timer:

 

   if IdTCPClient1.IOHandler.InputBufferIsEmpty then
   begin
     if IdTCPClient1.IOHandler.CheckForDataOnSource(500) then
     begin
      receivedtext1 := IdTCPClient1.IOHandler.WaitFor(#6,true,true,IndyTextEncoding_8bit);
      memo1.SelStart := memo1.GetTextLen;
      memo1.SelLength := 0;
      memo1.SelText := 'Received ACK:' + receivedtext1 + #13#10;
      sleep(500);

 

      receivedtext := IdTCPClient1.IOHandler.WaitFor(#3,true,true,IndyTextEncoding_8bit);
      memo1.SelStart := memo1.GetTextLen;
      memo1.SelLength := 0;
      memo1.SelText := 'Received message:' + receivedtext + #13#10;
     end;

 

 

Share this post


Link to post
On 2/22/2021 at 8:36 AM, boris.nihil said:

  IdTCPClient1.IOHandler.InputBuffer.Clear;

Why are you clearing the InputBuffer?  If you are reading messages correctly, you should never need to do that.

Quote

receivedtext1 := IdTCPClient1.IOHandler.WaitFor(#6,true,true,IndyTextEncoding_8bit);

As I said earlier, since the ACK/NAK is only 1 byte, you should use ReadByte() instead of WaitFor(), eg:

var
  b: Byte;

b := IdTCPClient1.IOHandler.ReadByte;
if b = $6 then // ACK
begin
  // read POS's next message...
end
else if b = $15 then // NAK
begin
  // failure of previous message you sent...
end
else begin
  // unexpected, do something...
end;

Personally, I would just get rid of the timer completely.  Following the flow chart you posted earlier is easier when using blocking I/O operations with the IOHandler's ReadTimeout property, eg:

var
  iTry: Integer;
  b, lrc: Byte;
  ReceivedText: string;

  procedure AddToMemo(const S: string);
  begin
    Memo1.SelStart := Memo1.GetTextLen;
    Memo1.SelLength := 0;
    Memo1.SelText := S + sLineBreak;
  end;

begin

  iTry := 0;
  try
    IdTCPClient1.IOHandler.DefStringEncoding := IndyTextEncoding_8Bit;
    IdTCPClient1.IOHandler.ReadTimeout := 1000;

    repeat
      IdTCPClient1.IOHandler.Write(...); // send message to POS

      try
        b := IdTCPClient1.IOHandler.ReadByte; // wait for ACK/NAK
      except
        on E: EIdReadTimeout do
        begin
          Inc(iTry);
          if iTry < 3 then
            Continue;
          raise;
        end;
      end;

      if b <> $6 then // not ACK?
      begin
        Inc(iTry);
        if iTry < 3 then
          Continue;
        raise Exception.Create('No ACK received');
      end;
      
      repeat
        // wait for message...
        ReceivedText := IdTCPClient1.IOHandler.WaitFor(#3, True, True);
        lrc := IdTCPClient1.IOHandler.ReadByte;

        AddToMemo('Received message:' + ReceivedText + Char(lrc));

        if (ReceivedText is not valid) then
        begin
          IdTCPClient1.IOHandler.Write(Byte($15)); // send NAK
          Continue; // keep waiting...
        end;

        IdTCPClient1.IOHandler.Write(Byte($6)); // send ACK

        if (ReceivedText is HOLD message) then
          Continue; // keep waiting...

        // use ReceivedText as needed...

        Break; // all done...
      until False;
    until False;
  except
    on E: Exception do
      AddToMemo('ERROR! ' + E.Message);
  end;

end;

But, if you really want to use a separate timer, you should implement a state machine, eg:

type
  TState = (stIdle, stSendingMsg, stWaitingForAck, stWaitingForMsg, stError);

private
  iTry: Integer;
  CurrentMsg: string;
  State: TState;

...

procedure TMyForm.AddToMemo(const S: string);
begin
  Memo1.SelStart := Memo1.GetTextLen;
  Memo1.SelLength := 0;
  Memo1.SelText := S + sLineBreak;
end;

procedure TMyForm.IdTCPClient1Connected(Sender: TObject);
begin
  IdTCPClient1.IOHandler.DefStringEncoding := IndyTextEncoding_8Bit;
  IdTCPClient1.IOHandler.ReadTimeout := 1000;
  State := stIdle;
end;

procedure TMyForm.DoSomething;
begin
  iTry := 0;
  CurrentMsg := ...;
  State := stSendingMsg;
  Timer1.Interval := 10;
  Timer1.Enabled := True;
end;

procedure TMyForm.Timer1Timer(Sender: TObject);
var
  b, lrc: Byte;
  ReceivedText: string;
begin
  case State of
    stSendingMsg:
    begin
      try
        IdTCPClient1.IOHandler.Write(CurrentMsg); // send message to POS
      except
        on E: Exception do
        begin
          State := stError;
          Timer1.Enabled := False;
          AddToMemo('ERROR! ' + E.Message);
          Exit;
        end;
      end;
      State := stWaitingForAck:
    end;

    stWaitingForAck:
    begin
      try
        b := IdTCPClient1.IOHandler.ReadByte; // wait for ACK/NAK
      except
        on E: Exception do
        begin
          if E is EIdReadTimeout then
          begin
            Inc(iTry);
            if iTry < 3 then
            begin
              State := stSendingMsg;
              Exit;
            end;
          end;
          State := stError;
          Timer1.Enabled := False;
          AddToMemo('ERROR! ' + E.Message);
          Exit;
        end;
      end;

      if b <> $6 then // not ACK?
      begin
        Inc(iTry);
        if iTry < 3 then
        begin
          State := stSendingMsg;
        end else
        begin
          State := stError;
          Timer1.Enabled := False;
          AddToMemo('ERROR! No ACK received');
        end;
        Exit;
      end;
      
      State := stWaitingForMsg;
    end;

    stWaitingForMsg:
    begin
      try
        ReceivedText := IdTCPClient1.IOHandler.WaitFor(#3, True, True);
        lrc := IdTCPClient1.IOHandler.ReadByte;
      except
        on E: Exception do
        begin
          State := stError;
          Timer1.Enabled := False;
          AddToMemo('ERROR! ' + E.Message);
          Exit;
        end;
      end;

      AddToMemo('Received message:' + ReceivedText + Char(lrc));

      if (ReceivedText is not valid) then
      begin
        IdTCPClient1.IOHandler.Write(Byte($15)); // send NAK
        Exit; // keep waiting...
      end;

      IdTCPClient1.IOHandler.Write(Byte($6)); // send ACK

      if (ReceivedText is HOLD message) then
        Exit; // keep waiting...

      // use ReceivedText as needed...

      State := stIdle;
      Timer1.Enabled := False;
    end;
  end;
end;

 

Edited by Remy Lebeau

Share this post


Link to post

Thank a lot for a time you spend for this..

Solution 2 with timer works on some way...it's hard to work since i can't play with this device so i need to improvise..

 

Problem is that timer must work all the time until transaction is complete..

After i received message 'INSERT CARD' timer stops and i can't read anymore.

 

Could you help me how to send some message from server I made with IdTCPServer1 component

to Client program with some Button on server form ?

Something like "AContext.Connection.IOHandler.Write"...

 

 

Share this post


Link to post
1 hour ago, boris.nihil said:

Problem is that timer must work all the time until transaction is complete..

After i received message 'INSERT CARD' timer stops and i can't read anymore.

The code I presented is only handling 1 request/response at a time, per the documented flow chart.  If there are multiple messages involved in a single transaction, then of course you will have to adjust the code to account for that.  In the part where it says "use ReceivedText as needed...", you would have to actually look at the message data, and if it is INSERT CARD then progress to the next state and act accordingly to continue the transaction, not go back to the stIdle state until the transaction is finished.

Quote

Could you help me how to send some message from server I made with IdTCPServer1 component

to Client program with some Button on server form ?

Something like "AContext.Connection.IOHandler.Write"...

I have posted examples of that MANY times before, in many different forums.  Should not be hard to find with some searching around.

 

The best way to handle this is to implement a per-client thread-safe queue for each TIdContext, then your UI thread can push data into the queue of the desired client as needed, and the server's OnExecute event can send queued data for the calling TIdContext when it is safe to do so.

Edited by Remy Lebeau

Share this post


Link to post

Hi sorry for my request but i need to integrate same procedure in c#. Could you send me protocol manual to help me?

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×