Storefront for partners

What’s a Storefront?

The Storefront is one of the most important entities in Headless. It’s a basic building block and the foundation were all the other commerce entities will be created. In fact, you need to have a storefront setup, because it’s a requirement to create products, add stock, set prices, configure the session, cart, orders, etc.

To illustrate this, let’s see a snippet of the GraphQL mutation used to create a new product. Take note of the first parameter of the mutation: the name of a Storefront (storefrontName) is a requirement to create it.

mutation CreateProductMutation(
	$storefrontName: String!
	$data: ProductInputType!) {
	createProduct(storefrontName: $storefrontName, data: $data) {
		...
	}
}
💡 The **Storefront** property `name` is used in all of the other entities that requires `storefrontName` as parameter.

Go to the top


Anatomy of a Storefront

Here’s the Storefront and all its fields (in pseudo code) that are currently used.

enum StorefrontStatus {
	CREATING = 'CREATING'
  RUNNING = 'RUNNING'
  PUBLISH = 'PUBLISH'
  DRAFT = 'DRAFT'
}

enum AddOnTypes {
  catalog = 'CATALOG',
  createOrder = 'CREATE_ORDER',
  createSession = 'CREATE_SESSION',
  getProductBySKU = 'GET_PRODUCT_BY_SKU',
  searchProduct = 'SEARCH_PRODUCT',
}

enum StockType {
	INFINITE = 'INFINITE',
  SELF_MANAGED = 'SELF_MANAGED',
  REAL_TIME = 'REAL_TIME',
}

const CheckoutRules = {
  cartConditionPendingMinAmount: {
    quantity: number,
    message: string,
		isActive: boolean,
  },
  cartConditionPendingMaxAmount: {
    quantity: number,
    message: string,
		isActive: boolean,
  },
  cartConditionPendingMinQty: {
    quantity: number,
    message: string,
		isActive: boolean,
  },
  cartConditionPendingMaxQty: {
    quantity: number,
    message: string,
		isActive: boolean,
  },
	cartConditionPendingMinAdditionalMeasurement: {
		type: string,
		unit: string,
		quantity: number,
	  message: string,
	  isActive: boolean,
	},
	cartConditionPendingMaxAdditionalMeasurement: {
		type: string,
		unit: string,
		quantity: number,
	  message: string,
	  isActive: boolean,
	},
  cartConditionPendingUserValidation: {
    message: string,
		sku: string[],
		attribute: string,
		accepted: boolean,
		isActive: boolean,
  },
  cartWarnignCheckReturnables: {
    message: string,
    sku: string[],
		attribute: string,
		accepted: boolean,
		isActive: boolean,
  },
	orderDailyLimit: {
		quantity: number,
    message: string,
    isActive: boolean,
	}
}

const **Storefront** = {
	name: string
	owner: string
	status: StorefrontStatus
	endpoints: [
		{
			url: string
			status: StorefrontStatus
		}]
	templateID: string
	configuration: {
		useStores: boolean,
		stock: {
			type: StockType,
			threshold: {
				lowWarning: {
			    id: string
			    warningMessage: string
			    quantity: number
			  },
			  criticalLowWarning: {
			    id: string
			    warningMessage: string
			    quantity: number
			  }
			},
			timer: {
				expirationMinutes: number
			},
      key: string
	
			// @deprecated
		  enabled: boolean,
			// @deprecated
		  stockThreshold: number
		},
		priceless: boolean,
		promotions: {
		  enabled: boolean,
			maximumActive: number
		}, 
	  i18n: {
	    language: string,
	    currencyFormat: string, 
	    currencySymbol: string,
	    currencyCode: string,
	    country: string,
			timezone: string,
	  },
		workflow: {
			*workflowName*: {
		    name: string,
		    channel: string,
		    channelUid: string,
				ng: boolean,
		    stephook: {
					*stephookName*: string
				},
		    jwtToken: string,
		    body: JSON,
		  }
		},
		checkoutRulesByType:[{
			type: string,
			checkoutRules: CheckoutRules,
		}],
	  checkoutRules: CheckoutRules,
		sessionTtl: number,
		groupers: {
			cart: {
				active: boolean,
				session: string[],
				product: string[],
			},
			order: {
				active: boolean,
				session: string[],
				product: string[],
			}
		},
		splitters: {
			order: {
				active: boolean,
				maxProduct: number,
			}
		}
	},
	addons: {
		instead: [AddOnTypes],
		after: [AddOnTypes]
	},
	showRecommendations: boolean
}
💡 In the example above, the keys `workflowName` **and **`stephookName` are in italic because those names are placeholders. In real world scenarios, they will be defined using names related to the workflow, like `bimbo-br-wa`. Those keys are not fixed and could be any valid JSON key.

