soapcoldfusioncoldfusion-11cfinvoke

How to convert a CFHTTP request with SOAP body in to a CFINVOKE request


I have a ColdFusion 11 site that need to communicate with a payment processor using a SOAP request. I have gotten it to work just fine with a CFHTTP request, but the return value (delivered in a SOAP message) is a bit difficult to work with.

Looking for something better, I came across CFINVOKE and the webservice attribute. It appears I am correctly calling the webservice method, but I think the data I am passing to the service is not in the correct format.

This is the working SOAP/CFHTTP request ("..." replaces sensitive implementation details):

<!--- build SOAP data --->
<cfsavecontent variable="soapBody">
    <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
        <s:Body>
            <PreparePaymentv2 xmlns="https://common.checkout.cdc.nicusa.com">
                <request xmlns:a="http://schemas.datacontract.org/2004/07/Common.Payment.Common" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
                    <a:STATECD>...</a:STATECD>
                    <a:HASHVALUE i:nil="true"/>
                    <a:AMOUNT i:nil="true"/>
                    <a:CID i:nil="true"/>
                    <a:SERVICECODE>...</a:SERVICECODE>
                    <a:UNIQUETRANSID>...</a:UNIQUETRANSID>
                    <a:DESCRIPTION>...</a:DESCRIPTION>
                    <a:LOCALREFID>...</a:LOCALREFID>
                    <a:MERCHANTID>...</a:MERCHANTID>
                    <a:MERCHANTKEY>...</a:MERCHANTKEY>
                    <a:PAYTYPE i:nil="true"/>
                    <a:NAME>...</a:NAME>
                    <a:COMPANYNAME i:nil="true"/>
                    <a:COUNTRY>...</a:COUNTRY>
                    <a:FAX i:nil="true"/>
                    <a:ADDRESS1 >...</a:ADDRESS1>
                    <a:ADDRESS2 i:nil="true"/>
                    <a:CITY>...</a:CITY>
                    <a:STATE>...</a:STATE>
                    <a:ZIP>...</a:ZIP>
                    <a:PHONE>...</a:PHONE>
                    <a:EMAIL>...</a:EMAIL>
                    <a:EMAIL1 i:nil="true"/>
                    <a:EMAIL2 i:nil="true"/>
                    <a:EMAIL3 i:nil="true"/>
                    <a:HREFSUCCESS>...</a:HREFSUCCESS>
                    <a:HREFFAILURE>...</a:HREFFAILURE>
                    <a:HREFDUPLICATE>...</a:HREFDUPLICATE>
                    <a:HREFCANCEL>...</a:HREFCANCEL>
                    <a:ORDERATTRIBUTES i:nil="true"/>
                    <a:LINEITEMS>
                        <a:LINEITEM>
                            <a:ITEM_ID>...</a:ITEM_ID
                            <a:SKU>...</a:SKU>
                            <a:DESCRIPTION>...</a:DESCRIPTION>
                            <a:UNIT_PRICE>...</a:UNIT_PRICE>
                            <a:QUANTITY>....</a:QUANTITY>
                            <a:ATTRIBUTES/>
                        </a:LINEITEM>
                    </a:LINEITEMS>
                    <a:ALTNAME i:nil="true"/>
                    <a:ALTADDRESS1 i:nil="true"/>
                    <a:ALTADDRESS2 i:nil="true"/>
                    <a:ALTCITY i:nil="true"/>
                    <a:ALTSTATE i:nil="true"/>
                    <a:ALTZIP i:nil="true"/>
                    <a:ALTCOUNTRY i:nil="true"/>
                </request>
            </PreparePaymentv2>
        </s:Body>
    </s:Envelope>
</cfsavecontent>

<!--- submit info to SOAP endpoint --->
<cfhttp url="https://stageccp.dev.cdc.nicusa.com/CommonCheckout/CCPWebService/ServiceWeb.svc" method="post" result="httpResponse">
    <cfhttpparam type="header" name="content-type" value="text/xml">
    <cfhttpparam type="header" name="SOAPAction" value="https://common.checkout.cdc.nicusa.com/IServiceWeb/PreparePaymentv2">
    <cfhttpparam type="header" name="content-length" value="#len(soapBody)#">
    <cfhttpparam type="header" name="charset" value="utf-8">
    <cfhttpparam type="header" name="Accept-Encoding" value="*">
    <cfhttpparam type="Header" name="TE" value="deflate;q=0">
    <cfhttpparam type="xml" value="#soapBody#">
</cfhttp>

This is the CreateObject / CFINVOKE request I am trying to get to work:

