Extending API Methods

Vue Storefront integration for SAP Commerce Cloud ships with a default set of API methods responsible for direct communication with SAP's OCC API. It should be covering all of the most frequently used features of the platform. However, there might be scenarios where extending its capabilities will be required. Fortunately, in Vue Storefront it's easy to achieve with an integration extension (opens new window) and its extendApiMethods (opens new window) option.

Although the main Vue Storefront documentation describes the process pretty well, it does not mention the bits specific to SAP Commerce Cloud integration. This guide fills that gap and walks you through the process of creating a custom createCart() API method which should:

1. Creating an extension

Let us start by registering a new extension under sapcc in middleware.config.js. It should be an object with name and extendApiMethods properties. Don't worry about the latter being an empty object - we will define our custom API method there soon.

/* middleware.config.js */

const createCartExtension = {
  name: 'create-cart-extension',
  extendApiMethods: {}
};

module.exports = {
  integrations: {
    sapcc: {
      // ...
      extensions: existing => existing.concat(createCartExtension),
      // ...
      }
    }
  }
};

2. Defining a custom API method

With the extension in place, we can use its extendApiMethods object to define our empty createCart() method. It should be an async function accepting two arguments: context and props.

/* middleware.config.js */

const createCartExtension = {
  name: 'create-cart-extension',
  extendApiMethods: {
+   createCart: async (context, props) => {
+      
+   }
  }
}