Go to the top

Top level properties

Property nameTypeDescriptionReq.Default valueNotes
namestringA unique non-null name that identifies the Storefront. This value is used in almost all the Headless CRUD operations.N/Aname is converted to lowercase before use.
ownerstringWho owns the Storefront.N/Aname and owner can be different
statusenumThe current status of the Storefront as an enum value.CREATINGThe enum values can be: CREATING, RUNNING, PUBLISH and DRAFT

configuration properties

The configuration contains a plethora of options used to establish how the Storefront is going to work. From stock management and price, to promotions, internationalization and business rules. This is a big object with several nested properties (with their own properties), so the definitions will be broken down for better understanding.

useStores

Boolean value declaring if the stock and catalog is distributed by store.

💡 The stores features is not fully implemented yet

stock

Options to activate and configures the handling of stock

Property nameTypeDescription
typeenumAn enum value that indicates the stock type that is being used. Current values: INFINITE, SELF_MANAGED and REAL_TIME.
thresholdobjectHas warning messages for threshold values of stock.
timerobjectIndicates how much time in minutes the stock will be reserved when added to the cart.
keystringKey used to consume the stock from the database. This key should be the same that has been loaded to the stock collection (stock.key).
enabledbooleanDeprecated: use type instead.
Indicate if stock should be checked during product catalog/search
stockThresholdnumberDeprecated: use threshold instead.
The number of items that should be retained as stock reserve when the catalog is requested.

priceless

A boolean property indicating whether the Storefront is priceless (this means that the Storefront doesn’t handle prices). Its default value is false.

promotions

Options to enable an configure promotions in this Storefront:

Property nameTypeDefault valueDescription
enabledbooleanfalseIndicate if the promotions should be enabled and handled in this Storefront.
maximumActivenumberIndicate the maximum number of active promotions that a cart or order can have for the Storefront. The restriction only applies if the promotions.enable field is true

i18n

The i18n section is intended to contain the configuration related with the internationalization of the Storefront instance. Some fields should be inferred given the country.

💡 All the values should be validated at creation/edition time in order to enforce the compliance with the ISOs documented below
Property nameTypeDescription
languagestringDeclare the language used by the Storefront instance. This value should be used for text i18n in the front end. The format for the language must be declared as xx_YY where, xx is the ISO 639 language codes and YY is the ISO 3166 country codes. Example: es_MX should be the language value used for Mexican Spanish.
currencyFormatstringThe currency format should be declared using locales. e.g to declare the currency format for Mexico, the value should be es-MX. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString#using_locales for more information.
currencySymbolstringThe currency symbol should comply with the https://www.newbridgefx.com/currency-codes-symbols/ standard.
currencyCodestringThe currency code should comply with the https://www.newbridgefx.com/currency-codes-symbols/ standard
countrystringThe country should comply with the https://www.google.com/search?hl=en&q=iso country codes
timezonestringThe timezone used for date operations in the storefront.

workflow

This section will contain the data required for workflow interaction during the run-time execution. The structure of this section is of a group of nested, mapped objects. And the key for accessing the workflow, is its name. That way one or more workflows can be set up in the Storefront.

For example, the following snippet shows two different workflows inside the workflow property, both with different names (keys) but the same structure:

