Whats the best way to get Samba Entity Data onto a webserver with strict network policies

Given your limitations with ports and connectivity, this ^ is probably your best option.

Use JScript helper functions…

web.Upload(<url>,<content>)
web.PostData(<url>,<data>,[<user>],[<password>])
web.PostJson(<url>,<json>,[<user>],[<password>])

… last 2 functions are identical but web.PostJson function adds “Content-Type”, “application/json” header to the request.

Even if you have no “real” data or content to upload, you can still pass data in the URL as you suggest.

But given you can use PHP, the JSON method might work very well for you because you can form the JSON using the helper JSON.stringify()

{
  var contact = new Object();
  contact.firstname = "Emre";
  contact.surname = "Eren";
  contact.phone = ["555-0100", "555-0120"];
  return JSON.stringify(contact);
}

… and in your PHP it is very easy to parse JSON using json_decode()

1 Like

What’s the User and Password for in this? what does it do?

Also would you have some documentation where I can find some more info about how to listen to it on the webserfer>

User and Password are optional. It depends on whether the web service requires them, and in most cases it would be a good idea to supply them so that you don’t have the entire planet posting data to your service. If you look at the PMI integration done by @JTRTech, you will see that the system he posts data to requires that authentication. He is posting (and requesting) data to (from) an API which is available in the online service he uses.

No I don’t. You will need to read about web technologies that work with data received by the POST method. In general, HTML <FORM> elements work this way to submit user-entered data from a webpage form. You see this used on the internet nearly everywhere, and it is easy to build. But as far as “listening”, I don’t have implementation details for the server-side to enable this.

In fact, you don’t need to “listen” at all AFAIK. You are posting data to the server, in the same way you would do with form data when you click the Submit button on a webpage. Your code processes the submitted/posted data however you choose.

I haven’t worked all of it out quite yet, but so far I have some interesting results, so I know I am on the “right track” to receiving data using PHP as posted by SambaPOS …

$_SERVER------------------
$_SERVER :: URL:/posthandler.php
$_SERVER :: SERVER_SOFTWARE:Microsoft-IIS/10.0
$_SERVER :: SERVER_PROTOCOL:HTTP/1.1
$_SERVER :: SERVER_PORT_SECURE:0
$_SERVER :: SERVER_PORT:82
$_SERVER :: SERVER_NAME:localhost
$_SERVER :: SCRIPT_NAME:/posthandler.php
$_SERVER :: REQUEST_METHOD:POST
$_SERVER :: CONTENT_TYPE:application/json
$_SERVER :: CONTENT_LENGTH:17
$_SERVER :: HTTP_CONTENT_TYPE:application/json
$_SERVER :: HTTP_CONTENT_LENGTH:17
$_SERVER :: FCGI_ROLE:RESPONDER
$_SERVER :: PHP_SELF:/posthandler.php
$_SERVER :: PHP_AUTH_USER:q
$_SERVER :: PHP_AUTH_PW:nada
$_ENV------------------
$_REQUEST------------------
$_GET------------------
$_POST------------------
$_COOKIE------------------
$_FILES------------------

This is the interesting bit:

$_SERVER :: CONTENT_TYPE:application/json
$_SERVER :: CONTENT_LENGTH:17
$_SERVER :: PHP_AUTH_USER:q
$_SERVER :: PHP_AUTH_PW:nada

… because this is what I sent:

function postdata()
{
  var url = "http://localhost:82/posthandler.php";
  var data = "this is test data";
  var usr = "q";
  var pwd = "nada";
  //var res = web.PostData(url,data);
  var res = web.PostJson(url,data,usr,pwd);
  return res;
}

So you can see the User, Password, and JSON data content length (17) which corresponds perfectly.

I have it working now. I will clean up the code and post implementation details in a little while …

SambaPOS PostJson Function:

function postdata()
{
  var url = "http://localhost:82/posthandler.php";
  var data = '{"content":"this is test data"}';
  var usr = "q";
  var pwd = "nada";
  var res = web.PostJson(url,data,usr,pwd);
  return res;
}

Server receives:

$_SERVER :: PHP_AUTH_USER:q
$_SERVER :: PHP_AUTH_PW:nada
$_SERVER :: CONTENT_TYPE:application/json
$_SERVER :: CONTENT_LENGTH:31
{"content":"this is test data"}

