Prefilled Cart Add-On

Using the Prefilled Cart Template in FlowBuilder

Prefilled Cart Activity in Flow

Adding the workflow property to the storefront configuration

Your storefront should have the workflow property present in the configuration. If it doesn't, you should add it:

mutation UpdateStorefront($name: String, $data: UpdateStorefrontInput!) {
  updateStorefront(name: $name, data: $data) {
    id
  }
}
{
  "name": "<<storefront-name>>",
  "data": {
    "configuration": {
      "workflow": {
        "<<worflow-name>>": {
          "ng": true,
          "name": "<<workflow-name>>",
          "channel": "whatsapp",
          "channelUid": "<<channel-phone-number>>"
        },
      }
    }
  }
}

Adding the pre-fill cart add-on to your storefront

⚠️ Add just ONE of the add-on configurations below.

Configuration based on the last order (admin workspace)

mutation CreateAddOn($storefrontName: String!, $data: AddOnInput!) {
  createAddOn(storefrontName: $storefrontName, data: $data) {
    id
  }
}
{
  "storefrontName": "<<storefront_name>>",
  "data": {
    "type": "CREATE_SESSION",
    "flavor": "REST",
    "config": {
          "url": "https://commerce-functions.yalochat.dev/v1/prefill",
          "method": "POST",
          "authMethod": "BASIC",
          "token": "",
          "body": {
            "type": "lastOrder"
          }
    },
    "executionTime": "AFTER"
  }
}

Configuration based on recommendations (admin workspace)

mutation CreateAddOn($storefrontName: String!, $data: AddOnInput!) {
  createAddOn(storefrontName: $storefrontName, data: $data) {
    id
  }
}
{
  "storefrontName": "<<storefront_name>>",
  "data": {
    "type": "CREATE_SESSION",
    "flavor": "REST",
    "config": {
          "url": "https://commerce-functions.yalochat.dev/v1/prefill",
          "method": "POST",
          "authMethod": "BASIC",
          "token": "",
          "body": {
            "type": "suggestedOrder"
          }
    },
    "executionTime": "AFTER"
  }
}

Adding the pre-fill cart on LUA

📌 You can see this implementations in prefillCart activity in yalo-commerce-product-prefill flow

Configuration based on the last order (flow studio)

-- staging environment "https://commerce-functions.yalochat.dev/v1"
functionsUrl = Profile.get('storefrontFunctionUrl')
url = functionsUrl.."/prefill"
commerceSessionObj = JSON.decode(Context.get('commerceSession'))
-- quick fix to make it work as the addon
commerceSessionObj._id = commerceSessionObj.id

artifacts = {
    storefrontName = Profile.get('storefrontName'),
		-- this session needs _id, customerUid or it will return 500
    session = commerceSessionObj
}

inside = {
    type = "lastOrder",
}

body = {
    body = inside,
    artifacts = artifacts
}

headers = {
  ["content-type"] = "application/json"
}

response = HTTP.post(url, body, headers)

if response.status == 200 then
  Template.text("We already prefill your cart")
  Workflow.branch(34)
else
  Template.text("We couldn't get your last order")
  Workflow.branch(62)
end

Configuration based on recommendations (flow studio)

-- staging environment "https://commerce-functions.yalochat.dev/v1"
functionsUrl = Profile.get('storefrontFunctionUrl')
url = functionsUrl.."/prefill"
commerceSessionObj = JSON.decode(Context.get('commerceSession'))
commerceSessionObj._id = commerceSessionObj.id

artifacts = {
    storefrontName = Profile.get('storefrontName'),
		-- this session needs _id, customerUid, workflow { name, channel, channelUid, userUid }, configuration { workflow } or it will return 500
    session = commerceSessionObj
}

inside = {
    type = "suggestedOrder",
}

body = {
    body = inside,
    artifacts = artifacts
}

headers = {
  ["content-type"] = "application/json"
}

response = HTTP.post(url, body, headers)

if response.status == 200 then
  Template.text("We already prefill your cart")
  Workflow.branch(34)
else
  Template.text("We couldn't get your suggested order")
  Workflow.branch(62)
end

Configuration based on fixed items (flow studio)

-- staging environment "https://storefront-user.yalochat.dev/v3/user/storefronts" 
storefrontName = Profile.get('storefrontName')
userUrl = Profile.get('storefrontUserUrl')
commerceSessionObj = JSON.decode(Context.get('commerceSession'))
commerceSession = commerceSessionObj.id

headers = {
    ["content-type"] = "application/json"
}

-- prefilling 3 items of sku 10133, you can prefill n items and n skus
createSessionPayload = {
    query = 'mutation UpdateStorefront($storefrontName: String!, $sessionUid: ID!, $items: [CartProductInput]) { prefillCart(storefrontName: $storefrontName, sessionUid: $sessionUid, items: $items) { id }}',
    variables = string.format('{ "storefrontName": "%s", "sessionUid": "%s", "items": [{"sku": "%s", "quantity": %d }]}', storefrontName, commerceSession, "10133", 3)
}

