Dynamic Bundles
Table of contents
- Overview
- Technology
- Features
- Architecture
- Configuration
- Installation
- Development
- Tests
- Build & Deployment
- Complete the Solution: Your Implementation Responsibilities
- Support
Overview
Dynamic bundles allow for more complex bundling scenarios. Dynamic bundles specify a group of products where the user can select one product variant. Common use-cases include mix-&-match offers, where a user can select six craft beers to build a six-pack or build an entire outfit choosing a shirt, jacket, pants, and tie. Another common use-case is composite products, where the user can configure the final product, for example building a computer by choosing a CPU, GPU, RAM, and HDD.
Where a static bundle points to specific SKUs, a dynamic bundle points to specific categories and stores business rules around each category. Pricing for the dynamic bundle can be a fixed amount or change based on the bundle selections.
Dynamic bundles require a custom UI for the customer to make their selections and additional code when adding to the cart and checking out. The system presented handles some common use-cases, however companies often have unique business rules that need to be considered. To accommodate these rules, the code can be modified to meet the needs of the store.
Dynamic bundles are achieved by creating a new product with a specific type that references all the product groups and business rules within the bundle.
The Merchant Center custom application in this solution assists merchandisers in creating and managing dynamic bundles.
Technology
- ReactJS
- Apollo & GraphQL
- Merchant Center Application Kit
- UI Kit - Merchant Center component library
- Yarn - Package manager
- Jest - Test runner
- Enzyme - React testing utility
- Prettier - Code formatter
- ESLint - JS, CSS, and GraphQL linter
Features
Bundles List
The landing page for the static bundles custom application is a list displaying the static bundles within the commercetools project.
- Pagination
- Default page size of 30,
- Displayed when the number of static bundles exceeds the page size
- Sorting
- Sortable columns are Name, Price, and Last Modified
- Initial sort is Last Modified in descending order (most recent to least recent bundles)
- Search for a bundle using product projections search
- Filter
- By bundle price type (fixed or dynamic)
- By category within bundle
Bundle Creation
- Single step to create a static bundle with basic information
- Search for a category using GraphQL category search
- Automatic slug creation based on the name of the bundle, which can be modified to suit your needs
- Only displays additional charge component option when bundle is a fixed price
Bundle Details
Manage bundle information, add images, and determine pricing strategies.
- Manage the bundle’s published status
- Delete unpublished bundle
General
Edit the bundle’s general information.
Images
- Add image(s) directly to the bundle via Merchant Center
- Remove images
- Manage other image properties via Merchant Center
Prices
View the prices of the bundle components in various scopes to determine its prices.
- View bundle component prices in different price scopes
- Displays the min and max prices of each category for the selected scope
- Add a price via Merchant Center
- View prices via Merchant Center
Note: Dynamically priced bundles require a price defined (i.e. USD $0) to be added to a cart.
Preview
A reference product detail page (PDP) for merchandisers to visualize the bundle.
- Configurable
- Modify the price scope to see the bundle as different customers
- View each product projection
- Search for and select a product variant from each bundle category
- Form validation based on bundle configuration (min/max quantities for bundle components)
- Preview add to cart action
- Displays bundle price based on the bundle’s price type
- Fixed price
- Calculates additional charge components by adding price of selected variant to the base fixed price
- Dynamic price
- Displays price range of bundle based on bundle categories
- Calculates price of bundle by summing price of selected product variant multiplied by its quantity
- Fixed price
Fixed Price Bundle with Additional Charge Component
Dynamic Price Bundle
Architecture
Product
A bundle is a commercetools product with only a master variant. The bundle components are stored as attributes of the master variant in accordance with the bundle product types.
Product Types
DynamicBundleParent
- categories - Array of DynamicBundleChildCategory - Required
The components of the bundle. - category-search - Array of String - Required
The bundle category IDs that are used to filter the bundle list. - min-quantity - Number
The minimum quantity of the bundle that must be added to a cart. - max-quantity - Number
The maximum quantity of the bundle that may be added to a cart. - dynamic-price - Boolean
Determines whether the bundle’s price is determined by the selected product variants or has a fixed price.
DynamicBundleChildCategory
A bundle component, which is a product category specifically created for use with the dynamic bundles solution.
- category-ref - Category Reference - Required
A reference to the bundle category. - category-path - String - Required
The path of the bundle category. Used for display purposes within the category search when viewing a bundle. - min-quantity - Number
The minimum quantity of the selected product variant from this category that must be added to the bundle. - max-quantity - Number
The maximum quantity of the selected product variant from this category that may be added to the bundle. - additional-charge - Boolean
Determines whether adding a product variant from this category to the bundle results in an increase to the bundle price. Only available as a selection when the bundle is statically priced.
Note: If any of the underlying values of the selected category change, the attribute values will not be updated until the bundle component in question is re-selected and saved on the bundle with the updated values.
Sample Bundle
Click to expand
```json { "id": "fba0d3d7-40f8-494e-8159-7efad907299e", "version": 16, "productType": { "typeId": "product-type", "id": "d7a7078d-77fb-452c-9f89-c4f3ff580cd3", "obj": { "id": "d7a7078d-77fb-452c-9f89-c4f3ff580cd3", "version": 8, "createdAt": "2019-10-09T13:21:33.210Z", "lastModifiedAt": "2019-12-05T17:33:08.919Z", "lastModifiedBy": { "isPlatformClient": true, "user": { "typeId": "user", "id": "c9065b8c-aae2-45e6-988d-b453869396be" } }, "createdBy": { "clientId": "kkwLptxStfpV2ga6cx1_iKLu", "isPlatformClient": false }, "name": "DynamicBundleParent", "description": "A dynamic bundle of product categories", "classifier": "Complex", "attributes": [ { "name": "categories", "label": { "en": "Categories" }, "isRequired": false, "type": { "name": "set", "elementType": { "name": "nested", "typeReference": { "typeId": "product-type", "id": "126dfa1e-bb88-4ca8-b4c6-90809a427ccd" } } }, "attributeConstraint": "None", "isSearchable": false, "inputHint": "SingleLine", "displayGroup": "Other" }, { "name": "min-quantity", "label": { "de": "", "en": "Minimum Quantity" }, "inputTip": { "de": "", "en": "" }, "isRequired": false, "type": { "name": "number" }, "attributeConstraint": "None", "isSearchable": true, "inputHint": "SingleLine", "displayGroup": "Other" }, { "name": "max-quantity", "label": { "de": "", "en": "Max Quantity" }, "inputTip": { "de": "", "en": "" }, "isRequired": false, "type": { "name": "number" }, "attributeConstraint": "None", "isSearchable": true, "inputHint": "SingleLine", "displayGroup": "Other" }, { "name": "dynamic-price", "label": { "de": "", "en": "Dynamic Price" }, "inputTip": { "de": "", "en": "" }, "isRequired": false, "type": { "name": "boolean" }, "attributeConstraint": "None", "isSearchable": true, "inputHint": "SingleLine", "displayGroup": "Other" }, { "name": "category-search", "label": { "de": "", "en": "Category (Search)" }, "inputTip": { "de": "", "en": "" }, "isRequired": false, "type": { "name": "set", "elementType": { "name": "text" } }, "attributeConstraint": "None", "isSearchable": true, "inputHint": "SingleLine", "displayGroup": "Other" } ], "key": "dynamic-bundle-parent" } }, "name": { "en": "Test Bundle" }, "description": { "en": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." }, "categories": [], "categoryOrderHints": {}, "slug": { "en": "test-bundle-1" }, "masterVariant": { "id": 1, "prices": [ { "value": { "type": "centPrecision", "currencyCode": "USD", "centAmount": 0, "fractionDigits": 2 }, "id": "6c49d1a0-410d-4ff3-9f4f-03f11b21efe3" } ], "images": [ { "url": "https://3e82993a4eb41d4c4033-d629e744ec32fa52597723d397b63314.ssl.cf1.rackcdn.com/sandals+black+again-rYgI68Y4.jpeg", "dimensions": { "w": 262, "h": 192 } }, { "url": "https://3e82993a4eb41d4c4033-d629e744ec32fa52597723d397b63314.ssl.cf1.rackcdn.com/sandals+black-5VEcMfOQ.jpeg", "dimensions": { "w": 225, "h": 225 } }, { "url": "https://via.placeholder.com/250", "dimensions": { "w": 250, "h": 250 } } ], "attributes": [ { "name": "categories", "value": [ [ { "name": "category-ref", "value": { "typeId": "category", "id": "33e6235b-def0-4ed6-aaf9-b23e8f364eae" } }, { "name": "category-path", "value": "New > Men > Shoes (2)" }, { "name": "min-quantity", "value": 2 } ], [ { "name": "category-ref", "value": { "typeId": "category", "id": "c93cedb4-464e-41ee-8031-2d7e8de2294f" } }, { "name": "category-path", "value": "New > Men > Clothing (11)" }, { "name": "max-quantity", "value": 2 } ] ] }, { "name": "category-search", "value": [ "33e6235b-def0-4ed6-aaf9-b23e8f364eae", "c93cedb4-464e-41ee-8031-2d7e8de2294f" ] }, { "name": "dynamic-price", "value": true }, { "name": "min-quantity", "value": 2 }, { "name": "max-quantity", "value": 3 } ], "assets": [] }, "variants": [], "searchKeywords": {}, "hasStagedChanges": false, "published": true, "taxCategory": { "typeId": "tax-category", "id": "a1af21d4-444c-409d-82a6-c3163b28b8e9", "obj": { "id": "a1af21d4-444c-409d-82a6-c3163b28b8e9", "version": 1, "createdAt": "2019-05-31T14:01:54.035Z", "lastModifiedAt": "2019-05-31T14:01:54.035Z", "lastModifiedBy": { "clientId": "AS2q4W4InSWREs5JQBltxP5V", "isPlatformClient": false }, "createdBy": { "clientId": "AS2q4W4InSWREs5JQBltxP5V", "isPlatformClient": false }, "name": "standard", "rates": [ { "name": "20% incl.", "amount": 0.2, "includedInPrice": true, "country": "AT", "id": "8us0Y-Ay", "subRates": [] }, { "name": "10% incl.", "amount": 0.1, "includedInPrice": true, "country": "US", "id": "EGg9RL8e", "subRates": [] }, { "name": "19% incl.", "amount": 0.19, "includedInPrice": true, "country": "DE", "id": "FIHSMRUP", "subRates": [] } ], "key": "standard" } }, "createdAt": "2020-01-21T13:54:37.110Z", "lastModifiedAt": "2020-07-31T14:19:21.668Z" } ```Configuration
A terraform script initializes the commercetools project for using dynamic bundles. Prior to using dynamic bundles, this terraform script must be executed against the commercetools project and will deploy:
- Dynamic Bundle Product Type – For creating new bundles.
- Nested Product Type – For managing 1…n category references from a dynamic bundle and configuration data.
Installation
Simply run yarn
from the repository root to install the application’s dependencies.
Development
Start the development server
Run the following command to start the development server and launch the application:
yarn start
If this is the first time running the application locally, create an env.json
file at the static bundles root directory using env.local.json
as an example. Based on your region, you may find it necessary to modify the values of frontendHost
, mcApiUrl
, and location
.
Troubleshooting
graphql_error.invalid_token
error
Log out of Merchant Center. Log back in, then return to the custom application and reload.
Do’s and Don’ts
- Don’t use the application development login screen to authenticate.
- Do make sure you are logged in to Merchant Center before developing or running a custom application.
Linting & Formatting
Formatting code
Run the following command to format JS, CSS, JSON and GraphQL files
yarn format
Linting code
Run the following command to lint JS, CSS, and GraphQL files
yarn lint
Linting GraphQL Queries
A prerequisite for linting GraphQL queries is generating a schema.graphql
file, which contains the Types exposed by CTP API. Every time the API introduces new Types, Queries or Mutations, the local schema.graphql
must be updated.
Generating CTP GraphQL schema
- If you haven’t done so already, create an API client under
Settings -> Developer Settings
in Merchant Center for your project - Generate an access token using the Client Credentials flow
- Export both your Merchant Center project key and generated access token as environment variables
- Retrieve schema with
graphql-cli
export PROJECT_KEY={project_key}
export AUTH_TOKEN={access_token}
npx graphql-cli get-schema
Git Hooks
Git hooks are configured using Husky. The root workspace runs all workspace hooks using Lerna (example repository). The hooks are configured as follows:
- Pre-commit: JS, CSS, and GraphQL files are linted (ESLint/Stylelint) and formatted (Prettier). Fixes are automatically added to Git.
- Commit Message: Commit messages are linted against the conventional commit format using commitlint
Tests
Run the following command to run the tests:
yarn test
To run the tests in watch mode:
yarn test:watch
To run the tests with coverage:
yarn test:coverage
Build & Deployment
Run the following command to build the production bundles with webpack:
yarn build
Please check for deployment examples documentation here.
NOTE: Be sure to set the env vars for the placeholders in custom-application-config.mjs.
- Example: For AWS deployment, env variables can be set using the file env.aws. For other deployments, duplicate the file and set values accordingly.
For more information on how to use .env files, check official documentation.
Registration with Merchant Center
After deploying the custom application, it needs to be registered with a Merchant Center project.
Configuration Values
- Main Route Path: dynamic-bundle-manager
- Link Permissions: Manage Products, View Products
Complete the Solution: Your Implementation Responsibilities
To complete the bundles solution, you will first need to supply a product detail page (PDP) for the dynamic bundle products in your frontend implementation. The custom application provides an example PDP as a reference along with corresponding documentation for the necessary API calls. The UX for displaying the bundle components will depend on your specific use case.
For dynamically priced bundles, you will need to provide the pricing algorithm for the bundles. The example PDP sums the price of the selected product variants multiplied by the quantity of each.
For statically priced bundles with components that are designated with an additional charge, you will need to provide the algorithm for determining what that additional charge is and updating the bundle’s price accordingly.
Support
Please create an issue in the repository for all support requests related to the dynamic bundles solution.