This is cool. Never done this before, so I find it to be somewhat awesome. One more step toward online integration …

##SambaPOS postJSON() Script:

function postJSON(tid,oid,onm,ouid)
{
  // override values for testing
  //var tid = 3214;
  //var oid = 7757;
  //var onm = 347;
  //var ouid = 'YTYb4Ra7H0aN4ucamXrwDg';
  //return "TID:"+tid + " OID:"+oid + " ONM:"+onm + " OUID:"+ouid;
  
  // webserver URL, Username, Password
  var url = "http://localhost:82/posthandler.php";
  var usr = "q";
  var pwd = "nada";
  
  var datafields = new Array();
  datafields.push("TicketId");
  datafields.push("Id");
  datafields.push("OrderNumber");
  datafields.push("OrderUid");
  
  datafields.push("MenuItemId");
  datafields.push("MenuItemName");
  datafields.push("PortionName");
  datafields.push("Price");
  datafields.push("Quantity");
  datafields.push("PortionCount");

  datafields.push("PriceTag");
  datafields.push("Tag");
  datafields.push("Taxes");
  datafields.push("OrderTags");
  datafields.push("OrderStates");
  
/*
  qry += " ,[CalculatePrice]";
  qry += " ,[DecreaseInventory]";
  qry += " ,[IncreaseInventory]";

  qry += " ,[CreatingUserName]";
  qry += " ,[CreatedDateTime]";
  qry += " ,[LastUpdateDateTime]";
  qry += " ,[AccountTransactionTypeId]";
  qry += " ,[ProductTimerValueId]";

  qry += " ,[GroupTagName]";
  qry += " ,[GroupTagFormat]";
  qry += " ,[Separator]";
  
  qry += " ,[Locked]";
  qry += " ,[WarehouseId]";
  qry += " ,[DepartmentId]";
  qry += " ,[TerminalId]";
*/

  var datafieldscount = datafields.length;
  
  var qry = "";

  qry += "SELECT";
  
  for (var d=0; d < datafieldscount; d++)
  {
    qry += (d>0 ? " ," : " ");
    qry += "[" + datafields[d] + "]";
  }
  
  qry += " FROM [Orders]";

  qry += " WHERE 1=1";
  qry += " AND [TicketId] = " + tid;
  //qry += " AND [Id] = " + oid;
  qry += " AND [OrderNumber] = " + onm;
  qry += " AND [OrderUid] = '" + ouid + "'";
  qry += " ORDER BY [Id]";
  
  //return qry;
  
  var qryresult = sql.Query(qry).Delimit('~').First;
  var qrydata = qryresult.split('~');
  var qryfieldcount = qrydata.length;  

  //return qryfieldcount;
  
  var data = new Object();
  
  for (var d=0; d < datafieldscount; d++)
  {
    data[datafields[d]] = qrydata[d];
  }
  
  var jsondata = JSON.stringify(data);
  //return jsondata;

  var res = web.PostJson(url,jsondata,usr,pwd);
  return res;
}

##Server PHP posthandler.php

I am writing the post data to a file, but you could easily process it to create INSERT statements to push data into a DB on the Server.

<?php
$usr_auth = "q";
$pwd_auth = "nada";

$usr           = $_SERVER["PHP_AUTH_USER"];
$pwd           = $_SERVER["PHP_AUTH_PW"];

if ($usr!==$usr_auth || $pwd!==$pwd_auth) {
    die("Authentication FAILED.");
}

$contentType   = $_SERVER["HTTP_CONTENT_TYPE"];
$contentLength = $_SERVER["HTTP_CONTENT_LENGTH"];

//$data .= "USER:" . $usr . " PWD:" . $pwd . "\r\n";
//$data .= "CT:" . $contentType . " LEN:" . $contentLength . "\r\n";

$inputJSON = file_get_contents('php://input');
$input = json_decode( $inputJSON, TRUE ); //convert JSON into array

$data .= "================================================================\r\n";

$data .= "JSON (" . count($input) ."):\r\n";
$data .= $inputJSON ."\r\n\r\n";

$ticketID = $input["TicketId"];
$orderID  = $input["Id"];
$orderNum = $input["OrderNumber"];
$orderUID = $input["OrderUid"];

