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
Note: RPC/Encoded WSDLs are not supported. However, we do support RPC/Literal, Document/Encoded, and Document/Literal WSDLs.

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. 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, set collectionType=indexed in the <wsdl_name>.wsdl.properties properties file.
  2. Place the files in the webreferences2 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 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.

    • 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);
        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 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.
    • execute callback to make the service call.
    • parseResponse to parse the result of the call.
  4. 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.
  5. Develop a pipeline to call the web service and create a script node that points to your web service script.
  6. 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
  7. 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: 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.
  8. Create an ISML template to show the results of the web service and trigger a page refresh when the information shown changes.
  9. Troubleshoot any errors using the request log and customerror development logs. See also Troubleshooting Web Services below.
See also Web Service Security.

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.

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?

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:
response = service.newOrder(newOrder); 
Throws the following error:
var result = response.['return']; Unknown dynamic property 'return' for class