Skip to Content
Technical Articles
Author's profile photo Robert Zieschang

SAP Customer Checkout Plugin Development – Part II

Back to Part I

Part II: Create a backend Plugin

Welcome to the second part of this blog series. In this part we will add two plugin properties, one for a list of material Ids and the second one for storing a message, read them from the database and we will show the message to the user if a sales item is added which was configured in the list of material Ids.

Maybe you want to remind the cashier to ask for the customers id for certain sales items?

We will also learn how to debug our code and log messages to the cco log. So let us start.

I copied the plugin we created in the first part, renamed it as blogpluginpart2 in the pom.xml and set the package name accordingly.

Let’s dive in. The first thing we need to do is to override the persistPropertiesToDB() method and return true. This ensures, that CCO handles the persistence of our plugin properties. Open the App.java class, click in the source at the bottom and press Strg + Spacebar. A list of methods will appear that we can override. Select the persistPropertiesToDB() method.

This method should always return true.

The next method we need to override is the getPluginPropertyConfig() method. This method should return a Map<String,String> of our properties.

The key of the HashMap is the name of the plugin property and the value is the type of data which will be stored in this property. So use int for integers or e.g. a boolean will create a checkbox. For now we will use two string properties. The first property will save a comma separated list of material Ids and the second one is a message string, we will show to the cashier, if a new sales item is in the LIST_OF_MATERIALS.

Next thing to do is to let our plugin react when a new sales item is added to the receipt. The Customer Checkout API is aspect-oriented; means whenever SAP is adding so called exit-points to their methods in their classes their API is calling any plugin which has this particular exit-point as parameter in annotation.

So we will add a method checkSalesItem like this:

@PluginAt is the annotation. And we will set this annotation to be called when the addSalesItems Method (method=“addSalesItems“) in the IReceiptManager class (pluginClass=IReceiptManager.class) was executed (where=POSITION.AFTER).

So our method checkSalesItem is called, whenever a sales item was successfully added to the receipt.

In the array of objects (Object[] args) cco sends all necessary entities we need for our process. E.g. the receipt and the added salesItem.

If you want to know, which entities are stored in the array of objects, we will need to setup our debug environment. Double click on the line with the method head to set a break point.

Save your files and build your plugin. Copy the plugin in the plugin folder of your Customer Checkout installation and start cco. Go back to your eclipse and right click on your project folder. Choose Debug As and Debug Configurations….

On the left list look for Remote Java Application and select this entry. Now create a new configuration via click on the new button.

Set a name for your configuration and change the port to 4000 (this is the port we set in part 1 in the run.bat, remember?). Click Apply and Debug.

Login into your cco and add a sales item. Your eclipse should change it’s perspective to debug.

Confirm with yes. In the variables window you will now see the content of the args array.

Disconnect your debug session. We will now implement the checkSalesItem Method. The exitpoint for addSalesItems method of the IReceiptManager class at the POSITION.AFTER is called multiple times. To ensure, that we will only react on the last one, we will check, how many entries the args array has.

Our plugin only reacts if the args array has two entries. If so, we will retrieve the 0 and the 1 indexed entry and cast it to the right type.

Will get our plugin property LIST_OF_MATERIALS and will split the string into a string array. To comfortably check if the new sales item has a material Id we configured we will create a List<String> of this String array so that we are able to use the contains method.

After that we are calling a showMessageToUi method we will implement later. This method will given two strings. The message itself and a “type” of the message.

Whenever the plugin shows the message to the cashier it should also create a log entry. SAP Customer Checkout has everything you need to log your own error messages, exception stacktraces and so on. Simply add this to your global class members:

Now we can log our messages right into the cco logs (and of course the cco console) when we have to.

The last thing we are going to implement in this part is the showMessageToUi method. This method allows you to create simple notifications to the cashier right from your cco plugin.

The UIEventDispatcher takes the „SHOW_MESSAGE_DIALOG“ action as parameter and also a Map where we set the message, in id, the lifetime and the type.

Ok, build your plugin, copy the jar into your plugin folder and start cco! Go to your plugin configuration in your cco backend and set your properties.

Save these changes. SAP Customer Checkout caches the properties from every plugin at startup so if you change this, you should restart.

