SOAP Web Services
When you upload and run a pipeline with a script node that uses a
WebReference2
object to invoke a WSDL file, Salesforce B2C Commerce
automatically generates classes from the WSDL using Apache CXF.
The generated classes are automatically added to the webreferences2
package. You use the generated classes in a B2C Commerce script to call the web service
operations and process any response from the web service.
B2C Commerce implements SOAP web services through the dw.ws
package
port
, webreference2
, and WSUtil
classes.
Overview
The diagram below assumes you are using the dw.ws
package to implement web
services.
For additional information about transport-layer and application-layer security, see also Web Service Security.
B2C Commerce also maintains a legacy implementation that uses the
webreferences
object and Axis 1.4 to generate classes from the WSDL.
This legacy implementation is deprecated and will be removed in a future release.
Supported Standards
- SOAP 1.1 and SOAP 1.2
- WSDL 1.1
- Apache CXF
- JAX-WS for all features except WS-addressing, attachments, or asynchronous calls.
- https 1.0 and 1.1
SOAP Web Service Implementation
Integrating a web service into your storefront is relatively straightforward.
- Create the following files:
- filename.wsdl - the WSDL file for the web service.
- filename.jks - (optional) if you are using a java keystore file for WS-Security, it must have the same filename as the WSDL.
- filename.wsdl.properties - (optional) if you are using the properties file to generate fully qualified class names to avoid class name collisions.See Resolving Namespace Collisions for WSDLs and Associated Files below.
Note: The filename for all three files must be identical for the files to be used by B2C Commerce. For example, if your WSDL file is namedHelloWorld.wsdl
, then the other files in the directory must be namedHelloWorld.wsdl.properties
, andHelloWorld.jks
.Important: If you have elements in a WSDL schema that contain the underscore character, Webreferences2 can encounter problems during code generation. To resolve this problem, you can use theunderscoreBinding=asCharInWord
property to control code generation. To direct Webreferences2 to use theunderscoreBinding
property, create a properties file in the same directory as the WSDL file. The name of the property file is<wsdl_name>.wsdl.properties
, where<wsdl_name>
is the name of the WSDL file. Also, if you are migrating from Webreferences to Webreferences2, you can expedite the process by directing Webreferences2 to generate arrays instead of lists. To specify that you want to use arrays instead of lists, setcollectionType=indexed
in the<wsdl_name>.wsdl.properties
properties file. - Place the files in the
webreferences2
folder in your cartridge. -
Add a service definition to your service registry script for the SOAP web service.
Example; Simple SOAP ServiceDefinition{ initServiceClient: function() { // Storing this in a custom attribute so it can be used later this.webReference = webreferences.LuhnChecker; // The return here is the 'svc.serviceClient' in subsequent calls return webReference.getDefaultService(); } createRequest: function(svc:SOAPService, param1) { var requestObject = new this.webReference.CheckCC(); requestObject.setCardNumber(param1); return requestObject; }, execute: function(svc:SOAPService, requestObject) { return svc.serviceClient.checkCC(requestObject); }, parseResponse: function(svc:SOAPService, responseObject) { var responseWrapper = {}; responseWrapper.type = responseObject.checkCCResult.cardType; responseWrapper.valid = responseObject.checkCCResult.cardValid; return responseWrapper; } }
The ServiceDefinition must specify:
-
initServiceClient callback to get the following using the
dw.ws
package:- a
WebReference2
object - a service port
- WSDL request parameters
This callback must also set the serviceClient property for the Service.
The webreference2 object is required to generate the classes for the WSDL, which you must use to call the web service. When you have created this callback you can call the pipeline to generate the API for the WSDL. You might want to skip to the steps where you develop the pipeline and run it, so that you have the WSDL API available, and then finish the task of creating the service definition.
- a
-
createRequest callback to set the WebReferences2 method to call and return a requestData object. If you are using WS-Security, this is where you will construct the security hashmap.
- To implement transport layer security for TLS/SSL, add the certificates that you want to use for your SSL connection, to your instance using Business Manager. See Importing Certificates for an Instance.
- To implement WS-Security to sign, encrypt or decrypt soap
messages:
- for a JAX-WS web service, using the
dw.ws
package:- Get a certificate from a known certificate authority or trusted provider to sign the SOAP message to the web service. You must provide a keystore for the WS-Security actions: Signature, Encryption, and Decryption.
- Place the keystore in the webreferences2 folder in the
same cartridge as the WSDL file.Note: The name of the keystore must be the same as the WSDL file and the file extension must be that of the keystore type (jks or pkcs12). For example, for the
CheckFraud.WSDL
service and a pkcs12 keystore type, the keystore must be namedCheckFraud.pkcs12
. -
In your B2C Commerce script that invokes the web service, create a request and response security configuration. The security configuration is a HashMap, whose first element defines the actions you want B2C Commerce to take: whether to add a timestamp, encrypt the message, sign the message, or other actions. The other elements in the HashMap assign a value to constants defined for the WSUtil class.
Create a separate security configuration for the request messages and the response messages and pass them both into the
setWSSecurityConfig(port : Object, requestConfigMap : Map, responseConfigMap : Map)
class to set the request and response security configuration for the web service.Example:
// define a map with all the secrets var secretsMap : Map = new HashMap(); secretsMap.put("myclientkey", "ckpass"); secretsMap.put("myservicekey", "ckpass"); secretsMap.put("username", "password"); var requestCfg : Map = new HashMap(); // define the ws actions to be performed - in this case add a username token, timestamp, // sign and encrypt the message requestCfg.put(WSUtil.WS_ACTION, WSUtil.WS_USERNAME_TOKEN + " " + WSUtil.WS_TIMESTAMP + " " + WSUtil.WS_SIGNATURE + " " + WSUtil.WS_ENCRYPT); requestCfg.put(WSUtil.WS_USER, "username"); requestCfg.put(WSUtil.WS_PASSWORD_TYPE, WSUtil.WS_PW_DIGEST ); requestCfg.put(WSUtil.WS_SIG_DIGEST_ALGO, "http://www.w3.org/2001/04/xmlenc#sha256" ); // define signature properties // the keystore file has the basename of the WSDL file and the // file extension based on the keystore type (for example, HelloWorld.jks). // The keystore file has to be placed beside the WSDL file. requestCfg.put(WSUtil.WS_SIG_PROP_KEYSTORE_TYPE, "jks"); requestCfg.put(WSUtil.WS_SIG_PROP_KEYSTORE_PW, "cspass"); requestCfg.put(WSUtil.WS_SIG_PROP_KEYSTORE_ALIAS, "myclientkey"); requestCfg.put(WSUtil.WS_SIGNATURE_USER, "myclientkey"); // define enrcryption properties requestCfg.put(WSUtil.WS_ENC_PROP_KEYSTORE_TYPE, "jks"); requestCfg.put(WSUtil.WS_ENC_PROP_KEYSTORE_PW, "cspass"); requestCfg.put(WSUtil.WS_ENC_PROP_KEYSTORE_ALIAS, "myservicekey"); requestCfg.put(WSUtil.WS_ENCRYPTION_USER, "myservicekey"); requestCfg.put(WSUtil.WS_SIGNATURE_PARTS, "{Element}{http://schemas.xmlsoap.org/soap/envelope/}Body"); requestCfg.put(WSUtil.WS_ENCRYPTION_PARTS,"{Element}{" + WSU_NS + "}Timestamp;"+"{Content}{http://schemas.xmlsoap.org/soap/envelope/}Body"); // set the secrets for the callback requestCfg.put(WSUtil.WS_SECRETS_MAP, secretsMap); var responseCfg : Map = new HashMap(); // define the ws actions to be performed for the response responseCfg.put(WSUtil.WS_ACTION, WSUtil.WS_TIMESTAMP + " " + WSUtil.WS_SIGNATURE + " " + WSUtil.WS_ENCRYPT); // define signature properties responseCfg.put(WSUtil.WS_SIG_PROP_KEYSTORE_TYPE, "jks"); responseCfg.put(WSUtil.WS_SIG_PROP_KEYSTORE_PW, "cspass"); responseCfg.put(WSUtil.WS_SIG_PROP_KEYSTORE_ALIAS, "myservicekey"); responseCfg.put(WSUtil.WS_SIGNATURE_USER, "myservicekey"); // define decryption properties responseCfg.put(WSUtil.WS_ENC_PROP_KEYSTORE_TYPE, "jks"); responseCfg.put(WSUtil.WS_ENC_PROP_KEYSTORE_PW, "cspass"); responseCfg.put(WSUtil.WS_ENC_PROP_KEYSTORE_ALIAS, "myclientkey"); responseCfg.put(WSUtil.WS_ENCRYPTION_USER, "myclientkey"); // set the secrets for the callback responseCfg.put(WSUtil.WS_SECRETS_MAP, secretsMap); // set the security WSUtil.setWSSecurityConfig(port, requestCfg, responseCfg);
Note: You must create a pipeline, even if you intend to call the script using a hook for an OCAPI application, because you must run the pipeline to generate the classes for the WSDL. To generate the classes, the script must get awebreferences2
object. When the classes are generated, however, you can call the script using an OCAPI hook. However, if the WSDL changes and you need to generate a new version of the classes, you must run the pipeline again. - for a JAX-WS web service, using the
- execute callback to make the service call.
- parseResponse to parse the result of the call.
-
- Make sure your cartridge is in the path for your instance and upload the cartridge to the server. This initializes the Service objects, so that they can be accessed when calling web services.
- Develop a pipeline to call the web service and create a script node that points to your web service script.
- Execute the pipeline in the storefront, either by navigating to a page that calls
the pipeline or by calling the pipeline manually, by adding the pipeline to the
end of the URL after the site name. For example:
www.mycompany.com/default/Pipeline-Start
- Download the classes generated by B2C Commerce in Studio. Click , select
your server connection, select CommerceCloudServer, and
then Download Web Service API. Note: You must execute the pipeline at least once to generate the classes.Tip: You must create a pipeline, even if you intend to call the script using a hook for an OCAPI application, because you must run the pipeline to generate the classes for the WSDL. To generate the classes, the script must get a
webreferences2
object. When the classes are generated, however, you can call the script using an OCAPI hook. However, if the WSDL changes and you need to generate a new version of the classes, you must run the pipeline again.Important: TheWebReference2
script class generation process uses the name of the WSDL service and port elements to create a class representing the service or port. If the service or port name contains an underscore character, the generated class name might or might not contain the underscore character based on naming rules used in the code generation process. Regardless of this, callingWebReferences2.getDefaultService()
orWebReferences2.getService(String, String)
resolves the service and port element names to the corresponding script classes. - Create an ISML template to show the results of the web service and trigger a page refresh when the information shown changes.
- Troubleshoot any errors using the request log and customerror development logs. See also Troubleshooting Web Services below.
Application-Layer Security with X509 Certificates for Signing and Encryption
Resolving Namespace Collisions for WSDLs and Associated Files
When calling a web service from a script file, B2C Commerce generates
dw.ws.Port
and supporting classes, using the default namespace
webreferences2.<wsdl_file_name>.
If your web service WSDL has many different types with the same name, compilation errors
can occur because the type classes are all put in the same namespace package. To resolve
this issue, you can specify that you want B2C Commerce to generate a namespace-aware
Port
and supporting classes, by including a properties file in the
same location as your WSDL file. In this properties file, you specify the property as
follows:
namespace=true
For B2C Commerce to apply namespace support, the properties file name must be specified as follows:
<wsdl_file_name>.wsdl.properties
For example, if your WSDL file is HelloWorld.wsdl
, the properties file
must be HelloWorld.wsdl.properties
and it must be placed in the same
webreferences2
directory as the WSDL file itself.
In your B2C Commerce script file, create objects using the WebReference2 class. For
example, if the namespace for HelloWorld.wsdl
is
com.test.wsdl
, creating objects for the namespace requires the
qualified name to be:
var webRef : WebReferences2 = webreference2.HelloWorld;
var request : new webRef.com.test.wsdl.HelloRequest();
var svc = webRef.getDefaultService();
var response = webRef.hello(request);
Troubleshooting Web Services
Q: I am seeing an error that references security headers and is similar to the following: "com.ibm.wsspi.wssecurity.SoapSecurityException: WSEC....."
A: The request requires a username and password token in the request header. Make sure when constructing this request that the correct credentials (as provided by the web service being connected to) are included.
Q: I am seeing an error that references 'FaultMessage', however, everything looks syntactically correct. What is wrong and how can I fix it?
_Elemen
t' suffix. What this means is that, for a WSDL element
'fmt_FaultMessage
' there is a class with the name
'Fmt_FaultMessage_Element.java
' created (when executing wsdl2java).
The WSDL and code to reference this must be adjusted accordingly. To do this, modify the
WSDL to change all references to (in this example) 'fmt_FaultMessage
':
<xsd:element name="fmt_FaultMessage"> <wsdl:message name="fmt_FaultMessage">
Q: I am seeing an error that the WSDL file can't be located in B2C Commerce, but I know the WSDL file is there. The error is: "Script exception in line 42:org.mozilla.javascript.EcmaError: ReferenceError: WSDL file for webreference2 'example' doesn't exist"
A: Make sure the that WSDL file is in the cartridgeβs webreferences2
directory and make sure the pipeline executing/calling it's in the same cartridge (as
the WSDL file).
Q: I am having difficulty invoking a method that expects an ArrayOf type as a parameter, how can I get this to work?
A: To pass an ArrayOf structure as a parameter to a method in a script, one should
follow the following conventions (keeping in mind that the exact implementation will
vary depending on your specific code/logic):
request.setSomething([theArray]);
or
request.setSomething(new Array(x))
;
Q: I am trying to integrate with a web service that has fields defined as dateTime and keep getting this error: "Cannot convert [Calendar id=19206138] to java.util.Calendar "
A: All variables have setter methods. Salesforce recommends always using setter methods. However, when using primitive types it isn't necessary, but by using setters you will always be safe.
Q: I am running into some data-type specific issues. Which data-types are not supported?
- NonNegativeInteger
- NonPositiveInteger
- UnsignedByte
- UnsignedInt
- UnsignedLong
- UnsignedShort
Tip for Accessing Elements with the Name 'Return'
<element name="NewOrderResponse">
<complexType>
<sequence>
<element name="return" type="ns:NewOrderResponseElement" minOccurs="1" maxOccurs="1"/>
</sequence>
</complexType>
</element>
response = service.newOrder(newOrder);
var result = response.['_return'];
With an underscore in the name, you can access all the properties in the object.
Errors shown for names without an Underscore
Attempting to access the element causes some of the following errors:
Example 1:
response = service.newOrder(newOrder); var result = response.return;
Throws
the following
error:
org.mozilla.javascript.EvaluatorException: missing
response = service.newOrder(newOrder);
Throws the following
error:
var result = response.['return']; Unknown dynamic property 'return' for class