$data .= "TicketId:" . $ticketID ."\r\n";
$data .= "OrderID:" . $orderID ."\r\n";
$data .= "OrderNum:" . $orderNum ."\r\n";
$data .= "OrderUID:" . $orderUID ."\r\n";

$data .= "------------------\r\n";

foreach ($input as $inkey => $inval) {
    $data .= $inkey . ":";
    $data .= $inval . "\r\n";
}

$data .= "================================================================\r\n\r\n";

$fileName = "ticket_".$ticketID.".txt";
// FILE_APPEND | LOCK_EX
$fwsuccess = file_put_contents ($fileName, $data, FILE_APPEND);

return 0;
?>

This is what is written to the “Ticket_<ticketId>.txt” file when it receives the data:

================================================================
JSON (15):
{"TicketId":"3235","Id":"7836","OrderNumber":"390","OrderUid":"fVokZfRcckmb9qLCXSt8Fg","MenuItemId":"118","MenuItemName":"Item A","PortionName":"Normal","Price":"100.00","Quantity":"1.000","PortionCount":"1","PriceTag":"","Tag":"","Taxes":"[]","OrderTags":"[{\"OI\":25,\"OK\":\"000010\",\"PR\":-10,\"Q\":1,\"TF\":true,\"TN\":\"Discount Festival\",\"TV\":\"Category 1\",\"UI\":2}]","OrderStates":"[{\"D\":\"\\/Date(1461272870335-0600)\\/\",\"OK\":\"000000\",\"S\":\"Submitted\",\"SN\":\"Status\",\"SV\":\"\",\"U\":2},{\"D\":\"\\/Date(1461272868589-0600)\\/\",\"OK\":\"000000\",\"S\":\"0\",\"SN\":\"Staff\",\"SV\":\"\",\"U\":2}]"}

TicketId:3235
OrderID:7836
OrderNum:390
OrderUID:fVokZfRcckmb9qLCXSt8Fg
------------------
TicketId:3235
Id:7836
OrderNumber:390
OrderUid:fVokZfRcckmb9qLCXSt8Fg
MenuItemId:118
MenuItemName:Item A
PortionName:Normal
Price:100.00
Quantity:1.000
PortionCount:1
PriceTag:
Tag:
Taxes:[]
OrderTags:[{"OI":25,"OK":"000010","PR":-10,"Q":1,"TF":true,"TN":"Discount Festival","TV":"Category 1","UI":2}]
OrderStates:[{"D":"\/Date(1461272870335-0600)\/","OK":"000000","S":"Submitted","SN":"Status","SV":"","U":2},{"D":"\/Date(1461272868589-0600)\/","OK":"000000","S":"0","SN":"Staff","SV":"","U":2}]
================================================================

#Automation

… Actions and Rules removed from this post - see later posting with updated Automation …

4 Likes

Thats amazing, I can really see how to do this now.

One more question I have is when you have the PD Store value
and you have values like [=TN(’{ORDER NO}’)]
What is that, how do I know how to get data like tIcket entity?
I’ll ask some more questins a bit later after I give it a test.

[=TN('{ORDER NO}')]

[= ... ] indicates that we want to perform some type of function. It could be adding numbers together, or concatenating strings, or whatever.

TN(<something>) is a built-in SamabPOS method that converts a value/string to a number. If the value/string is empty or null, it returns 0.

{ORDER NO} is a Printer Template Tag. Not all tags are available or have values - it depends on the Rule Event and what state the Ticket, Order, or other operation is in at the time.

We are storing values in Program Settings with isLocal set to True which means we are holding the values in memory, as opposed to storing the values in the Database.

We can access Program Settings by their Name using {SETTING:settingName} which is a Global Tag accessible anywhere at any time.

You should be able to access and store Ticket Entities using syntax like:

{ENTITY NAME:Customers}
{ENTITY NAME:Tables}

… or if you want a Entity Custom Data Field, you would use, for example:

{ENTITY DATA:Customers:Phone}
{ENTITY DATA:Customers:Membership Number}

