Shoppable Stories

Shoppable Stories enables your users to add products and services within the cart and checkout directly within the Story without leaving the Story watching experience.

This walkthrough shows how to use Shoppable Stories in your app.

📘

Before you begin

You need to have at least 1 active Story with the Product Catalog in it to test this feature out.

Set Up Product Listener

This walkthrough shows you how to handle Shoppable Stories events in your app. Shoppable Stories' events provide insight into what is happening on Storyly instance related to products.

📘

Before you begin

You need to have the working Storyly integration as described in Initial SDK Setup

StorylyView notifies the application when an event occurs. You can register the listener using the following code example and then override its functions to learn about specific events, which will be explained in the next sections.

// the class(indicated with self) extends StorylyProductDelegate
self.storylyView.productDelegate = self // Override event functions
...
extension ViewController: StorylyProductDelegate {
    // Override event functions
}

In order to get notification about these basic events, you should override the following functions in StorylyProductListener.

storylyUpdateCartEvent

This function will notify you about updates to the cart in a StorylyView component.

STRCartItem

This class represents an individual item in the shopping cart. You can find the all properties of STRCartItem here.

📘

Info

You can get the productId, productGroupId, price, salesPrice, currency, desc etc under change.item.

onSuccess

It represents a callback function that will be executed if the "update cart" operation is successful.

onSuccess?(STRCart((items: [STRCartItem], totalPrice: Float, oldTotalPrice: NSNumber?, currency: String) ))

onFail

It represents a callback function that will be executed if the "update cart" operation fails.

onFail(STRCartEventResult("Your Failed Message"))

Usage of storylyUpdateCartEvent

extension ViewController: StorylyProductDelegate {
     /*
     * This function will notify you about updates the cart in a StorylyView component
     *
     * storylyView: StorylyView instance in which the event is received
     * event: Storyly event type which is received
     * cart: Contains information about the items in the cart
     * change: Represents the item being changed in the cart.
     * onSuccess: It represents a callback function that will be executed if the "update cart" operation is successful
     * onFail: It represents a callback function that will be executed if the "update cart" operation fails
     *
     */
    func storylyUpdateCartEvent(storylyView: StorylyView, event: StorylyEvent, cart: STRCart?, change: STRCartItem?, onSuccess: ((STRCart) -> Void)?, onFail: ((STRCartEventResult) -> Void)?)
    {
     when (event){
                   StorylyEvent.StoryProductAdded -> {
                      print("Shopping StoryProductAdded")
                     //This event sent when a product is added.
                   }
                   StorylyEvent.StoryProductUpdated -> {
                      print("Shopping StoryProductUpdated")
                     //This event sent when a product is updated.
                   }
                   StorylyEvent.StoryProductRemoved -> {
                      print("Shopping StoryProductRemoved")
                     //This event sent when a product is removed.
                   }
                 }
      print("Shoppable ShoppableEvent: \(event)")
      print("Shoppable ShoppableCart: \(cart)")
      print("Shoppable ShoppableChange: \(change)")
      
      DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(2_000), execute: {
            onSuccess?(STRCart((items: [STRCartItem], totalPrice: Float, oldTotalPrice: NSNumber?, currency: String) ))
      })
      onFail(STRCartEventResult("Your Failed Message"))
    }
}

storylyEvent

This function will notify you about all Storyly events so that you can make redirections accordingly. Also, you can send these events to your data platform to track user journeys on your end.

Usage of storlyEvent

extension ViewController: StorylyProductDelegate {
    func storylyEvent(_ storylyView: StorylyView,
                      event: StorylyEvent,
                      product: STRProductItem?, extras: [String:String]) {
      when (event){
                    StorylyEvent.StoryCheckoutButtonClicked -> {
                        print("Shopping StoryCheckoutButtonClicked")
                    }
                    StorylyEvent.StoryCartButtonClicked -> {
                        print("Shopping StoryCartButtonClicked")
                    }
                    StorylyEvent.StoryCartViewClicked -> {
                        print("Shopping StoryCartViewClicked")
                    }
                    StorylyEvent.StoryProductSelected -> {
                        print("Shopping StoryProductSelected")
                    }
                }
    }
}

StoryCheckoutButtonClicked Event

If isProductCartEnabled is set to true, this event is sent when the "Go to Checkout" button is clicked.

StoryCartButtonClicked Event

