A very frequent request in AX for Retail 2009 was for the ability to customize the Retail Transaction Service.  The Transaction Service is used to make synchronous calls into AX (Retail Headquarters) to get real-time information – a very common scenario for many POS operations.

While it was possible to customize Transaction Service in 2009, it was a bit of a hack and wasn’t officially supported.  I’m happy to report that this functionality was added in 2012 and it is now very easy to create your own methods in AX that you can call from the POS.

Just as a brush-up, a quick overview on how the POS and Transaction Service work together:  The Transaction Service is a standard Windows Service that listens to the POS on a specific TCP/IP port – it’s only job is to listen for requests (almost exclusively from the POS) and turn those requests over to the AX .Net Business Connector for processing on the AOS.  Since this is done synchronously, it holds up the POS until a processing is returned from the AOS.  A message is returned which will contain not only a success or failure flag, but usually some sort of data (a payout) that is needed by the POS.  The Transaction Service doesn’t actually do any work – it’s just a “middle man” between the POS and the AX Headquarters.

As mentioned, the Transaction Service places calls into AX using the .Net Business Connector.  In the previous version, any possible calls were hard-coded directly in the Transaction Service executable.  In AX for Retail 2012 we have added a way to send in a generic method name for execution.  All you need to do is add this method to the RetailTransactionService class in the AOT and you can immediately start calling it from the POS.

The X++ Side

Here is a method that you can copy and paste into your RetailTransactionService class:

public static container myTestMethod(str TestString)
{
   
container       returnResult;
   
;

   
if (timeNow() mod 2 == 0)
   
{
       
returnResult = [true,"", strFmt("Current second (%1) was even.  You sent:  %2", timeNow(),TestString)];
   
}
   
else
       
returnResult = [false, strFmt("This call failed.  The current second (%1) was odd instead of even.", timeNow()), ""];

   
return returnResult;

}

There are a couple of things of interest in this code.  First of all, the method has to be a static method – the calls are made through the .Net Business Connector without actually creating an instance of the RetailTransactionService object.  Secondly, there is only one option for returning information back to your POS code:  an X++ container object.  In this example, I have created a container of three values:  a boolean for success or failure, an error message, and a payout.  The simple code just uses the current time (odd or even second) to determine success or failure.  The important thing with sending back a container is to make sure that your call is expecting the same number of values. 

The final thing to note on the X++ side are the parameters that are sent in.  This is a simple array of objects; my code just sends in one (a string).  Because X++ is very closely-related to all .Net languages, arguments between C# and X++ are usually pretty interchangeable, but I would caution against getting too creative.  Stick to more simple types (strings, integers) and if you need to send in more structured data, convert it to XML.  You can see some good examples in the other methods in the RetailTransactionService class.

The POS (C#) Side

An easy way to test out your new method is to add the following code to your Blank Operation plug-in:

using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
 
{…}
 
        public void BlankOperation(IBlankOperationInfo operationInfo, IPosTransaction posTransaction)
       
{

           
try
           
{
               
ReadOnlyCollection<object> containerArray;
               
containerArray = Application.TransactionServices.Invoke("myTestMethod", "ThisTest");

               
bool retValue = (bool)containerArray[1];
               
string errorMessage = containerArray[2].ToString();
               
string payout = containerArray[3].ToString();
               
string comment = string.Empty;

               
if (retValue)
               
{
                   
comment = string.Format("Call succeeded.  Payout:\n{0}", payout);
               
}
               
else
               
{
                   
comment = string.Format("Call failed.  Error Message:\n{0}", errorMessage);
               
}

               
using (LSRetailPosis.POSProcesses.frmMessage dialog = new LSRetailPosis.POSProcesses.frmMessage(comment.ToString(), MessageBoxButtons.OK, MessageBoxIcon.Error))
               
{
                   
LSRetailPosis.POSProcesses.POSFormsManager.ShowPOSForm(dialog);
               
}

           
}
           
catch (System.Exception ex)
           
{
               
LSRetailPosis.ApplicationExceptionHandler.HandleException(this.ToString(), ex);
               
throw;
           
}
       
}

As you can see, to call your method you just use the Application.TransactionServices.Invoke()  method with your X++ method name and your parameter(s).  The X++ container gets returned as a ReadOnlyCollection that you can use to read the results.

Note the distinction between an error in the business logic (the boolean in the containerArray) and the try/catch block.  An exception in the latter will most commonly happen if the Transaction Service is down but can also happen if your method is not found in the RetailTransactionService class or if there are some type conversion problems.  As a developer, you’ll have to make sure to line up method names and data types in both your POS code and the X++ code; because they are separate environments, there is no automatic checking to help you out.

Final Notes

Most of the calls to Transaction Service from the POS code still use the hard-coded methods, but some have been migrated over to using the and Invoke() method.  Take a look at the SalesOrder.cs file in the Sales Order plug-in for some good examples of how you can get creative for packaging up information to send back and forth from C# to X++ code.  The corresponding X++ methods in the RetailTransactionService are especially worth digging into to see how columns and rows of data are created to send back.

I was very happy to see this new functionality added to the product – hopefully it will make your life a bit easier as you’re customizing the POS.