The reason we do the Actions in different steps or Rules is because not all data is immediately available until the Ticket is committed/created in the DB. So we are storing various values that we require for our SQL Query and then fire the JScript function in another Rule using the Execute Automation Command Action with the Run in Background parameter set to True. This ensures the data has been committed to the DB so that the JScript Query can find the matching record before it performs the Post operation.

In the case of the [Orders] table, we could join the [Tickets] and [TicketEntities] and [Entities] tables in the query in JScript rather than storing that data in Program Settings in Actions.

It all depends on exactly what data you want to capture and post, and when.

Ok, im having a problem where It only returns one menu item in the order, and if in the order there is multiple it just repeats the first one again, is theree an easy way to fix this?

Ensure you update your PHP script to the version posted above. It is writing to a file named:

Ticket_<ticketId>.txt

… and it is set to append to the file…

$fileName = "ticket_".$ticketID.".txt";
// FILE_APPEND | LOCK_EX
$fwsuccess = file_put_contents ($fileName, $data, FILE_APPEND);

I have that updated PHP Script, and this is what i get when I put through an order of a Chocolate Milkshake and a Banana Milkshake

================================================================
JSON (15):
{"TicketId":"1127","Id":"1190","OrderNumber":"129","OrderUid":"Sno3VO9kK0Oy4DCTWmGfWg","MenuItemId":"36","MenuItemName":"Banana Milkshake","PortionName":"Normal","Price":"3.50","Quantity":"1.000","PortionCount":"1","PriceTag":"","Tag":"","Taxes":"[]","OrderTags":"","OrderStates":"[{\"D\":\"\\/Date(1461280619544+1000)\\/\",\"OK\":\"000000\",\"S\":\"Submitted\",\"SN\":\"Status\",\"SV\":\"\",\"U\":1}]"}

TicketId:1127
OrderID:1190
OrderNum:129
OrderUID:Sno3VO9kK0Oy4DCTWmGfWg
------------------
TicketId:1127
Id:1190
OrderNumber:129
OrderUid:Sno3VO9kK0Oy4DCTWmGfWg
MenuItemId:36
MenuItemName:Banana Milkshake
PortionName:Normal
Price:3.50
Quantity:1.000
PortionCount:1
PriceTag:
Tag:
Taxes:[]
OrderTags:
OrderStates:[{"D":"\/Date(1461280619544+1000)\/","OK":"000000","S":"Submitted","SN":"Status","SV":"","U":1}]
================================================================

================================================================
JSON (15):
{"TicketId":"1127","Id":"1190","OrderNumber":"129","OrderUid":"Sno3VO9kK0Oy4DCTWmGfWg","MenuItemId":"36","MenuItemName":"Banana Milkshake","PortionName":"Normal","Price":"3.50","Quantity":"1.000","PortionCount":"1","PriceTag":"","Tag":"","Taxes":"[]","OrderTags":"","OrderStates":"[{\"D\":\"\\/Date(1461280619544+1000)\\/\",\"OK\":\"000000\",\"S\":\"Submitted\",\"SN\":\"Status\",\"SV\":\"\",\"U\":1}]"}

TicketId:1127
OrderID:1190
OrderNum:129
OrderUID:Sno3VO9kK0Oy4DCTWmGfWg
------------------
TicketId:1127
Id:1190
OrderNumber:129
OrderUid:Sno3VO9kK0Oy4DCTWmGfWg
MenuItemId:36
MenuItemName:Banana Milkshake
PortionName:Normal
Price:3.50
Quantity:1.000
PortionCount:1
PriceTag:
Tag:
Taxes:[]
OrderTags:
OrderStates:[{"D":"\/Date(1461280619544+1000)\/","OK":"000000","S":"Submitted","SN":"Status","SV":"","U":1}]
================================================================

Ok, yes you are correct.

Need to modify some things, but I need to play with it for a bit… it has to do with the Actions / Rules.

2 Likes

Needles about this small bug, this really is so cool, i can see so many opportunties for this. like remote reporting, customer’s can see there receipt online.

1 Like

Ah @QMcKay - if I could just bottle your knowledge & skills (1 very big bottle!) then feed it into a clone bot army :robot: - I would take over the world …

2 Likes

The problem is caused by the speed at which things happen. So our stored values are being overwritten before the data can be posted. What seems to work is to set values to Setting Names that are more unique, so we will base this on the {ORDER_UID} since it is a very unique ID for an Order-line.