workflow: {
  *bimbo-br-whatsapp*: {
    name": "bimbo-br-whatsapp",
    channel: "botslug-wa",
		// Other fields
  },
  *bimbo-br-messenger*: {
    name: "bimbo-br-messenger",
    channel: "botslug-ms",
		// Other fields
  }
}
💡 The `workflow` key should be the same string value as the nested workflow’s `name` property. For example, for the workflow with the key `bimbo-br-whatsapp`, the `name` should have the same value: `bimbo-br-whatsapp`.
Property nameTypeDescription
namestringName of the workflow where this Storefront would live
channelstringChannel name where the workflow is being executed. eg: WhatsApp, Messenger
channelUidstringThe identifier of the channel, in the case of WhatsApp, this value should be the WA phone number
ngbooleanIndicates whether this workflow is Studio NG (true) or Studio OG (false)
stephookobjectAn object with mapped string values. Its use depends on the value of the ng property.
jwtTokenstringJWT token required to consume the hook
bodyobjectAn object with custom properties that will be passed as payload when the Studio NG/OG trigger is called.

checkoutRulesByType

An array of checkoutRules that are keyed by type. This is the way to configure checkout rules per customer, using the type property to match with the customer type. In case the key doesn’t match any customer, or the checkoutRulesByType is empty, then the checkout rules applied will be the ones in the configuration.checkoutRules property.

Property nameTypeDescription
typestringThe key that will be matched with the customer type.
checkoutRuleobjectThe group of checkout rules that will be applied (if possible) to the customer with the same type as the type used. This group or rules are the same as the ones in the configuration.checkoutRules property.

checkoutRules

All the values that should be applied for the checkout rules by default. When the Session is created this values should be added to the checkoutRules object inside the Session.

These values may be overridden during the workflow execution given that are modules that handle these values. That being said, these values are the default for any user of the Storefront workflow.

Property nameTypeDescription
cartConditionPendingMinAmountobjectValue of the default minimum amount in currency that a user is allowed to spend
cartConditionPendingMaxAmountobjectValue of the default maximum amount in currency that a user is allowed to spend
cartConditionPendingMinQtyobjectValue of the default minimum quantity of items that a user should be allowed to buy
cartConditionPendingMaxQtyobjectValue of the default maximum quantity of items that a user should be allowed to buy
cartConditionPendingMinAdditionalMeasurementobjectValue of the default minimum in additional measures of items that a user should be allowed to buy
cartConditionPendingMaxAdditionalMeasurementobjectValue of the default maximum in additional measures of items that a user should be allowed to buy
cartConditionPendingUserValidationobjectRule that verifies that the user accepted a condition/validation (e.g. he es 18 years old if some products are alcoholic beverages)
cartWarnignCheckReturnablesobjectVerifies that the user accepted to return product packages (e.g. plastic bottles for some soft drinks).
orderDailyLimitobjectRules how many orders can be created per day for the given customer

sessionTtl

Indicates the time to live in minutes of the Sessions created for this Storefront. This property is numeric and its default value is 1,440 (number of minutes in one day).

groupers

⚠️ Groupers and splitters **must not be active at the same time**. If you try to create or modify a storefront with both `groupers` and `splitters` activated, an error will be returned. ℹ️ Groupers is an optional property.

This property enables the grouping of multiple carts and/or orders, and configure how this grouping will be made. Currently there are two properties inside groupers that have the same fields, but different purpose:

  • **cart**: Enables and configures multiple groups in a cart
  • **order**: Enables and configures multiple groups in an order

For each one of these two fields, the following options are available

Property nameTypeDescription
activebooleanEnables or disables the grouper
sessionstring[]A list of properties from the session that indicates what labels will be used in the groupBy field to identify a particular cart group. Examples: “paymentMethod"
productstring[]A list of properties from the product that indicates what labels will be used in the groupBy field to identify a particular cart group. Examples: "brand", "promotion"
{
	"groupers": {
    "cart": {
      "active": true,
      "session": ["paymentMethod"],
      "product": ["brand", "promotion"]
    },
    "order": {
      "active": true,
      "session": ["paymentMethod"],
      "product": ["brand", "promotion"]
		}
	}
}

splitters

⚠️ Groupers and splitters **must not be active at the same time**. If you try to create or modify a storefront with both `groupers` and `splitters` activated, an error will be returned. ℹ️ Splitters is an optional property.