-- Prefill cart
getClientResponse = HTTP.post(userUrl, createSessionPayload, headers)

if getClientResponse.status == 200 then
    if getClientResponse.data["errors"] == null then 
        Template.text("We already prefill your cart")
        Workflow.branch(34)
    else
        Template.text("We couldn't prefill your cart")
        Workflow.branch(62)
    end 
else
    Template.text("We couldn't prefill your cart")
    Workflow.branch(62)
end

Creating a session (user workspace)

Sessions should be created including the workflow property:

mutation CreateSession(
  $createSessionStorefrontName: String!
  $createSessionData: SessionInput!
) {
  createSession(
    storefrontName: $createSessionStorefrontName
    data: $createSessionData
  ) {
    id
  }
}
{
  "createSessionStorefrontName": "<<storefront-name>>",
  "createSessionData": {
    "code": "<<customer-phone-number",
    "validateBy": "phoneNumber",
    "validate": true,
    "workflow": {
      "name": "<<workflow-name>>",
      "userUid": "<<users-phone-number>>"
    }
  }
}
💡 If you use the Commerce SDK to create a session in FlowBuilder, the `workflow` property will be set automatically for you.

Once the session is created, a cart will be create as well. If there are orders and the pre-fill is of type lastOrder, the cart will be pre-filled with the products of that order; if it is of type suggestedOrder, it will call an endpoint to get the product recommendations for that user and pre-fill the cart with those products.

If there are no orders or no recommendations, the cart will remain empty.

If there are products in the last order or in the recommendations that are out of stock or have been removed (inactive), they won't be added to the cart and won't throw any errors but you can see them in the warnings:

query Cart($storefrontName: String!, $sessionUid: ID!) {
  cart(storefrontName: $storefrontName, sessionUid: $sessionUid) {
    id
    items {
      sku
      quantity
      price
    }
    warnings
  }
}
{
  "storefrontName": "<<storefront-name>>",
  "sessionUid": "<<session-id>>"
}

Using the Pre-fill Cart Function without an Add-On

When you use the pre-fill cart add-on in a storefront, the configuration is easier but you lose flexibility, as the add-on will of a fixed type (lastOrder or suggestedOrder) and will be applied to all customers of that Flow.

To have more flexibility, you can call the pre-fill cart function directly, so you can choose when and how to call it.

Disabling your add-on

In case you already have a pre-fill cart add-on in your storefront, you can simply disable it using this mutation in the admin workspace:

mutation UpdateAddOn($storefrontName: String!, $updateAddOnId: ID!, $data: AddOnInputUpdate!) {
  updateAddOn(storefrontName: $storefrontName, id: $updateAddOnId, data: $data) {
    id
  }
}
{
    "storefrontName": "_STOREFRONT_NAME_",
    "updateAddOnId": "_ADD_ON_ID_",
    "data": {
        "type": "CREATE_SESSION",
        "flavor": "REST",
        "config": {
            "url": "https://commerce-functions.yalochat.dev/v1/prefill",
            "method": "POST",
            "authMethod": "BASIC",
            "token": "",
            "body": {
                "type": "suggestedOrder"
            }
        },
        "status": "DISABLED"
    }
}

Calling the pre-fill cart function directly

Whether you're using the lastOrder or the suggestedOrder option, the only thing that changes in the payload is that, the body.type property.

Here's an example of a minimal payload:

{
    "artifacts": {
        "storefrontName": "_STOREFRONT_NAME_",
        "session": {
            "_id": "_SESSION_ID_",
            "customerUid": "_CUSTOMER_ID_",
            "workflow": {
                "channelUid": "_WORKFLOW_CHANNEL_ID_",
                "userUid": "_USERS_PHONE_NUMBER_",
                "name": "_WORKFLOW_NAME_"
            },
            "configuration": {
                "workflow": {
                    "_WORKFLOW_NAME_": {
                        "name": "_WORKFLOW_NAME_"
                    }
                }
            }
        }
    },
    "body": {
        "type": "suggestedOrder" // CAN ALSO BE lastOrder
    }
}

Calling the pre-fill cart function with Lua in FlowBuilder

Here's an example of how to call the function in Lua, to be used within your Flow in FlowBuilder:

-- STEP 1: create the session

-- this also could be 'code' when validating by a customer's document instead
validateBy = 'phoneNumber' 
userId = tostring(Context.get('userId'))

commerceSession = Commerce.sessionCreate(validateBy, userId)
Context.set('commerceSession', commerceSession)

if commerceSession.status == 'ok' then
	Context.set('commerceSession', commerceSession.data)
	Workflow.branch(SUCCESS_STEP_ID)
end

Workflow.branch(FAIL_STEP_ID)
-- STEP 2: call the pre-fill cart function

commerceSession = JSON.decode(Context.get("commerceSession"))