<cfscript>
    nullValue = "";
    args = {
        STATECD = ...,
        HASHVALUE = nullValue,
        AMOUNT = nullValue,
        CID = nullValue,
        SERVICECODE = ...,
        UNIQUETRANSID = ...,
        DESCRIPTION = ...,
        LOCALREFID = ...,
        MERCHANTID = ...,
        MERCHANTKEY = ...,
        PAYTYPE = nullValue,
        NAME = ...,
        COMPANYNAME = nullValue,
        COUNTRY = ...,
        FAX = nullValue,
        ADDRESS1 = ...,
        ADDRESS2 = ...,
        CITY = ...,
        STATE = ...,
        ZIP = ...,
        PHONE = ...,
        EMAIL = ...,
        EMAIL1 = nullValue,
        EMAIL2 = nullValue,
        EMAIL3 = nullValue,
        HREFSUCCESS = ...,
        HREFFAILURE = ...,
        HREFDUPLICATE = ...,
        HREFCANCEL = ...,
        ORDERATTRIBUTES = nullValue,
        LINEITEMS = [
            {
                ITEM_ID = ...,
                SKU = ...,
                DESCRIPTION = ...,
                UNIT_PRICE = ...,
                QUANTITY = ...,
                ATTRIBUTES = nullValue
            }
        ],
        ALTNAME = nullValue,
        ALTADDRESS1 = nullValue,
        ALTADDRESS2 = nullValue,
        ALTCITY = nullValue,
        ALTSTATE = nullValue,
        ALTZIP = nullValue,
        ALTCOUNTRY = nullValue
    };

    ws = CreateObject("webservice","https://stageccp.dev.cdc.nicusa.com/CCPWebService/ServiceWeb.wsdl");
    ws.PreparePaymentv2(args);
    response = getSOAPResponse(ws);
</cfscript>

<cfinvoke webservice="https://stageccp.dev.cdc.nicusa.com/CCPWebService/ServiceWeb.wsdl" method="PreparePaymentv2" request="#args#" returnvariable="response">

Both of these strategies return the error message:

Cannot perform web service invocation PreparePaymentv2.

Looking at the details reveals an extended stack trace, which I reason is from the remote server, containing this message near the top of the trace:

 Object reference not set to an instance of an object.

Edit: the full error Detail is as follows:

The fault returned when invoking the web service operation is: org.apache.axis2.AxisFault: Object reference not set to an instance of an object. at org.apache.axis2.util.Utils.getInboundFaultFromMessageContext(Utils.java:531
 at org.apache.axis2.description.OutInAxisOperationClient.handleResponse(OutInAxisOperation.java:375
 at org.apache.axis2.description.OutInAxisOperationClient.send(OutInAxisOperation.java:421
 at org.apache.axis2.description.OutInAxisOperationClient.executeImpl(OutInAxisOperation.java:229
 at org.apache.axis2.client.OperationClient.execute(OperationClient.java:165
 at com.nicusa.cdc.checkout.common.ServiceWebStub.preparePaymentv2(ServiceWebStub.java:217
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method
 at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source
 at java.lang.reflect.Method.invoke(Unknown Source
 at coldfusion.xml.rpc.Axis2ServiceProxy.getResult(Axis2ServiceProxy.java:112
 at coldfusion.xml.rpc.ServiceProxy.invokeImpl(ServiceProx... 

And the full stack trace:

coldfusion.xml.rpc.ServiceProxy$ServiceInvocationException: Cannot perform web service invocation PreparePaymentv2. at coldfusion.xml.rpc.Axis2ServiceProxy.getResult(Axis2ServiceProxy.java:122
 at coldfusion.xml.rpc.ServiceProxy.invokeImpl(ServiceProxy.java:469
 at coldfusion.xml.rpc.ServiceProxy.invoke(ServiceProxy.java:413
 at coldfusion.runtime.CfJspPage._invoke(CfJspPage.java:2489
 at cfpayport_redirect2ek2648755446._factor4(C:\websites\preceptorStudent\test\payport_redirect.k2:312
 at cfpayport_redirect2ek2648755446._factor5(C:\websites\preceptorStudent\test\payport_redirect.k2:120
 at cfpayport_redirect2ek2648755446.runPage(C:\websites\preceptorStudent\test\payport_redirect.k2:1
 at coldfusion.runtime.CfJspPage.invoke(CfJspPage.java:247
 at coldfusion.tagext.lang.IncludeTag.handlePageInvoke(IncludeTag.java:736
 at coldfusion.tagext.lang.IncludeTag.doStartTag(IncludeTag.java:572
 at coldfusion.filter.CfincludeFilter.invoke(CfincludeFilter.java:65
 at coldfusion.filter.IpFilter.invoke(IpFilter.java:45
 at coldfusion.filter.ApplicationFilter.invoke(ApplicationFilter.java:474
 at coldfusion.filter.RequestMonitorFilter.invoke(RequestMonitorFilter.java:42
 at coldfusion.filter.MonitoringFilter.invoke(MonitoringFilter.java:40
 at coldfusion.filter.PathFilter.invoke(PathFilter.java:153
 at coldfusion.filter.LicenseFilter.invoke(LicenseFilter.java:30
 at coldfusion.filter.ExceptionFilter.invoke(ExceptionFilter.java:94
 at coldfusion.filter.ClientScopePersistenceFilter.invoke(ClientScopePersistenceFilter.java:28
 at coldfusion.filter.BrowserFilter.invoke(BrowserFilter.java:38
 at coldfusion.filter.NoCacheFilter.invoke(NoCacheFilter.java:58
 at coldfusion.filter.GlobalsFilter.invoke(GlobalsFilter.java:38
 at coldfusion.filter.DatasourceFilter.invoke(DatasourceFilter.java:22
 at coldfusion.filter.CachingFilter.invoke(CachingFilter.java:62
 at coldfusion.filter.RequestThrottleFilter.invoke(RequestThrottleFilter.java:151
 at coldfusion.CfmServlet.service(CfmServlet.java:219
 at coldfusion.bootstrap.BootstrapServlet.service(BootstrapServlet.java:89
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208
 at coldfusion.monitor.event.MonitoringServletFilter.doFilter(MonitoringServletFilter.java:42
 at coldfusion.bootstrap.BootstrapFilter.doFilter(BootstrapFilter.java:46
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208
 at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219
 at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:110
 at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:494
 at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:169
 at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:104
 at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116
 at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:458
 at org.apache.coyote.ajp.AjpProcessor.process(AjpProcessor.java:196
 at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:637
 at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:316
 at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source
 at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source
 at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61
 at java.lang.Thread.run(Unknown Source)

My best guess is this has something to do with the data passed in. I have also tried omitting any parameter from args that is set to nullValue. This results in the same error message. I realize using an empty string for nullValue is not optimal; I have not found a better alternative for ColdFusion 11.


Solution

  • the return value (delivered in a SOAP message) is a bit difficult to work with.

    Is there a specific problem you're having? Usually you just parse the response as XML and use structure notation to access the desired nodes:

    <cfset root = xmlParse(httpResponse.fileContent)>
    <cfdump var="#root#" label="HttpResponse">
    <cfoutput>FaultCode = #root.Envelope.Body.Fault.FaultCode.xmlText#</cfoutput>
    

    Typically that's easier than working with createObject("webservice"). You've already encountered the biggest issue, namely that troubleshooting web service errors can be a painful process ;-). Error messages tend to be vague, with the most common being "Web service operation SomeMethod with parameters {...some params...} cannot be found.". Not particularly informative. All it says is that something's wrong with the arguments but doesn't provide any clue about which argument(s), what's wrong or how to fix it.

    Granted, you can usually get things working ... eventually. However, if it's a complex web service, that's not always worth the time and effort, which is why cfhttp is often recommended instead.

    Looking at the details reveals an extended stack trace, which I reason is from the remote server, containing this message near the top of the trace: Cannot perform web service invocation PreparePaymentv2.

    Anyway, if you still want to try cfinvoke, there are least three problems with the arguments:

    1. Not all of the elements are strings. So using nullValue = "" causes an error when the empty string can't be converted into the correct type. That's what happens with ORDERATTRIBUTES. Since it's optional, if you don't want to pass a value for that key, just omit it from the args structure altogether.

    2. LINEITEMS isn't an array. It's a structure containing a single key named "lineitem", which IS an array. So the value should be constructed like this:

       LINEITEMS.LINEITEM = [ {... lineitem data ... } ]
      
    3. The same goes for ORDERATTRIBUTES. It's a structure containing a key named "field", that's an array of structures:

      ORDERATTRIBUTES.FIELD = [ {...field data ... } ]
      

    If you're interested in learning more about how web services work internally, CF's functionality is based on Axis which uses a tool called wsdl2java to translate the WSDL into java classes. You can discover a lot about how the arguments should be structured by saving and reviewing the files it generates. That's how I figured out the issues above. The saved files will be located in the cfusion/stubs subdirectory.

     // Only need to do this once. DON'T use in Prod as it's very time consuming
     ws = CreateObject("webservice","https://example.com/ServiceWeb.wsdl"
         , {saveJava=true, refreshWSDL=true});