Now if we add sales items with the configures material Id, we should see the MESSAGE_FOR_CASHIER and also an entry in the console.

And of course, also in the customer checkout log.

 

That’s all for this part of the course. If you have any questions or feedback, please do not hesitate to get in touch with me.

The code of this parts plugin is hosted in gitlab: https://gitlab.com/ccoplugins/blogpluginpart2

Stay tuned for the next part, where we are implementing a plugin sync job to retrieve data from the b1i and we are implementing a way to send custom monitoring messages to CCOm.

Assigned Tags

      45 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Tobias Siltmann
      Tobias Siltmann

      very understandable and clearly explained

      Author's profile photo Sergei Zagoskin
      Sergei Zagoskin

      Hi Robert,

       

      It looks like “UIEventDispatcher.INSTANCE.dispatchAction("SHOW_MESSAGE_DIALOG", null, dialogOptions);” allows to pass button and input fields to front end.

      Can you please give me an example or suggest where can I find info about this. I need to ask question on frontend and get manually inputted reply from user on back end.

       

      Thanks,

      Sergei

      Author's profile photo Robert Zieschang
      Robert Zieschang
      Blog Post Author

      Hi Sergei,

       

      afaik there is no real API Documentation unfortunately. But take this example to create a small input dialog for the cashier.

      JSONObject dialogOptions = new JSONObject();
      dialogOptions.put(“message”, “Please insert some value here:”);
      dialogOptions.put(“id”, “DIALOG_CONFIG”);
      dialogOptions.put(“type”, “info”);
      dialogOptions.put(“input”, “true”);

      JSONObject btnOkConf = new JSONObject();
      btnOkConf.put(“type”, “good”);
      btnOkConf.put(“id”, “DIALOG_CONFIG_BTN_OK”);
      btnOkConf.put(“text”, “OK”);
      btnOkConf.put(“default”, “true”);

      JSONObject btnCancelConf = new JSONObject();
      btnCancelConf.put(“type”, “bad”);
      btnCancelConf.put(“id”, “DIALOG_CONFIG_BTN_CAN”);
      btnCancelConf.put(“text”, “Abort”);

      dialogOptions.put(“buttons”, new JSONObject[] {btnOkConf, btnCancelConf});

       

      Then implement a method to get the input value:

       

      @ListenToExit(exitName=”genericButtonCallback”)
      public void handleDialogInput(Object caller, Object[] args) {

      String inputVal= (String) args[2];

      }

       

      You may also need to check, if the args are for the right event.

       

      Let me know if this worked for you.

      Regards

      Robert

       

      Author's profile photo Sergei Zagoskin
      Sergei Zagoskin

      Hi Robert,

      Thank you very much. It works like charm. It looks like your code for JavaScript, I just tweaked a bit for Java syntax and it works 🙂

      Thanks,

      Sergei

      Author's profile photo Robert Zieschang
      Robert Zieschang
      Blog Post Author

      Hi Sergei,

      glad I could help.

      Regards

      Robert

      Author's profile photo Tomorjin Arildii
      Tomorjin Arildii

      Hi Robert,

      How to print out additional receipt? I need to customize printing template.

       

      Looking forward to hearing from you.

      tomo

      Author's profile photo Robert Zieschang
      Robert Zieschang
      Blog Post Author

      Hi Tomorjin,

       

      do you need to print another receipt or just extend the existing templates?

      When you need to add additional infos to existing printjobs:

      annotate a method in your plugin:

      @ListenToExit(exitName=”BasePrintJobBuilder.mergeTemplateWithData”)

      You will get an array of objects iirc with the printtemplate, the entity which will be printed e.g. the receipt or the voucher. The first entry in the array is iirc an HashMap<String, Object> where you can put additional information like this:

      map.put(“MY_CUSTOM_FIELD”, “Hello my name is dr. greenthumb”);

       

      In the printtemplate you can access the text via ${MY_CUSTOM_FIELD}.

       

      When you need your own Printjob:

      You can implement your own printjob builder by extending the BasePrintJobBuilder class. When extending you need to implement certain methods which are mostly self explainatory.

       

      But I did not have the chance to implement a custom printjob in production.

       

      Regards

      Robert

      Author's profile photo Tomorjin Arildii
      Tomorjin Arildii

      I want to print a tax reciept via thermal printer in afterRecieptPost function.

      Tax reciept looks like below image.

      Author's profile photo Robert Zieschang
      Robert Zieschang
      Blog Post Author

      Hi Tomorjin,

       

      I would suggest not to handle this in the afterReceiptPost.

      Extend the salesReceipt Printtemplate with the layout of your tax receipt. Add a check for a boolean variable (printTaxReceipt yes / no). Printtemplates are implemented via Apache Freemarker Syntax.
      https://freemarker.apache.org/

      Implement a method with the ListenToExit as mentioned above. Something like this:

       

      @ListenToExit(exitName="BasePrintJobBuilder.mergeTemplateWithData)
      public void mergePrintTemplateData(Object caller, Object[] args) {
      
      Map<String, Object> root = (Map<String,Object)args[0];
      
          if(args[2] instanceof ReceiptDTO) {
              // further checks if tax receipt should be printed
              root.put("taxReceiptPrint", "true");
              root.put("var1", "Value1");
              root.put("var2", "Value2");
          }
      }

       

      In your print template you can now use the variable taxReceiptPrint, var1, var2 to enrich your template with the values you need to print on your tax receipt.

      For multilanguage purposes you may need textblocks in the print template like this:

      <@translate key="YOUR_TEXT_CODE">

       

      Then goto your translationsfolder (normally cco/translations) where you find the translation property files.

      Open the file for the desired language. Insert:

      YOUR_TEXT_CODE=your desired text in the desired language

       

      Save it. And now your text should automatically be inserted in your printtemplate.

       

      If you need further assistance or if you are interested that I shall implement this feature please contact info@hokona.de.

       

      Julian Wehmann FYI

       

      Regards

      Robert

       

      Author's profile photo Tomorjin Arildii
      Tomorjin Arildii

      Hello Robert,

      I saw your input dialog example. It's good example.
      But I need to create dialog that has has two or multiple input field. How can I create it?

      Author's profile photo Robert Zieschang
      Robert Zieschang
      Blog Post Author

      Hi Tomorjin Arildii ,

       

      depends on what you want to achieve, when and because of what do you need to show more input fields? If you just need more fields rather than the one age field, you can just add more divs to input your data like this.

      $(document).ready(function () {
          $('.customerInfoContainer').after($('<div style="position: relative; right: 0; top: 0; background-color: #FFF; padding: 5px; border: 1px solid #000; width: 25%; height: 68%; font-size: small; margin-left: 5px; margin-right: 5px; margin-top: 5px; float:left" id="customerAgeDiv">Age of Customer: <input type="number" id="customerAge"></div>'));
      
      // more input fields...
      });

      But this only applies to the retailUI. So please give me a hint, what you want to achieve, and I can try to push you into the right direction.

       

      Regards

      Robert

      Author's profile photo Tomorjin Arildii
      Tomorjin Arildii

      Thank you Robert,

      I am using below source to pop up dialog.

      public void showInputdialog(String question, String btnTxt) {
      JSONObject dialogOptions = new JSONObject();
      dialogOptions.put(“message”, question);
      dialogOptions.put(“id”, “VAT_REGISTER_DIALOG”);
      dialogOptions.put(“type”, “info”);
      dialogOptions.put(“input”, “true”);

      JSONObject btnDlgCorp = new JSONObject();
      btnDlgCorp.put(“type”, “good”);
      btnDlgCorp.put(“id”, “VAT_DIALOG_BTN_OK”);
      btnDlgCorp.put(“text”, btnTxt);
      btnDlgCorp.put(“default”, “true”);

      dialogOptions.put(“buttons”, new JSONObject[]{btnDlgCorp});

      //A SHOW_MESSAGE_DIALOG event is fired, which will show the message box popup
      UIEventDispatcher.INSTANCE.dispatchAction(“SHOW_MESSAGE_DIALOG”, null, dialogOptions);
      }

       

      My question is I need to add more input fields to this dialog? And how?

      Author's profile photo Isaac Valdez
      Isaac Valdez

      Already saw the answer, but would work for template xml?, look like is focus for jpos template.

      pleases, if you can give me some solution for xml.

      Author's profile photo Timothy Mbogo
      Timothy Mbogo

      Hi Robert,

      How do I capture the full receipt details ?

      Regards,

      Timothy.

      Author's profile photo Robert Zieschang
      Robert Zieschang
      Blog Post Author

      Hi Timothy Mbogo ,

       

      please explain in more detail, what you want to achieve, because it is not clear for me.

       

      Regards

      Robert

      Author's profile photo Timothy Mbogo
      Timothy Mbogo

      Hi Robert,

      I have a datecs fiscal printer which Is not integrated to customer checkout. Currently what I have done I have created a service that reads data from a file and send it to the printer. The printer prints the receipt

      So the easiest option I have is to try and get the receipt details that is the itemcode,Itemdescription and the amount plus the total amount tendered so that I create the txt file that will be consumed by the printer service I have created.

      Regards,

      Timothy.

      Author's profile photo Robert Zieschang
      Robert Zieschang
      Blog Post Author

      Hi Timothy Mbogo ,

      you can hook into the following method to be sure to get the posted receipt.

       

      @PluginAt(pluginClass=ReceiptPosService.class, method="postReceipt", where=POSITION.AFTER)
      public Object onReceiptPosted(Object proxy, Object[] args, Object ret, StackTraceElement caller) {
      // the receipt is stored in the array of objects "args"
         ReceiptEntity receipt = (ReceiptEntity) args[0];
      // create file for printing...
      
      }

      Regards
      Robert

      Author's profile photo Timothy Mbogo
      Timothy Mbogo

      Hi Robert,

      Thanks let me try it out.

      Regards,

      Timothy.

      Author's profile photo Timothy Mbogo
      Timothy Mbogo

      Hi Robert,

      If you maybe having some documentation or some learning materials for the CCO api I will greatly appreciate. I think am fumbling too much with less information hehe.

      Regards,

      Timothy.

      Author's profile photo Robert Zieschang
      Robert Zieschang
      Blog Post Author

      Hi Timothy Mbogo ,

      unfortunately there is no real api documentation. It's basically learning by doing.

      But if you are fumbling too much we could discuss your requirements in detail and can offer you some help with the development in various ways. See:

      https://blogs.sap.com/2019/05/29/hokona-sap-customer-checkout-extensions-and-plugins/

      Please drop us a line at info@hokona.de

       

      Regards

      Robert

      Author's profile photo Timothy Mbogo
      Timothy Mbogo

      Hi Robert,

      This is what have done but after I Post the receipt the method is not invoked.

      @PluginAt(pluginClass=ReceiptPosService.class, method="postReceipt", where=POSITION.AFTER)
      public Object onReceiptPosted(Object proxy, Object[] args, Object ret, StackTraceElement caller) {
      // the receipt is stored in the array of objects "args"
      ReceiptEntity receipt = (ReceiptEntity) args[0];
      // create file for printing...

      System.out.println("Call onReceiptPosted : "+args);
      logger.info("Call onReceiptPosted : "+args);
      return receipt;
      }

      Author's profile photo Robert Zieschang
      Robert Zieschang
      Blog Post Author

      Hi Timothy Mbogo ,

       

      maybe I have overseen it, but which UI Mode did you use? There was (unfortunately) a change between the receipt handling between Retail and Quickservice.

      Regards

      Robert

      Author's profile photo Timothy Mbogo
      Timothy Mbogo

      Hi Robert,

      I used the Sales Mode, scanned the item so I expected the method to be invoked after clicking "Yes post Receipt"

       

      Regards,

      Timothy.

      Author's profile photo Robert Zieschang
      Robert Zieschang
      Blog Post Author

      Hello Timothy Mbogo ,

      please go to the cco configuration -> POS System -> Sales Screen. There you will find the config property "UI Mode".

      Regards

      Robert

      Author's profile photo Timothy Mbogo
      Timothy Mbogo

      Hi Robert,

      Yeah have seen it. Am using Retail Mode.

      Regards,

      Timothy

      Author's profile photo Robert Zieschang
      Robert Zieschang
      Blog Post Author

      Hi Timothy Mbogo ,

      please use the following annotation...

      @PluginAt(pluginClass=IReceiptManager.class, method="finishReceipt", where=POSITION.AFTER)

      Method signature is the same as well as the position of the receipt entity in the array of objects.

       

      Regards

      Robert

      Author's profile photo Tomorjin Arildii
      Tomorjin Arildii

      Hi Robert,

      What does POSITION means? There is BEFORE, AFTER positions?

      where=POSITION.AFTER
      Author's profile photo Robert Zieschang
      Robert Zieschang
      Blog Post Author

      Dear Tomorjin Arildii,

      with this property you can define if your plugin code will be executed BEFORE or AFTER the standard CCO code is executed.
      So basically with BEFORE you can manipulate which parameter values the original CCO method (like finishReceipt) gets.

      With AFTER you make sure the standard CCO logic was run and you also get all parameters the original method got.

      If you want to know more about this programming style try reading what aspect oriented programming is all about.

      hth

      Robert

      Author's profile photo Tomorjin Arildii
      Tomorjin Arildii

      Thank you Robert Zieschang ,

      One more question. In IReceiptManager class finishReceipt method is "finishReceipt(ReceiptEntity re, boolean bln)" two parameters. But in Plugin code "afterReceiptPost(Object proxy, Object[] args, Object returnValue, StackTraceElement callStack)" afterReceiptPost function has 4 arguments. How can I understand it?

      IReceiptManager

      IReceiptManager

       

      @PluginAt(pluginClass = IReceiptManager.class, method = "finishReceipt", where = PluginAt.POSITION.AFTER)
      public Object afterReceiptPost(Object proxy, Object[] args, Object returnValue, StackTraceElement callStack) throws BreakExecutionException {
      Author's profile photo Robert Zieschang
      Robert Zieschang
      Blog Post Author

      Dear Tomorjin Arildii,

      the parameters of the original method are stored in the object array "args". The object "proxy" is the instance of the ReceiptManager.

      The returnValue object (only with POSITION.AFTER) is the returnValue which the original cco method would return to it's caller.

      The callstack is good to follow which method called the finishReceipt method.

      Regards

      Robert

      Author's profile photo Timothy Mbogo
      Timothy Mbogo

      Hi Robert,

      Thank you very much, have been able to create the file with the receipt details. Am now looking for Amount tendered so that I can show the change amount on the receipt.

       

      Regards,

      Timothy.

      Author's profile photo Robert Zieschang
      Robert Zieschang
      Blog Post Author

      Hi Timothy Mbogo ,

      glad you achieved your goal. Happy coding.

       

      Regards

      Robert

      Author's profile photo SathishRamanujam R
      SathishRamanujam R

      Hi All,

       

      Kindly guide me to how to put the validation while customer return in Customer Checkout ,i need to throughout the error if the receipt date exists more than one day...or Please guide to retrieve the receipt  date  while picking from receipt details window

       

      Regards,
      Sathish

      Author's profile photo Tomorjin Arildii
      Tomorjin Arildii

      Hi Robert Zieschang

      How can I cancel salesItem in plugin source code(Java)?

      I tried salesItem.setStatus(“3”);. But it doesn’t work.

      I found below method and don't know how to call it.

       

      Author's profile photo Amr Gamgoum
      Amr Gamgoum

      Hi Robert,

      Thank you for the great blog. It was extremely useful as I am starting to write my first plugin in CCO.

      I just have a comment. As you know SAP has released the new UI for retail and quick service and is looking to make this the default interface. The old UI will be depreciated soon as per SAP team.

      The plugin is working fine on the old UI, but not working at all on the new retail/quick service UI. Can you tell me how it can be enabled on the new interface.

      Thanks in advance

      Author's profile photo Robert Zieschang
      Robert Zieschang
      Blog Post Author

      Dear Amr Gamgoum,

      I cant answer this in a short way. Please check the other parts (especially part 5) with the new UI.

      So basically you need to handle the SALESITEM_ADD event in the eventbus. And then you can handle this totally on the UI level.

      When you want to implement this in the backend (java) then the NGUI uses different service classes. E.g. the ReceiptManager is the ReceiptPosService class. In this class there should also be a addSalesItem method available.

      Regards

      Robert

      Author's profile photo Pablo Aaron Mendieta
      Pablo Aaron Mendieta

      Hi Robert!

       

      Thanks in advance for sharing all of this with us,

      I've been following the steps since part 1, however, I'm facing the next issues

       

      - Every time I tried to start the debugger I get the next message

      Failed to connect to remote VM. Connection refused. Connection refused: connect

       

      I look for a solution to this problem on internet and most blogs suggested that this was caused by a wrong port  number, however, I set the same configuration as you did on my run.bat

       

      Do you have any idea what could be the reason for the debugger issue?

       

      - I already built and created my blogplugingpart2.jar (I've got no errors during building and code is the same as the example found in the git repository) file and put it on CCO installation path, I infer material ID is the same as Article ID (right?) so that I try to test but I'm not getting the pop-up window message.

      Here I have my plugin configuration

      Here I have my list of items to test with.

       

      Did I miss something? or Do you have any idea why it may not work?

      Any help will be really appreciated.

      Best Regards!

      Author's profile photo Robert Zieschang
      Robert Zieschang
      Blog Post Author

      Hi Pablo Aaron Mendieta,

      sorry for the late reply.
      I assume your CCO is running on localhost? Did you try to set your debug config to 127.0.0.1?

      Your run.bat also looks fine to me.

      Regards

      Robert

      Author's profile photo Osama Badarneh
      Osama Badarneh

      Hi Robert Zieschang,

      why the debug mode does not work when i change the screen mode from retail mode to any other mode??

      Author's profile photo Robert Zieschang
      Robert Zieschang
      Blog Post Author

      Hi Osama Badarneh,

      what do you mean with "does not work"? Some methods where Plugins can hook in via @PluginAt are different between the old retail UI and the newer QuickService and table mode.

      Maybe you debugging does not work, because the screen mode you are using does not even call the method your plugin hooked in.

      But I would need more information about your code, to verifiy.

      hth

      Robert

      Author's profile photo Hein Aung
      Hein Aung

      Hi Robert,

       

      Thank you for the detailed guide. I've managed to get the pop up message going. However, when I try to debug in eclipse, with localhost and port 4000, I got this error "failed to connect to remote vm. connection refused". I put the two lines in run.bat file from part 1 also. I've tried to put firewall on and off and it is not working. Do you have any suggestion?

      Author's profile photo Robert Zieschang
      Robert Zieschang
      Blog Post Author

      Hi Hein Aung,

      please set the property "suspend" in the line where you configured the remote connection to y. Suspend means, that CCO will stop loading until a remote debugging connection was established. If CCO stops, we can be sure, that this side works.

      Is CCO and your IDE running both on localhost?

      Regards
      Robert

      Author's profile photo Hein Aung
      Hein Aung

      Hi Robert Ziesche,

      I put y in the property suspend and ran the CCO, the console doesn't continue but says "listening for transport dt_socket at address: 4000". After I tried to debug from eclipse,  it started loading and open the login screen on web page for CCO. But the connection still failed for debug. Yes, both IDE and CCO are on my localhost.

      Regards,

      Hein

      Author's profile photo Naw War War Win Shwe
      Naw War War Win Shwe

      Hi Robert Zieschang,

      Please, let me ask one thing, the following plugin method is working in UI Mode of  (Retail (Old UI- Deprecated))  at CCO version 2.14.1 and the release ( 2.0 FP14 PL00) but it does not work in UI mode of (Retail service). Please can you help me with how can I do to work this function?

       

      @PluginAt(pluginClass=IReceiptManager.class, method="addSalesItems", where=POSITION.BEFORE)

      public void checkSalesItem(Object proxy, Object[] args, Object ret, StackTraceElement caller)

      {

      }

      Author's profile photo Naw War War Win Shwe
      Naw War War Win Shwe

      Hi Robert Zieschang,

       

      please can you help me to explain the following plugin method is not working UI mode of Retail service but it works in the UI mode of Retail (Old UI -Deprecated). we are using  CCO release 2.0 FP14 PL00 and the version is 2.14.1? how can I do to work this method in UI mode of Retail service, Please?

       

       

      @PluginAt(pluginClass=IReceiptManager.class, method="addSalesItems", where=POSITION.BEFORE)

      public void checkSalesItem(Object proxy, Object[] args, Object ret, StackTraceElement caller)

      {

      }