Recently, I ran into one of those projects that makes you question your own sanity. Where the documentation is limited, and everything you try should just work but it doesn’t. It required integrating Craft Commerce orders with a client’s instance of Microsoft AX — and we’re here today to document how it all goes down. May fewer folks suffer as we did.
Let’s start with the good bits — what you need to get up and running with Craft CMS and Microsoft AX. Then we’ll take a dip into memory lane to talk about what didn’t work and why.
The star of this show is Microsoft Dynamics AX, the antiquated younger sibling to Microsoft’s Dynamics 365. It’s an ERP system from pre-2018 that relies on .NET code to facilitate global enterpises’ product lines. For our client, this means direct to consumer (B2C) and direct to distributor (B2B) sales.
Looking to integrate with Dynamics 365? We’d recommend checking out Solspace’s article on using Smart Connect as a middle layer instead.
Playing an equally important role is Craft CMS, along with the 1st party Craft Commerce plugin. The plugin really has no bearing on the integration portion of this build — but the system hooks it provides are what allow you to pull order data aside to send it over to AX. There’s many guides for extending commerce functionality, so we won’t be getting into that here.
Supporting our stars are SOAP, the XML based API that we’ll need as our middleman between Craft and AX, in addition to a php library from Sabre that we’ll be using to write our highly custom XML data.
There’s three main aspects for writing the integration between PHP and the SOAP endpoint for Microsoft AX that you’ll need to tackle.
First, is ensuring your AX equivalent (the client’s AX expert) has setup the system to have a web service and corresponding WSDL. You’ll need the URL for this web service, including it’s port. The default port for AX is 8201 but it’s not uncommon for that to be customized by the client’s AX team. You should have something in the format of:
What’s a WSDL? If you’re not familiar with SOAP, the WSDL file is both your best friend and arch-nemesis. It contains everything that SOAP needs to know about the service and its functions, including necessary formatting and parameters.
You should also ask your counterpart for the exact properties they need to have sent, which ones are required, and what data type they expect. Even better if they can send an XML sample that’s already known to work.
Authentication varies, but we wrote ours relying on basic auth, using credentials given to us by our client. Keep in mind that this is a Microsoft product, so you’ll need the domain prefix — DOMAIN\username — for it to be recognized by the AX system.
Next, you need to understand AX namespacing. There’s three namespaces involved with a call: SOAP Envelope, MS General AX Services, and the specific AX Service for the call.
- The first is only used with the SOAP envelope in our XML and is part of general SOAP API usage: http://schemas.xmlsoap.org/soap/envelope/
* The trailing slash is required to avoid a 400 “Bad Request” Error
- The second namespace is for AX services and is typically: http://schemas.microsoft.com/dynamics/2008/01/services
- The third is specific to the service being used. For the SalesOrder service: http://schemas.microsoft.com/dynamics/2008/01/documents/SalesOrder
Third, you’ll need to understand how to use Sabre’s XML library to successfully achieve the required nesting. From the Sabre docs, we’re provided with the basic structure needed to form proper XML.
Sabre allows us to define the namespace(s) needed in our XML, and then uses the namespace and defined properties within it’s XML service. We can nest XML using nested arrays, as shown above. In addition to the basic setup, we’ll also need to add a class to some of our XML. Sabre tackles this with a more verbose declaration of properties:
With this setup, the properties needing a class attribute can be defined with the verbose array syntax. For nested values, an additional parameter called “value” is also accessible within the service. Note that each XML property is prefixed by a namespace — we’ll need to tackle several in our AX model. You can also use the namespaceMap array to declare aliases for your defined namespaces.
Ultimately, we want to achieve the following XML format being sent over to Microsoft AX (we trimmed out a number of fields, for the sake of brevity):
TIP: Do yourself a favour and hardcode this integration with known working data, before you start pulling in Craft data.
To achieve it, we’ll need to define our three namespaces and give them aliases. We’ll also want our XML root to be “Envelope” as we’ll need Sabre to write the entire XML Request. Using it inside a craft module, the entire data declaration looks like this:
TIP: Order does matter. Microsoft wants these properties alphabetically, and deviations from the WSDL will result in missing data on delivery. Sabre wants your namespaces listed in the order by which they first appear, make sure your aliases are in the same order as your url declarations.
Next, it’s time to format some cURL so that we can send our freshly packaged data. This is where the service URL we mentioned ages ago will come into play. You’ll also need to declare the Soapaction, which should match the namespace we used in our data, can be found in the WSDL, and can also be found in the headers section of any example request you’ve been given.
And there you have it. Provided you don’t run into any authentication issues on the client’s side, you should receive a victory response in the form of a Sales Order number. You can download the full code for our Craft module controller here.
Before arriving at some working code — there were a few other methods we tried and a few “gotchas” we had to work through. With the lack of documentation we could find, it made sense to include some failures alongside the working model.
Bad Idea #1: Using SOAPClient Methods
There’s a few things to note re: how SOAPClient tries to format and send data, to understand why this method flopped for us.
- If the data format does not match the WSDL, SOAPClient will strip out the data. You can tell this is happening if the AX side receives an empty body, or by investigating your request data with a traffic sniffing tool like Wireshark.
- SOAPClient doesn’t (seem) to handle multiple namespaces in requests, nor the ability to add classes to specific XML properties. You’ll need to add
class = "entity"to several properties, and we never found a way to do it.
- It’s really hard to see what SOAPClient calls are creating once you’ve handed them the data. Outputting the data you create prior to the call doesn’t help see the whole request, and if it’s malformed the data is stripped out before you can view requests in a traffic sniffer or with SOAPClient’s getLastRequest call.
Bad Idea #2: Sending Raw XML Strings
Theoretically, you could skip the Sabre XML step and hardcode an XML string to send over to AX. We wouldn’t recommend doing so due to how messy parsing a string like that quickly becomes. Sabre formats the XML from a key-value pair, which gives us plenty of room to sort out our craft data and then sort it into the array. It also adds in some additional data you’d need to write in yourself otherwise.
Code maintainability is important, and this code’s complex enough to begin with. Making sure it stays clean and redable will go a long way in making sure others understand what’s going on and how to modify it.
Bad Idea #3: Redundant XML Headers
Don’t attempt to wrap the Sabre data in additional XML. Since Sabre creates the opening XML lines, it’ll just cause the code to error. Everything you need for the XML call needs to be included in the Sabre write function, and as far as we’re aware that means you can’t concatenate Sabre outputs either.
Bad Idea #4: Underestimating how specific AX is
Microsoft AX will reject any data with vague errors if you don’t format it in the order and data types it expects. Once you have some code in place, it’s best to work with your counterpart on the AX side to have them read any internal errors and assist with debugging.
Best of Luck!
There you have it — PHP and XML to get your Craft website speaking with Microsoft AX. This solution works in both Craft CMS and with Craft Commerce, so long as you can provide the required data from whichever system you’re using. Technically speaking — this method should also work in any PHP based application, if you try it in Laravel let us know how it goes!
Want to work with us? Send your project details and budget to firstname.lastname@example.org