This event is sent when the "Go to Cart" button is clicked from the success sheet.

StoryCartViewClicked Event

This event is sent when the cart icon on Story is clicked.

StoryProductSelected Event

This event is sent when the product is selected.

storylyHydration

This function will notify you to get the products placed in Stories.

extension ViewController: StorylyProductDelegate {
    /**
 * This function will notify you to products
 *
 * - Parameter storylyView: StorylyView instance in which the user interacted with a component
 * - Parameter productIds: Found product ids in stories
 */
func storylyHydration(_ storylyView: StorylyView, products: [STRProductInformation]) {
        print("storylyHydration, products: \(products)")
    }
}

Example of Product Listener

You can find the usage of Product Listener in our Recipe which is below.

Product Fallback for Hydration

There is a boolean parameter named setFallbackAvailabilitywhich is used to determine whether the product data should be hydrated from the feed or not. By default, it is set to false, which means that product data will not be hydrated from the feed. However, when this parameter is set to true, product data will be hydrated from the feed.

You can set thesetFallbackAvailability to true if you want to enable product fallback from the feed.

storylyView.storylyInit = StorylyInit(
            storylyId: storylyToken,
            StorylyConfig.Builder()
                .setProductConfig(
                    StorylyProductConfig.Builder()
                        .setFallbackAvailability(isEnabled: Bool)
                        .build()
                )
                .build()
        )

Supplemental Product Feed Handling

If you're using a multi-market Product Feed depending on language or country, to automate the Shoppable Story Group creation, you can define the version of the feed that you'd like to use per user on the client side.

That way without creating Story Groups per each region or language, you can serve the relevant content to each market with a single Story Group. To set the locale, you need to use the IETF BCP 47 format as shown below:

self.storylyView.storylyInit = StorylyInit(
            storylyId: storylyToken,
            config: StorylyConfig.Builder()
                .setLocale(locale: "tr-TR")
                .build()
)

Storyly Cart

There is a boolean parameter named setCartEnabledwhich is added cart and cart icon to top of the left of Shoppable Stories. By default, it is set to false, but, when this parameter is set as true, the cart and the cart icon will be enabled on Shoppable Stories.

storylyView.storylyInit = StorylyInit(
            storylyId: storylyToken,
            StorylyConfig.Builder()
                .setProductConfig(
                    StorylyProductConfig.Builder()
                        .setCartEnabled(isEnabled: Bool)
                        .build()
                )
                .build()
        )

Update Storyly Cart

If you already have a cart in your application, you may sync your cart with Storyly cart. This function allows you to update your Storyly cart to sync with the already existing one.

self.storylyView.updateCart(cart: STRCart)

Product Hydration

This function allows you to hydrate your products while initializing the SDK to show products to end users in Stories with the Product Catalog feature.

With the storylyHydration event, Storyly will return the IDs of the products included in Stories. With these IDs, you can use hydrateProduct function to pass relevant products' data to the SDK.

self.storlyView.hydrateProducts(products: <STRProductItem>)

STRProductItem represents products shown in Stories.
STRProductVariant represents variants of products shown in Stories in terms of color, size, etc.

Here is an example of hydrateProducts. We set the product data manually as an example, but you can set your data from your database as well.

