Electronic Invoice and the event-driven architecture

In this blog post we are going to propose the use of the message-driven strategy to enable a system agnostic integration that generates electronic invoices based on one of the two event-driven prototypes you probably saw in the posts we published before: the one pushing data out of SAP Business ByDesign, and the other pulling data from that same core ERP.

The message broker cloud service we adopted for this sample is the SAP Cloud Platform Enterprise Messaging, as we previously detailed here.

Business Scenario

The flow is very simple: we want to act on the event’s data generated by the core ERP, e.g. creating an Electronic Invoice every time a Customer Invoice is created on the core ERP (SAP Business ByDesign). As soon as the Tax Authority issues the Electronic Invoice number, we sign the Customer Invoice back in the core ERP with the corresponding signature.


Go ahead and pick up the pulling or pushing approach which fits best your needs to produce messages. The sample code I provide here is the back-end service, named in the picture below as “Partner application“. It offers a way to orchestrate the whole process by:

  1. consuming the customer invoice data from a message on Enterprise Messaging queue;
  2. calling the core ERP to retrieve required data for the specific customer invoice;
  3. requesting the electronic invoice creation, to the tax authority system;
  4. writing back the signature ID in the customer invoice of the core ERP.

Proposed architecture and its main elements:

Very small change in the core ERP

On the left side of the image above you see the invoice document that is created in the SAP Business ByDesign. As a prerequisite we need to add a new element in the business object Customer Invoice to hold the tax authority’s signature that relates to the issued electronic invoice. Below you will find the BODL snippet code to create the new element:

import AP.Common.GDT;
import AP.CustomerInvoicing.Global; [Extension] businessobject AP.CustomerInvoicing.Global:CustomerInvoice raises Message_Cant_Release{ message Message_Cant_Release text "Missing Electronic Invoice"; // You must activate this business object before you can access the extension fields // or messages in script files, forms, and screens. node CustomerInvoice { [Tooltip ("Issued by Tax Authority")] [Label ("Electronic Invoice Signature")] element eInvSignature : ID; }

Please notice in the code above that, besides the additional eInvSignature element, we also defined a message in case you want to handle exceptions and raise it while executing ABSL logic for actions or events of the invoice document.

Message broker

As a new customer invoice is created, the pulling or pushing prototypes will generate a message into a queue on the SCP Enterprise Messaging. The message will look like this:

Note: the message above contains the Invoice Document ObjectID and few more data. We are going to need the ObjectID to query more data required to generate the electronic invoice.

The webhook below, which is subscribed to the queue, will consume the message and trigger the partner application’s endpoint along with the message’s body content, as soon as the it is available in the queue:

Partner Application

Here we explain the orchestrator of the whole process that is consumed by the webhook detailed before.It is developed in Node.js.

It calls the SAP Business ByDesign OData services to retrieve extra data required to generate the electronic invoice, such as amount, taxes, and more:

function callBydGetInv(custInvID, tenant, callback) { console.log( "Enterprise Messaging webhook input data: " + JSON.stringify(custInvID) ); var custInvObjID = custInvID.ObjectUUID || custInvID.ObjectID; var custTenant = tenant || custInvID.SourceTenantHost || process.env.ERP_URL; var uri = custTenant + "/sap/byd/odata/cust/v1/khcustomerinvoice/CustomerInvoiceCollection('" + custInvObjID + "')?$format=json"; console.log("ByD OData URI: " + uri); //Set HTTP Request Options var options = { uri: uri, headers: { Authorization: "Basic " + process.env.BYD_B64AUTH, }, };

Now that the invoice document data is gathered altogether, it’s time to call the Tax Authority service requesting to create an electronic invoice. In this example we are using the Argentinean SOAP web service provided by AFIP, but you may just replace this call to your own country’s tax authority.

Let’s first check what’s the first free number:

function callAFIPGetLast(data, callback) { var params_createInv = require("../params/compUltimo.js"); soap.createClient(urlTransac, function (err, client) { client.FECompUltimoAutorizado(params_createInv, function (err, result) { callback(null, result); }); });

And then actually create the invoice:

function callAFIPCreateInv(data, callback) { var params_createInv = initCrear.initValuesSolicitar(data.newInvNum, data.newInvAmount); soap.createClient(urlTransac, function (err, client) { client.FECAESolicitar(params_createInv, function (err, result) { callback(null, result); }); });

Once the electronic invoice is ready, the service returns a signature and we need to write it back into the field (eInvSignature_SDK) we have created on the Customer Invoice business object in ByD:

function callBydUpdateInv(signature, data, tenant, callback) { var custInvObjID = data.ObjectUUID || data.ObjectID; var custTenant = tenant || data.SourceTenantHost || process.env.ERP_URL; var uri = custTenant + "/sap/byd/odata/cust/v1/khcustomerinvoice/CustomerInvoiceCollection('" + custInvObjID + "')"; //Set HTTP Request Options var options = { uri: uri, headers: { Authorization: "Basic " + process.env.BYD_B64AUTH, "x-csrf-token": "fetch", }, }; //Make Request console.log("Getting ByD token for " + uri); req.get(options, function (error, response) { if (!error && response.statusCode == 200) { var xcsrftoken = response.headers["x-csrf-token"]; var xcsrfcookie = response.headers["set-cookie"]; console.log("Token and cookie retrieved successfully"); body = { eInvSignature_SDK: signature[0].CAE + signature[0].CAEFchVto, }; //Set HTTP Request Options var options = { uri: uri, body: JSON.stringify(body), headers: { "Authorization": "Basic " + process.env.BYD_B64AUTH, "x-csrf-token": xcsrftoken, "Cookie": xcsrfcookie, "x-http-method": "MERGE", "Content-Type": "application/json", }, }; //Make Request console.log("Updating ByD Customer Invoice with AFIPs signature data " + uri);, function (error, response, body) { if (!error && (response.statusCode == 200 || response.statusCode == 204)) { callback(null, response.statusCode); } else { callback(response.statusMessage, response); } }); } });

Additional validations on the core ERP (optional)

You may also finally want to restrict any further changes to the customer invoice document before the electronic invoice is assigned, by using a validation ABSL code such as this one, for the Invoice Document:

import ABSL;
// controls the invoice status and electronic invoice signature
if ((this.eInvSignature.Trim() == "") && (this.Status.ApprovalStatusCode == "3")){ raise Message_Cant_Release.Create("E"); return false;
return true;

That’s all for the electronic invoice sample. Take it as an example and leverage this prototype to the next level.

Stay tuned!