Product Options & Variations
Configurable Products
In the Basic Flow tutorial we have handled the Checkout flow for a simple product. In the Simpler realm a simple product refers to a standalone product, as in a product that requires no options selection from the user, such as as a bag or sunglasses.
This guide focuses on the extension of our SDK & Platform Interface implementation to handle configurable products. Configurable products refer to products that require the user to select options before proceeding to the checkout, such as a T-Shirt with different sizes and colours.
There are two widespread ways to handle configurable products :
- Using Variations : Each configuration of our parent product has a separate Product ID.
- Using Options : The parent product ID needs to be accompanied by its options.
Adding configurable products to the Simpler cart
When displaying the Simpler Quick Buy button on a cart page, the user has already made their option selections for the products and can proceed immediately to checkout.
In this case we can directly add the selected products to the Simpler cart via the SDK, and we'll receive the selected variation in the Product Details & Quote calls.
- Variations
- Options
window.addEventListener('DOMContentLoaded', () => {
Simpler.checkout({
appId: 'YOUR_SIMPLER_APP_ID',
currency: document.getElementById('simpler-checkout-container'),
items: [{
id: selectedVariationId,
quantity: 1
}]
});
});
window.addEventListener('DOMContentLoaded', () => {
Simpler.checkout({
appId: 'YOUR_SIMPLER_APP_ID',
currency: document.getElementById('simpler-checkout-container'),
items: [{
id: productId,
quantity: 1,
attributes: {
color: 'red',
size: 'XS'
}
}]
})
})
In both cases the items
array will be passed through as-is to the Platform Interface API calls.
You can use these properties from the request in your Simpler controllers to ensure that the correct product is loaded
and added to the cart.
Since the items
array is constant between all requests in the Simpler lifecycle,
it would be a good idea to extract a common helper function to retrieve a product from your catalog
- Variations
- Options
class SimplerProductsHelper {
public static function getProduct($item) {
$product = CatalogService::getProductBySKU($item['id']);
// if you're using a separate query function for variations you'll need to query that too
if (!$product) {
$product = CatalogService::getVariationProductBySKU($item['id']);
}
return $product;
}
}
class SimplerProductsHelper {
public static function getProduct($item) {
$product = CatalogService::getProductBySKU($item['id']);
$product->setOptionSelections($item['attributes']);
return $product;
}
}
Reacting to user selections in the product page
On the other hand, when displaying the Simpler Quick Buy button on the product page, the user might not have made their selections yet on the initial render of the button.
We can extend our SDK setup by making use of the onBeforeCheckout
handler, where we can prepare our cart before continuing with the Quick Buy click handler :
- Variations
- Options
window.addEventListener('DOMContentLoaded', () => {
Simpler.checkout({
appId: 'YOUR_SIMPLER_APP_ID',
currency: document.getElementById('simpler-checkout-container'),
items: [{
id: productId,
quantity: 1
}],
onBeforeCheckout: (checkout) => handleBeforeSimplerCheckout(checkout),
});
function handleBeforeSimplerCheckout(checkout) {
// check if a variation has been selected
const variationInput = document.getElementById('form#add-to-cart input#selected-variation-id')
if (!variationInput.value) {
// stop the click handler from continuing and show an error to the user
alert('Please select a variation to proceed');
return false;
}
checkout.setItems([{
id: variationInput.value,
// get the value of the quantity input from the add to cart form
quantity: document.getElementById('form#add-to-cart input#quantity')
}])
}
});
window.addEventListener('DOMContentLoaded', () => {
Simpler.checkout({
appId: 'YOUR_SIMPLER_APP_ID',
currency: document.getElementById('simpler-checkout-container'),
items: [{
id: productId,
quantity: 1
}],
onBeforeCheckout: (checkout) => handleBeforeSimplerCheckout(checkout),
});
function handleBeforeSimplerCheckout(checkout) {
// get all the option fields
const optionFields = document.querySelector('form#add-to-cart input.option-selection');
// fetch the selected values
const selectedOptions = Array.from(optionFields).map(field => ({
[field.name]: field.value
}));
// make sure all required values are selected
if (selectedOptions.some(el => !el)) {
// stop the click handler from continuing and show an error to the user
alert('Please select all required product options');
return false;
}
checkout.setItems([{
id: productId,
// get the value of the quantity input from the add to cart form
quantity: document.getElementById('form#add-to-cart input#quantity'),
// set the selected attributes
attributes: selectedOptions
}])
}
});
Select options on the Simpler Checkout Form
Our examples so far should work, but the User Experience is suffering from the alerts we're showing to the user. As we're using Simpler to simplify the experience and streamline the checkout process, we can allow the user to proceed with the checkout with a variable product, and allow them to select their options in the Simpler window.
This is an optional step for web-based Checkouts, but is required if you want to leverage the Checkout Links or In-Store Simpler offerings
In order for Simpler to show the Options selection screen it should be aware of the options and variations of your configurable product.
This is handled by the Product Details API leveraging the options
& variations
response properties.
When Simpler receives a product details response it checks the variations
key to decide if the product can be added to the cart as-is or the options selection screen should be shown.
If there are variations in the response it will use the options
key values to show the available options, and once the user has submitted their selection,
it will send another products request with the updated values, until it receives a response that contains no variations.
Once the final variation object has been received, it will proceed to Quote with this item in the cart.
In order to support this flow we will have to extend our Product Details Controller to take into account the case where the request contains a Configurable product ID :
class SimplerProductsController extends AbstractController {
public function postData(Request $request): Response {
$requestData = json_decode($request->getContent(), true);
$response = [
'request_id' => $requestData['request_id'],
'items' => []
];
foreach ($request['items'] as $item) {
try {
$product = CatalogService::getProductBySKU($item['id']);
$productResponse = [
'id' => $product->getSKU(),
'title' => $product->getTitle(),
'description' => $product->getDescription(),
'image_url' => $product->getImageURL(),
'shippable' => $product->requiresShipping()
];
if ($product->isConfigurable()) {
// our options array will hold all options that the user should select
$options = [];
// our variations array will hold all the variation products that the options selections
$variations = [];
foreach ($product->getConfigurableOptions() as $option) {
// get all the available values for this option
$optionValues = [];
foreach ($option->getOptionValues() as $optionValue) {
$optionValues[] = [
'id' => $optionValue->getId(),
'title' => $optionValue->getLabel()
]
}
// add the option to our options array
$options[] = [
'id' => $option->getId(),
'title' => $option->getLabel(),
'values' => $optionValues;
]
}
foreach ($product->getVariationProducts() as $variation) {
// we need to retrieve the options that define the variation
$variationOptions = $variation->getSelectedOptions();
// we will use these options as the `attributes` value of the variation.
// when the user selects these options we will traverse the variations array and get the variation that matches these attributes
$attributes = [];
foreach ($variationOptions as $variationOption) {
$attributes[$variationOption->getOptionId()] = $variationOption->getOptionValueId();
}
// and we serialize the variation product attributes alongside the attributes
$variations[] = [
'id' => $variation->getSKU(),
'title' => $variation->getTitle(),
'description' => $variation->getDescription(),
'image_url' => $variation->getImageURL(),
'attributes' => $attributes
];
}
// now that we have built the variations and options, we attach them to the parent product
$productResponse['options'] = $options;
$productResponse['variations'] = $variations;
}
$response['items'][] = $productResponse;
} catch (ProductNotFoundException $e) {
// if the product is not found return a meaningful error
return new NotFoundHttpException('The product was not found');
}
}
return $this->json($response);
}
}
You can see a full example response payload for configurable products in our Examples.