let products = [
     STRProductItem(
         productId:"1",
         productGroupId:"1",
         title:"High-waist midi skirt",
         url:"https://www.storyly.io/",
         description:"High-waist midi skirt made of a viscose blend. Featuring a slit at the hem and invisible zip fastening.",
         price: 25.99, salesPrice: 25.99,
         currency:"USD",
         
         imageUrls:["https://random-feed-generator.vercel.app/images/clothes/group-1/1-6D7868.jpg","https://random-feed-generator.vercel.app/images/clothes/group-1/2-6D7868.jpg","https://random-feed-generator.vercel.app/images/clothes/group-1/3-6D7868.jpg","https://random-feed-generator.vercel.app/images/clothes/group-1/4-6D7868.jpg"],
         variants:[
             STRProductVariant(name:"color",value:"#6D7868"),
             STRProductVariant(name:"size",value:"XS")
         ]
     ),
     STRProductItem(
         productId:"3",
         productGroupId:"1",
         title:"High-waist midi skirt",
         url:"https://www.storyly.io/",
         description:"High-waist midi skirt made of a viscose blend. Featuring a slit at the hem and invisible zip fastening.",
         price: 25.99, salesPrice: 25.99,
         currency:"USD",
         
         imageUrls:["https://random-feed-generator.vercel.app/images/clothes/group-1/1-6D7868.jpg","https://random-feed-generator.vercel.app/images/clothes/group-1/2-6D7868.jpg","https://random-feed-generator.vercel.app/images/clothes/group-1/3-6D7868.jpg","https://random-feed-generator.vercel.app/images/clothes/group-1/4-6D7868.jpg"],
         variants:[
             STRProductVariant(name:"color",value:"#6D7868"),
             STRProductVariant(name:"size",value:"M")
         ]
     ),
     STRProductItem(
         productId:"4",
         productGroupId:"1",
         title:"High-waist midi skirt",
         url:"https://www.storyly.io/",
         description:"High-waist midi skirt made of a viscose blend. Featuring a slit at the hem and invisible zip fastening.",
         price: 25.99, salesPrice: 25.99,
         currency:"USD",
         
         imageUrls:["https://random-feed-generator.vercel.app/images/clothes/group-1/1-6D7868.jpg","https://random-feed-generator.vercel.app/images/clothes/group-1/2-6D7868.jpg","https://random-feed-generator.vercel.app/images/clothes/group-1/3-6D7868.jpg","https://random-feed-generator.vercel.app/images/clothes/group-1/4-6D7868.jpg"],
         variants:[
             STRProductVariant(name:"color",value:"#6D7868"),
             STRProductVariant(name:"size",value:"L")
         ]
     ),
]
...
self.storlyView.hydrateProducts(products: products)

🚧

Warning

For a Story that needs product data, if that product data is not provided, the Story Group containing that Story is not displayed.

Please make sure that you pass the necessary product data to avoid any issue.

Client-Side Automation

This functionality enables the generation of dynamic, interactive Stories tailored for users without the necessity of uploading a feed to the Storyly Dashboard.

storylyView.storylyInit = StorylyInit(
            storylyId: storylyToken,
            StorylyConfig.Builder()
                .setProductConfig(
                    StorylyProductConfig.Builder()
                        .setProductFeed(feed: <String : [STRProductItem]>)
                        .build()
                )
                .build()

STRProductItem represents products shown in Stories.
STRProductVariant represents variants of products shown in Stories in terms of color, size, etc.

let productFeed = [
        "sg_client_id": [
            STRProductItem(
                productId:"1",
                productGroupId:"1",
                title:"High-waist midi skirt",
                url:"https://www.storyly.io/",
                description:"High-waist midi skirt made of a viscose blend. Featuring a slit at the hem and invisible zip fastening.",
                price: 25.99,
                salesPrice: 25.99,
                currency:"USD",
                imageUrls:["https://random-feed-generator.vercel.app/images/clothes/group-1/1-6D7868.jpg","https://random-feed-generator.vercel.app/images/clothes/group-1/2-6D7868.jpg","https://random-feed-generator.vercel.app/images/clothes/group-1/3-6D7868.jpg","https://random-feed-generator.vercel.app/images/clothes/group-1/4-6D7868.jpg"],
                variants:[],
              ctaText: "Buy now"
        )],
        "sg_client_id_1":[
            STRProductItem(
                productId:"1",
                productGroupId:"1",
                title:"High-waist midi skirt",
                url:"https://www.storyly.io/",
                description:"High-waist midi skirt made of a viscose blend. Featuring a slit at the hem and invisible zip fastening.",
                price: 25.99, 
                salesPrice: 25.99,
                currency:"USD",
                imageUrls:["https://random-feed-generator.vercel.app/images/clothes/group-1/1-6D7868.jpg","https://random-feed-generator.vercel.app/images/clothes/group-1/2-6D7868.jpg","https://random-feed-generator.vercel.app/images/clothes/group-1/3-6D7868.jpg","https://random-feed-generator.vercel.app/images/clothes/group-1/4-6D7868.jpg"],
                variants:[],
              ctaText: "Buy now"
            )]
    ]
storylyView.storylyInit = StorylyInit(
            storylyId: storylyToken,
            StorylyConfig.Builder()
                .setProductConfig(
                    StorylyProductConfig.Builder()
                        .setProductFeed(productFeed)
                        .build()
                )
                .build()
        )

📘

Tip

sg_client_id is string value that you can set it on the Storyly Dashboard for each Story Group as a string.