Delete both Rules (those prefixed by PD).

Modify the Action for PD Store Value (this isn’t really necessary, but it makes testing more flexible)

##PD Store Value [Update Program Setting] (Action)##

Action Name: PD Store Value
Action Type: Update Program Setting
###Parameters:###
Setting Name: [:settingName]
Setting Value: [:settingValue]
Update Type: [:updateType]
Is Local: [:isLocal]

Recreate your Rules, but this time, we are going to set the Setting Names to much more unique names so they don’t get over-written by subsequent/simultaneous Orders. You will notice that each settingName now has the {ORDER UID} as a suffix of the name, and when we fire the PD ExecAMC Action, we send the {ORDER UID} as the Command Value (AMCvalue) …

##PD Order Sumitted - Store Values [Order State Updated] (Rule)##

Rule Name: PD Order Sumitted - Store Values
Event Name: Order State Updated
Rule Tags:
Custom Constraint List (3):
Execute Rule if: Matches
State NameEqualsStatus
StateEqualsSubmitted
Previous StateEqualsNew

##Actions (5):##

PD Store Value

Constraint: (none)

settingName: PD_OrderUid_{ORDER UID}
settingValue: {ORDER UID}
updateType: Update
isLocal: True
PD Store Value

Constraint: (none)

settingName: PD_TicketId_{ORDER UID}
settingValue: [=TN('{TICKET ID}')]
updateType: Update
isLocal: True
PD Store Value

Constraint: (none)

settingName: PD_OrderId_{ORDER UID}
settingValue: [=TN('{ORDER ID}')]
updateType: Update
isLocal: True
PD Store Value

Constraint: (none)

settingName: PD_OrderNo_{ORDER UID}
settingValue: [=TN('{ORDER NO}')]
updateType: Update
isLocal: True
PD ExecAMC

Constraint: (none)

AMCname: PD Post Order Data
AMCvalue: {ORDER UID}
bg: True
delay: 0

##Mappings##

Mappings
Terminal User Role Department Ticket Type
****

The second Rule now references [:CommandValue] to pull the unique Setting Name in the {CALL:pd.postJSON()} method …

##PD Post Order Data [Automation Command Executed] (Rule)##

Rule Name: PD Post Order Data
Event Name: Automation Command Executed
Rule Tags:
Custom Constraint List (1):
Execute Rule if: Matches
Automation Command NameEqualsPD Post Order Data

##Actions (1):##

PD Store Value

Constraint: (none)

settingName: PD_PostResult_[:CommandValue]
settingValue: {CALL:pd.postJSON('{SETTING:PD_TicketId_[:CommandValue]}', '{SETTING:PD_OrderId_[:CommandValue]}', '{SETTING:PD_OrderNo_[:CommandValue]}', '{SETTING:PD_OrderUid_[:CommandValue]}')}
updateType: Update
isLocal: True

##Mappings##

Mappings
Terminal User Role Department Ticket Type
****

1 Like

Ill have a look at how this goes.

I agree. I had not thought of doing this before, and I didn’t really have a clear methodology in my mind on how to accomplish the task. But I like this type of challenge, so I thought I would give it a shot even though I won’t use this in the near future, if ever.

I too think it is very awesomely, amazingly cool!

LOL ;P, thanks for that @pauln … I didn’t understand how it could be done, so I had to do a little research to figure out how to process Post data.

Knowing PHP fairly well, I figured it would be somewhat simple and easy to do, but at first I had a hard time finding the right “thing”. While it is straight-forward to process Post data from a Form Submission using $_POST["form_element_name"], that wasn’t going to work in this case, because that is looking for a certain type of encoding, and we don’t have any of the form element names to reference, nor a “user” to click a Submit button ;).

What I was looking for is “sort of” like listening for something to happen behind the scenes, but with no user interaction like a Form Submission. And as I surmised earlier, we were not really “listening” for anything in the first place - we just needed to process the Post Data somehow. Given we have APIs all over the internet that allow for it, like @JTRTech’s PMI solution., I was confident there existed a method to do this.

I stumbled across the “key piece” a couple of times in my research without even realizing it and skimmed right over it. The “magic” is here:

file_get_contents('php://input');