This property enables the spliting of multiple orders, and configure how this splitting will be made. Currently there is one property inside splitters:

  • **order**: Enables and configures multiple splitters in an order. Has the following fields.
Property nameTypeDescription
activebooleanEnables or disables the grouper
maxProductnumberIndicates the maximum numbers of products that an order can have in each splitter. Example: if maxProduct = 15, if we create an order with 20 products, 15 will be in one splitter and 5 in another.
{
	"splitters": {
    "order": {
      "active": true,
      "maxProduct": 15
    }
  }
}

showRecommendations

Indicates if the storefront has the show recommendations option available. Consumers (like Cinnamon) will query this property to decide whether or not call the frequentlyBoughtTogether query to get recommended products.

Go to the top

addons properties

The addons property contains the list of the add-ons that should be called when certain operations are done with entities created in the Storefront. It may be undefined or if it’s defined it should contain one or the two of the following properties:

  • instead: If this property exists, it will contain an array with all the operations whose return value will be replaced by a call to a specific, valid add-on that must exist in the Storefront. While the end result will be the same, internally, the add-on system will make a call to an external service (configured in the corresponding add-on) and return whatever that service returned.
  • after: If this property exists, it will contain an array with all the operations that will trigger a call to a specific, valid add-on that must exist in the Storefront. The add-on system will make a call to an external service (configured in the corresponding add-on) that will be called after the operation (and will not replace its return value).

It’s valid for an operation to be in both the instead and the after arrays. The operations that currently support add-ons, and that can be defined as the values of the addons property, are the following:

enum AddOnTypes {
  catalog = 'CATALOG',
  createOrder = 'CREATE_ORDER',
  createSession = 'CREATE_SESSION',
  getProductBySKU = 'GET_PRODUCT_BY_SKU',
  searchProduct = 'SEARCH_PRODUCT',
}

An example of the addons property set to some valid values is:

addons: {
  instead: [
    "CREATE_SESSION",
    "CATALOG"
  ],
  after: [
    "CREATE_SESSION",
    "CREATE_ORDER"
  ]
}
💡 For more information, read the document [Add-ons for partners](https://www.notion.so/Add-ons-for-partners-30a5c9711e5f4590a0921595bba051ec?pvs=21).

Go to the top


Storefront Operations

This document will guide you through the basic CRUD operations for the Storefront. There are two options to work with Storefronts: The Admin/User API and the Commerce Manager.

Admin/User API

Headless uses GraphQL as the query and data manipulation language, so all the examples will be using GraphQL queries and JSON variables. The operations that you can execute, depend of the workspace or server where you are working:

Admin Workspace

Create a Storefront

To create a new Storefront, use the following GraphQL mutation:

mutation CreateStorefrontMutation($data: StorefrontInput!) {
	createStorefront(data: $data) {
		id
		name
		status
		owner
		configuration {
			useStores
			priceless
			stock {
				enabled
			}
		}
	}
}
💡 **Note:** For illustration purposes, almost all the fields used to create a **Storefront** were set. But several of these fields are optional. Remember to set all the properties (specially in the configuration) that are needed for your **Storefront** to work properly.

Here’s an example of the variables as JSON passed to the previous mutation:

{
  "data": {
    "name": "apollo-storefront-09",
    "status": "CREATING",
    "owner": "apollo-storefront-09",
    "configuration": {
      "useStores": true,
      "priceless": false,
      "stock": {
        "enabled": true,
        "stockThreshold": 100,
        "key": "YALO"
      },
      "promotions": {
        "enabled": false
				"maximumActive": 20
      },
      "i18n": {
        "language": "es_MX",
        "currencyFormat": "es-MX",
        "currencySymbol": "$",
        "currencyCode": "MXN",
        "country": "MX"
      },
      "workflow": {
        "whatsapp": {
          "name": "whatsapp",
          "channel": "botslug-wa",
          "channelUid": "5255555555555",
          "ng": true,
          "stephook": {
            "forgottenCart": "786876fdsf87678sd"
          },
          "jwtToken": "qwerty1234uiop.qwerty1234uiop.qwerty1234uiop",
          "body": {
            "message": "GREAT SUCCESS!"
          }
        }
      },
      "checkoutRules": {
        "cartConditionPendingMinAmount": {
          "quantity": 10,
          "message": "you should met the min amount of",
          "isActive": true
        },
        "cartConditionPendingMaxAmount": {
          "quantity": 20,
          "message": "you should met the max amount of",
          "isActive": false
        },
        "cartConditionPendingMinQty": {
          "quantity": 30,
          "message": "you should met the min quantity of",
          "isActive": true
        },
        "cartConditionPendingMaxQty": {
          "quantity": 20,
          "message": "you should met the max quantity of",
          "isActive": false
        },
        "cartConditionPendingUserValidation": {
          "message": "Are you over 18 years old?",
          "sku": [
            "YALO000001",
            "YALO000006"
          ],
          "attribute": "alcohol",
          "isActive": true
        },
        "cartWarningCheckReturnables": {
          "message": "You should return your plastic bottles",
          "sku": [
            "YALO000010",
            "YALO000011"
          ],
          "attribute": "returnable",
          "isActive": false
        }
      },
      "sessionTtl": 1440
    },
    "addons": {
      "instead": [
        "CREATE_SESSION",
        "CATALOG"
      ],
      "after": [
        "CREATE_SESSION",
        "CREATE_ORDER"
      ]
    }
  }
}

Example of a returned document that result of a successful Storefront creation:

{
  "data": {
    "createStorefront": {
      **"id": "61ec75d4c969e39e82eca95a",**
      "name": "apollo-storefront-09",
      "status": "CREATING",
      "owner": "apollo-storefront-09",
      "configuration": {
        "useStores": true,
        "priceless": false,
        "stock": {
          "enabled": true
        },
        "workflow": {
          "whatsapp": {
            "name": "whatsapp",
            "channel": "botslug-wa",
            "channelUid": "5255555555555",
            "ng": true,
            "stephook": {
              "forgottenCart": "786876fdsf87678sd"
            },
            "jwtToken": "qwerty1234uiop.qwerty1234uiop.qwerty1234uiop",
            "body": {
              "message": "GREAT SUCCESS!"
            }
          }
        }
      }
    }
  }
}
💡 The **bolded** property, `id`, is part of the returned data that you need to keep, because almost all the queries and mutations for Storefront, use this as parameter.

Go to the top

Update a Storefront

To update the Storefront, you can use the following mutation:

mutation UpdateStorefrontMutation($id: ID!, $data: UpdateStorefrontInput!) {
  updateStorefront(id: $id, data: $data) {
    id
    name
    status
    owner
    configuration {
      useStores
      priceless
      promotions {
        enabled
      }
      workflow
      sessionTtl
    }
  }
}

For this example, the following JSON will be passed as GraphQL variables. You pass the id of the Storefront that you need to update, and also a data property with all the information that you need to update.

In this case, there’s a mixture of adding and updating. Some properties (like priceless and promotions) are being modified, and also a new workflow (with the messenger key) is being added:

{
  "id": "61ec75d4c969e39e82eca95a",
  "data": {
    "configuration": {
      "priceless": true,
      "promotions": {
        "enabled": true
      },
      "workflow": {
        "messenger": {
          "name": "messenger",
          "channel": "botslug-ms",
          "channelUid": "5255555555555",
          "ng": true,
          "stephook": {
            "createOrder": "786876fdsf87678sd"
          },
          "jwtToken": "qwerty1234uiop.qwerty1234uiop.qwerty1234uiop"
        }
      },
      "sessionTtl": 2880
    }
  }
}

This is the response JSON document. The workflow now has two entries (whatsapp and messenger) and also the changed properties have their new values:

{
  "data": {
    "updateStorefront": {
      "id": "61ec75d4c969e39e82eca95a",
      "name": "apollo-storefront-09",
      "status": "CREATING",
      "owner": "apollo-storefront-09",
      "configuration": {
        "useStores": true,
        "priceless": true,
        "promotions": {
          "enabled": true
        },
        "workflow": {
          "whatsapp": {
            "name": "whatsapp",
            "channel": "botslug-wa",
            "channelUid": "5255555555555",
            "ng": true,
            "stephook": {
              "forgottenCart": "786876fdsf87678sd"
            },
            "jwtToken": "qwerty1234uiop.qwerty1234uiop.qwerty1234uiop",
            "body": {
              "message": "GREAT SUCCESS!"
            }
          },
          "messenger": {
            "name": "messenger",
            "channel": "botslug-ms",
            "channelUid": "5255555555555",
            "ng": true,
            "stephook": {
              "createOrder": "786876fdsf87678sd"
            },
            "jwtToken": "qwerty1234uiop.qwerty1234uiop.qwerty1234uiop"
          }
        },
        "sessionTtl": 2880
      }
    }
  }
}

Go to the top

Remove a Storefront

⚠️ Be careful when removing a **Storefront**. If it has dependencies (like sessions, customers, products, or other entities that were created using the **Storefront** `name`) trying to access some of those dependencies will result in an error.

The following mutation allows you to remove a Storefront:

mutation RemoveStorefrontMutation($id: ID!) {
  removeStorefront(id: $id) {
    id
    name
    status
    owner
  }
}

You pass the id of the Storefront you want to remove as variable:

{
  "id": "61ec75a2c969e39e82eca952"
}

Go to the top

Query a Storefront

To get the information of a Storefront, you only indicate the fields that you want to query:

query StorefrontQuery($id: ID!) {
  storefront(id: $id) {
    id
    name
    status
    owner
    configuration {
      useStores
		}
  }
}

And pass the id of the Storefront as variable:

{
    "id": "61ec75d4c969e39e82eca95a"
}

Example of the JSON document returned:

{
  "data": {
    "storefront": {
      "id": "61ec75d4c969e39e82eca95a",
      "name": "apollo-storefront-09",
      "status": "CREATING",
      "owner": "apollo-storefront-09",
      "configuration": {
        "useStores": true
      }
    }
  }
}

Go to the top

Query all the Storefronts

To get all the Storefronts stored, use the following GraphQL query:

query StorefrontsQuery(
	$pagination: PaginationInput
	$filter: FilterStorefrontInput) {
  storefronts(pagination: $pagination, filter: $filter) {
    id
    name
    status
    owner
    configuration {
      useStores
    }
  }
}

Both the pagination and filter parameters are optional. The first one is used when there’s a lot of documents stored and you want to select a few, for performance reasons. The filter is used to pass a criteria search, based on one of more properties of the Storefront. Here’s an example of variables passed to the previous query that uses both parameters:

{
  "pagination": {
    "pageNumber": 1,
    "pageSize": 10
  },
  "filter": {
    "status": "CREATING"
  }
}
💡 If the `pagination` parameter is omitted, then the default values for `pageNumber` will be **1**, and for the `pageSize` will be **10**.

In this case, we are requesting the first group of 10 Storefronts stored, and also we want to filter the query and only bring the Storefronts with status of CREATING. Here’s an example of the returned JSON document for the previous query:

{
  "data": {
    "storefronts": [
      {
        "id": "61ec75d4c969e39e82eca95a",
        "name": "apollo-storefront-09",
        "status": "CREATING",
        "owner": "apollo-storefront-09",
        "configuration": {
          "useStores": true
        }
      }
    ]
  }
}

Go to the top


User Workspace

Query a Storefront

In the User Workspace, you can query for a Storefront by id or name, but not both. Passing both parameters (or none) as variables results in an error. This is the query used:

query StorefrontQuery($id: ID, $name: String) {
  storefront(id: $id, name: $name) {
    id
    name
    status
    owner
  }
}

And you can pass either the id of the Storefront you are looking for:

{
  "id": "61ec75d4c969e39e82eca95a"
}

Or the Storefront name:

{
  "name": "apollo-storefront-09"
}

Example of the return value of the query using either of the previous variables:

{
  "data": {
    "storefront": {
      "id": "61ec75d4c969e39e82eca95a",
      "name": "apollo-storefront-09",
      "status": "CREATING",
      "owner": "apollo-storefront-09",
      "configuration": {
        "useStores": true
      }
    }
  }
}

Go to the top


Commerce Manager

Coming soon!🍿