prefillCartFunctionUrl = "https://commerce-functions-develop.yalochat.dev/v1/prefill"
prefillType = "suggestedOrder" -- can also be "lastOrder"

payload = {
  artifacts  = {
        storefrontName  = "STORE_FRONT_NAME",
        session  = commerceSession
		},
    body  = {
        type  = prefillType,
    },
}

headers = {
    ["content-type"] = "application/json"
}

response = HTTP.post(prefillCartFunctionUrl, payload, headers)

if response.status == 200 then
	Workflow.branch(SUCCESS_STEP_ID)
end

Workflow.branch(FAIL_STEP_ID)

Checking if there are pre-fill cart recommendations

To do that, we can call the ML endpoint directly:

In the Flow, we might want to check if there is a pre-fill cart recommendation for that user before trying to pre-fill their cart.

curl --request POST 'https://api-staging2.yalochat.com/v0/ml/features/v1/features/pre-filled-cart/predict' \
--header 'Authorization: Bearer <<BEARER_TOKEN>>' \
--header 'Content-Type: application/json' \
--data-raw '{
    "botId": "<<WORKFLOW_NAME>>",
    "userId": "<<USERS_PHONE_NUMBER>>"
}'
[
    {
        "value": {
            "amount": PRODUCT_QUANTITY,
            "productId": "PRODUCT_ID",
            "sku": "PRODUCT_SKU"
        },
        "weight": PRODUCT_RECOMMENDATION_WEIGHT
    }
]

You should expect an empty array if there are no recommendations for that combination of Flow name and user ID.

Checking for recommendations with Lua in FlowBuilder

Here's an example of how to check if there are recommendations in FlowBuilder:

preFillCartUrl = "https://api-staging2.yalochat.com/v0/ml/features/v1/features/pre-filled-cart/predict"

headers = {
    ["content-type"] = "application/json",
    ["Authorization"] = "Bearer <<BEARER_TOKEN>>"
}

getSuggestedCartPayload = {
    botId = "FLOW_NAME",
    userId = "USERS_ID"
}
    
response = HTTP.post(preFillCartUrl, getSuggestedCartPayload, headers)

if response.status ~= 200 then
	Workflow.branch(FAIL_STEP_ID)
end

items = {}
size = 0

for _, value in pairs(response.data) do 
    item = {}
    item["sku"] = value["value"]["sku"]
    item["quantity"] = value["value"]["amount"]
    table.insert(items, item)
    size = size + 1
end

hasCartSuggestions = size > 0

Context.set("prefillCartItems", items)
Context.set("hasCartSuggestions", hasCartSuggestions)

if hasCartSuggestions then
	Workflow.branch(SUCCESS_STEP_ID)
end

Workflow.branch(FAIL_STEP_ID)

Checking if the customer has an order

If you want to include the option to pre-fill the cart with the last order's products, you may want to check if the customer has orders.

At the moment, we'll have to use the admin workspace to do that:

curl --request POST 'https://storefront-admin.yalochat.dev/v3/admin/storefronts' \
  --header 'Content-Type: application/json' \
  --data-raw '{
                "query": "query Customer($storefrontName: String!, $customerId: ID!) {customer(storefrontName: $storefrontName, id: $customerId) {orders {id}}}",
                "variables": {
                  "storefrontName": "STOREFRONT_NAME,
                  "customerId": "CUSTOMER_ID"
                }
              }'
{
  "data": {
    "customer": {
      "orders": [
        {
          "id": "62cd9fd199db6c5202aeebfa"
        }
      ]
    }
  }
}

If the customer has no orders, you'll receive an empty array instead:

{
  "data": {
    "customer": {
      "orders": []
    }
  }
}

Checking for a customer's orders with Lua in FlowBuilder

Here's an example of how to check if there are orders for a customer in FlowBuilder:

headlessAdminUrl = "https://storefront-admin.yalochat.dev/v3/admin/storefronts"

headers = {
    ["content-type"] = "application/json",
}

getCustomerOrdersPayload = {
    query = "query Customer($storefrontName: String!, $customerId: ID!) {customer(storefrontName: $storefrontName, id: $customerId) {orders {id}}}",
    variables = {
        storefrontName = "STOREFRONT_NAME",
        customerId = "CUSTOMER_ID"
    }
}

response = HTTP.post(headlessAdminUrl, getCustomerOrdersPayload, headers)
responseData = response.data.data
responseError = response.data.errors

if response.status == 200 and responseError == nil then
    if tostring(responseData.customer) == 'null' then
        Workflow.branch(CUSTOMER_NOT_FOUND_STEP_ID)
    end

    if #responseData.customer.orders > 0 then
        Workflow.branch(HAS_ORDERS_STEP_ID)
    end
    
    Workflow.branch(HAS_NO_ORDERS_STEP_ID)
end

Workflow.branch(ERROR_GET_CUSTOMER_STEP_ID)