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.
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:
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.
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 ... } ]
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});