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. This implementation of web services is available in B2C Commerce as of version 14.4.

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 isn't recommended for development of new web services. However, it does support RPC-style web services, while the current implementation does not. See also Current vs. Legacy Web Service Implementation

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.

  1. 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 named HelloWorld.wsdl, then the other files in the directory must be named HelloWorld.wsdl.properties, and HelloWorld.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 the underscoreBinding=asCharInWord property to control code generation. To direct Webreferences2 to use the underscoreBinding 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.
  2. Place the files in the folder required by the type of service:
    • for a document-style service (also called message-style or document/literal), place the files in the webreferences2 folder in your cartridge.
    • for an RPC-style service, place the files in the webreferences folder in your cartridge.
  3. 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:

      • for a JAX-WS web service, using the dw.ws package:
        • a WebReference2 object
        • a service port
        • WSDL request parameters
      • for a JAX-RCP web service, using the dw.rpc package:
        • a webreference2 object
        • a service stub
        • WSDL request parameters

      This callback must also set the serviceClient property for the Service.

      The webreference/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.

    • 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.
      1. 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.
      2. To implement WS-Security to sign, encrypt or decrypt soap messages:
        • for a JAX-WS web service, using the dw.ws package:
          1. 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.
          2. 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 named CheckFraud.pkcs12.
          3. 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);
        • for a JAX-RCP web service, using the dw.rpc package:
          1. 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.
          2. Place the keystore in the webreferences 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 named CheckFraud.pkcs12.
          3. If you have additional certificates that you want to use for your SSL connection, add them to your instance using Business Manager. See Importing Certificates for an Instance.
          4. In your B2C Commerce script that invokes the web service, use the static setWSSecurityConfig(port : Object, requestConfigMap : Map, responseConfigMap : Map) : void;
            • Define a HashMap and put your client key and password, service key and password, username and password into it.
            • Define a HashMap for the web service request that configures the actions of the interceptor actions when a call is made to the web service. Interceptor actions are defined through methods in the SOAPUtil class.
            • Define a HashMap for the web service response that configures the interceptor actions for the return message.
            • Uses the SOAPUtil.setWSSecurityConfig method to configure the actions on the server
        Note: You must create a pipeline, even if you intend to call the script using a hook for an OC API 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 OC API hook. However, if the WSDL changes and you need to generate a new version of the classes, you must run the pipeline again.
    • execute callback to make the service call.
    • parseResponse to parse the result of the call.
  4. In your package.json file, add a line for the service registry script.
  5. Make sure your cartridge is in the path for your instance and upload the cartridge to the server. This initializes the ServiceDefinitions objects in the service registry, so that they can be accessed when calling web services. The web services are then registered with B2C Commerce.
  6. Develop a pipeline to call the web service and create a script node that points to your web service script.
  7. 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
  8. 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 OC API application, because you must run the pipeline to generate the classes for the WSDL. To generate the classes, the script must get a webreferences2 or webreferences object. When the classes are generated, however, you can call the script using an OC API hook. However, if the WSDL changes and you need to generate a new version of the classes, you must run the pipeline again.
    Important: The WebReference2 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, calling WebReferences2.getDefaultService() or WebReferences2.getService(String, String) resolves the service and port element names to the corresponding script classes.
  9. Create an ISML template to show the results of the web service and trigger a page refresh when the information shown changes.
  10. Troubleshoot any errors using the request log and customerror development logs. See also Troubleshooting Web Services below.
See also Web Service Security.

Current vs. Legacy Web Service Implementation

B2C Commerce maintains the legacy implementation of web services for backwards compatibility.

Differences in implementing services:

  • web service files are placed in different directories. Legacy implementation files are placed in the webreferences folder instead of the webreferences2 folder.

The legacy web service implementation supports the following standards:

Application-Layer Security with X509 Certificates for Signing and Encryption

Some web service integrations require the use of public key infrastructure (PKI) for application-layer web security, in which any messages exchanged are signed or encrypted to ensure that messages are not tampered with. A web service might also require secure communication with certificates issued by a known certificate authority, such as VeriSign, GoDaddy, or Comodo.
Note: X509 certificates are used for application layer security. See also Web Service Security.

Setting the HTTP Version for RPC-Style Services

If you are making RPC-style web service calls from WSDL files in the webreferences folder, the web service framework uses HTTP 1.0 when making the SOAP request by default. Some web services don't allow HTTP 1.0, so there is now a property you can set to direct the web services framework to use HTTP 1.1. The property is named 'http_version'. To use this property, first create a properties file and put it in the same webreferences folder as your WSDL file. The properties file must have the same name as your WSDL file, but with a .properties extension. For example, if your WSDL file is named PayPal.wsdl, the properties file is PayPal.wsdl.properties. Inside the properties file, add the following:
http_version=1.1
For the web services framework to pick up your changes, you must upload the WSDL file and the properties file to your server.

If you are making doc-style web service calls from WSDL files in the webreferences2 folder, the web service framework uses HTTP 1.1 when making the SOAP request by default. The properties file ignores any http_version set in the properties file.

Resolving Namespace Collisions for WSDLs and Associated Files

When calling a web service from a script file, B2C Commerce generates dw.rpc.Stub and supporting classes, using the default namespace webreferences.<wsdl_file_name>.
Note: This section uses the webreferences object, folder, and file names, instead of webreferences2, but the information is is equally applicable to JAX-WS as JAX-RPC web services and the associated webreferences2 object, folder, and file names.

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 Stub 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 webreferences directory as the WSDL file itself.

In your B2C Commerce script file, create objects using the WebReference 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 : WebReferences = webreference.HelloWorld;
var request : new webRef.com.test.wsdl.HelloRequest();
var svc = webRef.getDefaultService();
var response = webRef.hello(request);

Troubleshooting Web Services

Q: I am receiving an error that there is a duplicate file name but, the file is working fine in my SoapUI program. The error I am seeing is: “Script exception in line 44:org.mozilla.javascript.EcmaError: ReferenceError: Failed to load and compile WSDL for… Reason: org.mozilla.javascript.EcmaError: ReferenceError: Error while generating intermediate code for webservice. Reason: org.apache.axis.wsdl.toJava.DuplicateFileException: Duplicate file name.... Hint: you might have mapped two namespaces with elements of the same name to the same package name."

A: Duplicate Names can now be supported with the addition of a properties file with the same name and in the same directory of the WSDL which contains namespace=true. Example: foo.wsdl and foo.wsdl.properties. If you use this method the scripts calling them will need to use the fully qualifying name when creating objects or the response objects of the types dependent on this.

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?

A: When the Java files get created, the classes get created with an '_Element' 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?

A: The following data types are not supported:
  • NonNegativeInteger
  • NonPositiveInteger
  • UnsignedByte
  • UnsignedInt
  • UnsignedLong
  • UnsignedShort

Tip for Accessing Elements with the Name 'Return'

The following is an example element in a WSDL document with a name of 'return':
<element name="NewOrderResponse"> 
   <complexType> 
    <sequence> 
    <element name="return" type="ns:NewOrderResponseElement" minOccurs="1" maxOccurs="1"/> 
    </sequence> 
   </complexType> 
  </element>  
In order to access an element with the name "return" you need to add an underscore to the name.
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:

Using the following pseudocode:
response = service.newOrder(newOrder); var result = response.return;  
Throws the following error:
org.mozilla.javascript.EvaluatorException: missing
Example 2:
Using the following pseudocode:
name after . operator   response = service.newOrder(newOrder); 
Throws the following error:
var result = response.['return']; Unknown dynamic property 'return' for class