That ^ thing is some sort of magical PHP thing that contains the Post data - in our case, the JSON string. I didn’t even read about what it was - I finally figured it must be something special when I saw it more than one time in different code-snippets and I just tried it. When I realized it worked, I did a bit more reading. You can read about it and other “streams” here …

http://php.net/manual/en/wrappers.php.php

… and here …

1 Like

Ive gone and got my entity data working :slight_smile: I think I did it a long way, but still it works great :slight_smile:

[code]
function postJSON(tid,oid,onm,ouid)
{
// override values for testing
//var tid = 3214;
//var oid = 7757;
//var onm = 347;
//var ouid = ‘YTYb4Ra7H0aN4ucamXrwDg’;
//return “TID:”+tid + " OID:“+oid + " ONM:”+onm + " OUID:"+ouid;

// webserver URL, Username, Password
var url = “mywebsite.com is available for purchase - Sedo.com”;
var usr = “q”;
var pwd = “nada”;

var datafields = new Array();
datafields.push(“TicketId”);
datafields.push(“Id”);
datafields.push(“MenuItemName”);
datafields.push(“Price”);
datafields.push(“Quantity”);

/*
qry += " ,[CalculatePrice]“;
qry += " ,[DecreaseInventory]”;
qry += " ,[IncreaseInventory]";

qry += " ,[CreatingUserName]“;
qry += " ,[CreatedDateTime]”;
qry += " ,[LastUpdateDateTime]“;
qry += " ,[AccountTransactionTypeId]”;
qry += " ,[ProductTimerValueId]";

qry += " ,[GroupTagName]“;
qry += " ,[GroupTagFormat]”;
qry += " ,[Separator]";

qry += " ,[Locked]“;
qry += " ,[WarehouseId]”;
qry += " ,[DepartmentId]“;
qry += " ,[TerminalId]”;
*/

var datafieldscount = datafields.length;

var qry = “”;

qry += “SELECT”;

for (var d=0; d < datafieldscount; d++)
{
qry += (d>0 ? " ," : " ");
qry += “[” + datafields[d] + “]”;
}

qry += " FROM [Orders]";

qry += " WHERE 1=1";
qry += " AND [TicketId] = " + tid;
//qry += " AND [Id] = " + oid;
qry += " AND [OrderNumber] = " + onm;
qry += " AND [OrderUid] = ‘" + ouid + "’“;
qry += " ORDER BY [Id]”;

//return qry;

var qryresult = sql.Query(qry).Delimit(‘~’).First;
var qrydata = qryresult.split(‘~’);
var qryfieldcount = qrydata.length;

var qry2 = “”;

qry2 += " SELECT [EntityName] ,[EntityCustomData] FROM [TicketEntities] WHERE 1=1";
qry2 += "AND [Ticket_Id] = " + tid;

var qryresult2 = sql.Query(qry2).Delimit(‘~’).First;

var qrydata2 = qryresult2.split(‘~’);

var entity = JSON.parse(qrydata2[1]);

var data = new Object();

for (var d=0; d < datafieldscount; d++)
{
data[datafields[d]] = qrydata[d];
}
data[“Student ID”] = qrydata2[0];
data[“Student Year”] = entity[0].Value;
data[“Student Name”] = entity[1].Value;
data[“Student Email”] = entity[2].Value;

var jsondata = JSON.stringify(data);
//return jsondata

var res = web.PostJson(url,jsondata,usr,pwd);
dlg.ShowMessage(res);
return res;

}[/code]

Now to try and work out in getting this in my datbase.

1 Like

Ok, spent a bit of time, but this is amazing this can be done in Samba…

I order what I want

And pay it

I do a quick query of my webserver mysql datbase 2 seconds later


And the order, and entity data is all there.

From that I can do so much!

3 Likes

Hey @QMcKay would you have any idea of what to do if the terminal loses network connection? I dont want to lose all the sales on the webserver, and therfore get bad reports. Im thinkin about sominng like storing the ticketid_{number} in the samba db if it doesnt get a response from the web server and from then whenever I next tender an order, if script receives a response, it sends that off and after that it checks if in the datbase there are any values that containt ticketID and if so upload them and remove the value. Another complication with that is, how would I get the current system time that an order was made?