The context argument gives us access to:

  • the integration-specific configuration (including properties we've provided in middleware.config.js such as baseSiteId)
  • Axios (opens new window) instance used for communication between API Middleware and SAP OCC API

The props argument is simply an object coming from the frontend. In the example below, it would be { fields: 'BASIC' }.

import { onMounted } from '@nuxtjs/composition-api';
import { useVSFContext } from '@vue-storefront/core';

export default {
  setup() {
    const { $sapcc } = useVSFContext();

    onMounted(async () => {
      const cart = await $sapcc.api.createCart({ fields: 'BASIC' });
    });
  }
};

3. Creating the CartsApi

In simple terms, API methods are all about sending requests to SAP OCC API on behalf of the frontend. To simplify the process, Vue Storefront leverages CommerceWebservicesV2 (opens new window) - an SDK automatically generated by the Swagger Codegen project. It ships with various APIs and methods which make working with the OCC API a lot easier.

Good to know

A full list of APIs and their methods provided by CommerceWebservicesV2 (opens new window) can be found here (opens new window).

Vue Storefront provides its independent copy of the SDK as a separate package called @vsf-enterprise/sap-commerce-webservices-sdk. You can import any desired API from there and use it in your custom API methods. In this example, we are going to create a new instance of the CartsApi using some of the properties available in context.

/* middleware.config.js */

+ const { CartsApi } = require('@vsf-enterprise/sap-commerce-webservices-sdk');

const createCartExtension = {
  name: 'create-cart-extension',
  extendApiMethods: {
    createCart: async (context, props) => {
+     const { client, config } = context;
+     const { sdkConfig, api: { uri } } = config;

+     const cartsApi = new CartsApi(sdkConfig, uri, client);
    }
  }
};

4. Creating a request with basic options

With the CartsApi in place, we can use its createCart method to:

  • send the actual Axios request to SAP Commerce Cloud
  • return the received data to the frontend

Looking at the createCart() method interface, we can see it accepts a couple of arguments:

export declare class CartsApi extends BaseAPI {
  // ...
  createCart(
    baseSiteId: string,
    userId: string,
    fields?: 'BASIC' | 'DEFAULT' | 'FULL',
    oldCartId?: string,
    toMergeCartGuid?: string,
    options?: any
  ): Promise<import("axios").AxiosResponse<Cart>>;    
}

Let's start with the low-hanging fruits. We are going to extract the values for baseSiteId, oldCartId and toMergeCartGuid from context and props.

/* middleware.config.js */

const { CartsApi } = require('@vsf-enterprise/sap-commerce-webservices-sdk');

const createCartExtension = {
  name: 'create-cart-extension',
  extendApiMethods: {
    createCart: async (context, props) => {
      const { client, config } = context;
+     const { sdkConfig, api: { uri, baseSiteId } } = config;
+     const { oldCartId, toMergeCartGuid } = props;

      const cartsApi = new CartsApi(sdkConfig, uri, client);

+     const { data } = await cartsApi.createCart(
+       baseSiteId,
+       undefined,
+       undefined,
+       oldCartId,
+       toMergeCartGuid,
+       undefined
+     );
+
+     return data;
    }
  }
};

That was easy but what about the remaining undefined arguments? Let's replace them with real values using some of the utility methods imported from @vsf-enterprise/sapcc-api.

5. Adding userId to request

To establish whether the request is sent by an anonymous or current (authenticated) user, we can use the getUserIdFromRequest() method. It checks the req object for the vsf-sap-token cookie header. If the header is there, the method will return current. If it is not, it will return anonymous.

/* middleware.config.js */

const { CartsApi } = require('@vsf-enterprise/sap-commerce-webservices-sdk');
+ const { getUserIdFromRequest } = require('@vsf-enterprise/sapcc-api');

const createCartExtension = {
  name: 'create-cart-extension',
  extendApiMethods: {
    createCart: async (context, props) => {
+     const { client, config, req } = context;
      const { sdkConfig, api: { uri, baseSiteId } } = config;
      const { oldCartId, toMergeCartGuid } = props;

      const cartsApi = new CartsApi(sdkConfig, uri, client);

      const { data } = await cartsApi.createCart(
        baseSiteId,
+       getUserIdFromRequest(req),
        undefined,
        oldCartId,
        toMergeCartGuid,
        undefined
      );

      return data;
    }
  }
}

6. Adding fields to request

In the vast majority of cases, fields will be included in the props received from the frontend. In our custom API method, we only have to pass them on to cartsApi.createCart() as an argument.

/* middleware.config.js */

const { CartsApi } = require('@vsf-enterprise/sap-commerce-webservices-sdk');
+ const { getUserIdFromRequest, createRequestFields } = require('@vsf-enterprise/sapcc-api');

const createCartExtension = {
  name: 'create-cart-extension',
  extendApiMethods: {
    createCart: async (context, props) => {
      const { client, config, req } = context;
      const { sdkConfig, api: { uri, baseSiteId } } = config;
      const { oldCartId, toMergeCartGuid } = props;

      const cartsApi = new CartsApi(sdkConfig, uri, client);

      const { data } = await cartsApi.createCart(
        baseSiteId,
        getUserIdFromRequest(req),
+       createRequestFields({ props }),
        oldCartId,
        toMergeCartGuid,
        undefined
      );

      return data;
    }
  }
}

In the above example, we are using the createRequestFields() method. It extracts the fields from props and provides a fallback in case they are undefined

7. Adding options to the request

The last argument expected by the cartsApi.createCart() is options. It might be the crucial one because it should include the Authorization header and parameters such as lang (language) and curr (currency).

Appending the Authorization header is particularly important since the request's success or failure might depend on it. The header should carry either:

  • the application token (in case of anonymous users) or
  • the customer token (in case of authenticated users)

Distributing access tokens inside API methods could use a separate article. However, in this tutorial, we are going to simplify things and use the createRequestOptions() method. It does the heavy lifting for us and deals with both request parameters and the Authorization header.

/* middleware.config.js */

const { CartsApi } = require('@vsf-enterprise/sap-commerce-webservices-sdk');
+ const { getUserIdFromRequest, createRequestFields, createRequestOptions } = require('@vsf-enterprise/sapcc-api');

const createCartExtension = {
  name: 'create-cart-extension',
  extendApiMethods: {
    createCart: async (context, props) => {
      const { client, config, req } = context;
      const { sdkConfig, api: { uri, baseSiteId } } = config;
      const { oldCartId, toMergeCartGuid } = props;

      const cartsApi = new CartsApi(sdkConfig, uri, client);

      const { data } = await cartsApi.createCart(
        baseSiteId,
        getUserIdFromRequest(req),
        createRequestFields({ props }),
        oldCartId,
        toMergeCartGuid,
+       createRequestOptions({ context, props })
      );

      return data;
    }
  }
}

7. Testing the new API method

With the request options in place, our custom createCart() method implementation is ready. We are now able to call it directly from any of our frontend components:

import { onMounted } from '@nuxtjs/composition-api';
import { useVSFContext } from '@vue-storefront/core';

export default {
  setup() {
    const { $sapcc } = useVSFContext();

    onMounted(async () => {
      const cart = await $sapcc.api.createCart({
        fields: 'BASIC',
        whatever: 'whatever'
      });
    });
  }
};

If we set a breakpoint inside our custom method, it should pause the code execution at the selected line. This way we can double-check:

  • the method gets called
  • the method receives all expected props
  • the method's inner logic behaves as expected

Custom API method breakpoint