Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Overview

To display valid offers and rewards on the UI, the client needs to retrieve data from two sources: the Loyalty Engine (Through the Loyalty Middleware) and Sanity (RBI’s CMS). The Loyalty Engine will provide incentives for which the user is "eligible," meaning they satisfy all necessary criteria (rules). Meanwhile, Sanity will offer information about active incentives, and reward categories, as well as all visual display content, such as names, images, descriptions, and more.

The client must hit both of the previously mentioned data sources and intersect the data to decide what to present. The sequence in which these queries are executed will depend on the client and its implementation. It is advised to first query Sanity for all "sorted offers" (active system-wide offers), “config offers” (possible personalized offers for any given user), and "reward categories" (active rewards), and then utilize those identifiers to request pertinent information from the Loyalty Engine.

Querying Sanity using GraphQL

Info

GraphQL URL (Playground Available): https://czqk28jt.api.sanity.io/v1/graphql/dev_bk_us/default

  • Replace dev_bk_us with the {{stage}}_{{brand}}_{{country}} that you desire to query.

  • Sanity GraphQL can be hit via a POST request. Please reference the POST URL structure for each of the sample queries below.

  • Please use this playground to play with the queries below and examine the schema for fields you may wish to request.

Example Offers Query

  • The _id is subject to the dataset you are using. For the most part, it will be feature-loyalty-offers-ui-singleton, however, some markets have drifted from this convention

  • Sample Query:

Code Block
query getSanityOffers {
	LoyaltyOffersUI(id: "feature-loyalty-offers-ui-singleton") {
	# Available for all users.
    sortedSystemwideOffers {
      _id
      loyaltyEngineId
      name {
        enRaw
      }
    }
    # These are "templates", personalized offers get created based on this template.
    # You should use these only to display content (images, names, etc), and display only the actual offer from the engine.
    liveConfigOffers {
      _id
      loyaltyEngineId
      name {
        enRaw
      }
    }
  }
}

Example Rewards Query

...

Contents

Table of Contents
stylenone

Glossary of terms

The following terms are used throughout this document:

  • Offer: Offers are discounted products - this may come in the form of individual item discounts, bundle discounts, dollars-off discounts, or percent-off discounts.

  • System-Wide Offer: System-wide offers are the ‘base’ offers in the loyalty offers system. These are available to all users who meet the required criteria for redemption and do not require any steps or processes to be assigned to a specific user. You’ll see these referenced as GLOBAL offers.

  • Configuration Offer: Config. offers are one-half of the required components to create a personalized offer. These are templates that only exist to provide the configuration for personalized offers. As such, they will not appear on their own on the offers page if they are not referenced by a personalized offer and the entity by itself does not represent a redeemable incentive.

  • Personalized Offer: Personalized offers are offers assigned specifically to a particular user. Every time a personalized offer is assigned to a user, it creates a unique offer record that can only be used for that particular user.

  • Rewards: Rewards are a special type of product that usually has a price of zero, and can be exchanged for points accumulated from previous purchases.

  • Loyalty Middleware: Public facing API for Loyalty. Consists of both a REST API and a GraphQL endpoint.

  • Loyalty Engine: The core Loyalty component that handles everything behind the scenes.

  • Loyalty Platform: Refers to both Middleware and Engine as a whole.

  • Sanity: RBI’s Content Management System (CMS).

Overview

To display valid offers and rewards on the kiosk UI, the client needs to retrieve data from two sources: the Loyalty Engine (Through the Loyalty Middleware) and Sanity (RBI’s CMS). The Loyalty Engine will provide incentives for which the user is "eligible," meaning they satisfy all necessary criteria (rules). Meanwhile, Sanity will offer information about active incentives, and reward categories, as well as all visual display content, such as names, images, descriptions, and more.

The client must hit both of the previously mentioned data sources and intersect the data to decide what to present. The sequence in which these queries are executed will depend on the client and its implementation. It is advised to first query Sanity for all "sorted offers" (active system-wide offers), “config offers” (possible personalized offers for any given user), and "reward categories" (active rewards), and then utilize those identifiers to request pertinent information from the Loyalty Engine.

Querying Sanity

To query Sanity you must us GraphQL, as this is a more widely known querying language. Vendors can use the provided GraphQL playground to be able to see the whole schema during development and make necessary adjustments to their queries with ease.

Info

Recommended reading:

Using Sanity GraphQL

Info

GraphQL URL (Playground Available):

https://czqk28jt.api.sanity.io/v1/graphql/staging_bk_de/default

  • Replace staging_bk_de with the {{stage}}_{{brand}}_{{country}} that you desire to query.

  • Sanity GraphQL can be hit via a POST request. Please reference the POST URL structure for each of the sample queries below.

  • Please use this playground to play with the queries below and examine the schema for fields you may wish to request.

Example offers query

  • The Sanity ID for the live offers document, for the most part, will be feature-loyalty-offers-ui-singleton, however, some markets have drifted from this conventionSample Query.

  • An offer is open only if it has the ruleset RequiresAuthentication = False. If the ruleset RequiresAuthentication is not present in the offer OR if the ruleset RequiresAuthentication = True, then the offer is closed and should only show for guests that are signed into the kiosk.

  • Sample query:

query
Code Block
language
graphql
query getSanityRewardsgetSanityOffers {
  RewardListLoyaltyOffersUI(id: "reward-listfeature-loyalty-offers-ui-singleton") {
    rewardCategories# {Available for all users.
   _id # You can use these to labellookup {assets from global.
    sortedSystemwideOffers {
en      _id
}      loyaltyEngineId
rewards {     name {
  ... on Reward {   enRaw
      }
# Sanity ID of the reward rules {
        # Returns _idthe value of the AuthenticationRequired rule
     # Loyalty Engine ID... toon beRequiresAuthentication used{
in the Loyalty Engine query if needed    requiresAuthentication
      loyaltyEngineId  }
        # RewardAdd contentmore torules displayas toneeded the user (name, image, description...here (e.g. FirstOrderOnly, LoyaltyBetweenDates, etc)
      }
   name {}
    # These are config. offers (templates).
  en  # You should use these only to display content }(images, names, etc).
    # You can use imagethese {to lookup assets from the actual Personalized offers.
     enliveConfigOffers {
      _id
      loyaltyEngineId
asset {     name {
        enRaw
 url     }
    }
  }
 }
       }

Example reward query

  • The _id is subject to the dataset you are using. For the most part it will be reward-list-singleton, however, some markets have drifted from this convention

  • Sample query:

Code Block
query getSanityRewards {
  RewardList(id: "reward-list-singleton") {
    }rewardCategories {
      _id
  }    label {
     description {  en
      }
   enRaw   rewards {
      }  ... on Reward {
     # Here we add the rules# weSanity want to check forID of the rewardsreward
          # e.g., LoyaltyPoints (reward points cost), RewardPrice (reward money price),
          # SubtotalSpend (minimum subototal to spend for redeeming the reward)
   _id
          # Loyalty Engine ID to be used in the Loyalty Engine query if needed
          loyaltyEngineId
      ruleSet {   # Reward content to display to the user (name, image, description...)
 on LoyaltyPoints {       name {
      points      en
      }    }
        ... on RewardPriceimage {
            en {
price             } asset {
             ... on SubtotalSpend {url
              minimumSpend}
            }
          }
          #description Benefit{
of the reward, it can be either a Combo, Item, OfferDiscount  enRaw
        # or Picker}
          # e.g., OfferDiscount (It can be a discount Here we add the rules we want to check for the wholerewards
cart or         # e.g., LoyaltyPoints #(reward apoints discountcost), forRewardPrice a(reward specificmoney productprice),
          incentives# {SubtotalSpend (minimum subototal to spend for redeeming the reward)
    ... on OfferDiscount {   ruleSet {
          discountType  ... on LoyaltyPoints  {
        discountValue       points
       discountProduct {    }
            ... on ItemRewardPrice {
              price
   name {        }
            en... on SubtotalSpend {
              minimumSpend
}            }
    }        # Add more ruleSets as needed here   (e..g. onFirstOrderOnly, ComboLoyaltyBetweenDates, {etc)
            # See the screenshot below for namehow {get the list of available ruleSet from GraphQL Playground
          }
 en         # Benefit of the reward, it can be either a Combo, Item, OfferDiscount
    }      # or Picker
        }  # e.g., OfferDiscount (It can be a discount for the whole cart or }
          # a }discount for a specific product)
      }    incentives {
     # Reward redemption method available   ... on OfferDiscount {
    redemptionMethod
          # Here we can get the needed PLUs by looking for a specific vendor
          # e.g., Partner constant PLU
          vendorConfigs {
            partner {
              _type
              constantPlu
              pluType
            }
          }
        }
      }
    }
  }
}

Querying Loyalty Engine

Info

GraphQL URL (Playground Available): https://euc1-dev-bk-loyalty-middleware.rbictg.com/graphql

  • Replace dev-bk with the {{stage}}-{{brand}} that you desire to query. You will need to prefix the URL with the AWS region that your market lives in. Please communicate with RBI reps to know which region your market is hosted.

  • Make sure to set the x-ui-region header to the country you want to query:

...

  • Please use this playground to play with the queries below and examine the schema for fields you may wish to request.

  • Some where filters that may be relevant to Kiosk

    • ids - a collection of Engine IDs for offers/rewards. This is the loyaltyEngineId field on every sanity offer/reward document.

    • omitInvalids - This controls whether invalid incentives will be returned. Only incentives that failed “fixable” rules will be returned if this is set to false. A fixable rule is one that the user can change behavior to make the rule pass, like the minSpend, the user can add more items to their cart to meet the requirement. An absolute failure like date-band will not be returned regardless of what the value of this filter is

      • This is useful for displaying error / in-line messages in the UI

    • serviceMode - some incentives can only be redeemed if the service mode meets the required ruleset

    • storeId - some incentives can only be redeemed at certain stores

    • subtotalAmount = some incentives require a minimum spend

    • cartEntries - some incentives require certain items to be in the cart, this rule uses the sanity id of each rbi product

    • paymentMethod - some incentives require certain payment methods to be used

Example Offers Query

Info

You should use the sanityId to match these objects to the ones from the above query.

type = "GLOBAL" refers to Systemwide Offers (sorted offers) and "PERSONALIZED" refers to Config Offers (live config offers)

Code Block
query getLoyaltyOffers {
  loyaltyOffersV2(loyaltyId: "<USER_LOYALTY_ID>", where: { omitInvalids: false }) {
    id
    name
    type
    sanityId
    errors {
      code
      ruleId
    }
  }
}

Example Offers Query Response

Code Block
{
  "data": {
    "loyaltyOffersV2": [
      {
        "id": "a4c24211-2d36-47be-bad8-8deea437dbfa",          discountType
              discountValue
              discountProduct {
                ... on Item {
                  name {
                    en
                  }
                }
                ... on Combo {
                  name {
                    en
                  }
                }
              }
            }
          }
          # Reward redemption method available
          redemptionMethod
          # Here we can get the needed PLUs by looking for a specific vendor
          # e.g., Partner constant PLU
          vendorConfigs {
            partner {
              _type
              constantPlu
              pluType
            }
          }
        }
      }
    }
  }
}

Expand
titleGetting the list of ruleSets from GraphQL Playground

To get list of ruleSet available, navigate to https://czqk28jt.apicdn.sanity.io/v1/graphql/dev_bk_aq/default, and search for “ruleSet” in the Schema explorer as seen here:

image-20240813-035328.pngImage Added

Querying the Loyalty Platform

Info

GraphQL URL (Playground Available):

https://euc1-dev-bk-loyalty-middleware.rbictg.com/graphql

  • Replace euc1 with the AWS Short Region that your market belongs to. Please communicate with RBI reps to know which region your market is hosted.

  • Replace dev-bk with the {{stage}}-{{brand}} that you desire to query. You will need to prefix the URL with the AWS region that your market lives in. Please communicate with RBI reps to know which region your market is hosted.

  • When querying the loyalty platform, please make sure to set the x-ui-region header to the country code you want to query, in uppercase, for example:

...

  • Please use this playground to play with the queries below and examine the schema for fields you may wish to request.

  • Some where filters that may be relevant to kiosks:

    • ids - a collection of Engine IDs for offers/rewards. This is the loyaltyEngineId field on every sanity offer/reward document.

    • omitInvalids - This controls whether invalid incentives will be returned. Only incentives that failed “fixable” rules will be returned if this is set to false. A fixable rule is one that the user can change behavior to make the rule pass, like the minSpend, the user can add more items to their cart to meet the requirement. An absolute failure like date-band will not be returned regardless of what the value of this filter is

      • This is useful for displaying error / in-line messages in the UI

    • serviceMode - some incentives can only be redeemed if the service mode meets the required ruleset

    • storeId - some incentives can only be redeemed at certain stores

    • subtotalAmount = some incentives require a minimum spend

    • cartEntries - some incentives require certain items to be in the cart, this rule uses the sanity id of each rbi product

    • paymentMethod - some incentives require certain payment methods to be used

Example offers query

Info

You should use the sanityId to match these objects to the ones from the above query.

type = "GLOBAL" refers to Systemwide Offers (sorted offers) and "PERSONALIZED" refers to Config Offers (live config offers)

Code Block
query getLoyaltyOffers {
  loyaltyOffersV2(loyaltyId: "<USER_LOYALTY_ID>", where: { omitInvalids: false }) {
    id
    name
    type
    sanityId
    errors {
      code
      ruleId
    }
  }
}

Example offers query response

Code Block
{
  "data": {
    "loyaltyOffersV2": [
      {
        "id": "a4c24211-2d36-47be-bad8-8deea437dbfa",
        "name": "$6 Let's Get This Bacon Meal",
        "type": "GLOBAL",
        "sanityId": "AAA-AAA-AAA",
        "errors": null
      },
      {
        "id": "fbba4e93-eecd-42b6-a4ed-333856d47bda",
        "name": "Delivery Offer - $8 The Spicy One Meal",
        "type": "GLOBAL",
        "sanityId": "BBB-BBB-BBB",
        "errors": null
      },
      {
        "id": "2a7b2f20-1662-4854-9f4b-e9974200f294",
        "name": "Support 50% Discount Offer",
        "type": "PERSONALIZED",
        "sanityId": "CCC-CCC-CCC",
        "errors": null
      },
      {
        "id": "6646b80b-dea9-4bf4-81f5-390554926987",
        "name": "Loyalty Upsize Swap",
        "errors": [
          {
            "namecode": "$6 Let's Get This Bacon Meal","unsatisfied-cart-requirements",
            "typeruleId": "GLOBAL","cart-requirement"
        "sanityId": "AAA-AAA-AAA",
 }
       "errors": null]
      },
    ]
 { }
}

Example rewards query

Code Block
query getLoyaltyRewards {
  loyaltyRewards(loyaltyId: "id": "fbba4e93-eecd-42b6-a4ed-333856d47bda",
   <USER_LOYALTY_ID>", where: { omitInvalids: false }) {
    "name": "Delivery Offer - $8 The Spicy One Meal",
  id
    name
    errors {
     "type": "GLOBAL", code
      ruleId
 "sanityId": "BBB-BBB-BBB",  }
  }
}

Example rewards query response

Code Block
{
  "errorsdata": null
{
     },"loyaltyRewardsV2": [
      {
        "id": "2a7b2f203b5bdddc-1662c7c9-485442d8-9f4b9332-e9974200f294b0c3f36c8e1c",
        "name": "SupportValue 50% Discount Offer",
        "type": "PERSONALIZED",
        "sanityId": "CCC-CCC-CCCPowerade Zero",
        "errors": null
      },
      {
        "id": "6646b80b728aaedd-dea93234-4bf44eae-81f589af-39055492698735cd5672adcb",
        "name": "LoyaltyLarge UpsizeDr. SwapPepper",
        "errors": [null
      },
   {    {
        "codeid": "unsatisfied-cart-requirements",
   480122d7-86ff-4db0-965c-7dbb72bb5179",
        "ruleIdname": "cart-requirement"Value Soft Drinks",
        }
        ]"errors": null
      }
    ]
  }
}

Example Rewards Query

Code Block
query getLoyaltyRewards {
  loyaltyRewards(loyaltyId: "<USER_LOYALTY_ID>", where: { omitInvalids: false }) {
    id
    name
    errors {
      code
      ruleId
    }
  }
}

Example Rewards Response

...

 

Mapping loyalty offers between Sanity and the Loyalty Engine

After retrieving all the available offers from both sources, you need to map this data to get a complete list of offers available to the user. Below there is a simple example in JavaScript demonstrating how to perform this mapping:

Note

This example focuses on the basic data mapping process. It does not include logic related to offer rules, authentication requirements, or error handling. Each implementation should incorporate the necessary logic based on its specific needs.

Code Block
const { liveConfigOffers, sortedSystemwideOffers } = sanityOffers;
const { loyaltyOffersV2 } = loyaltyEngineOffers;

const allAvailableUserOffers = loyaltyOffersV2
  .map(loyaltyOffer => {
    let sanityOffer;

 

...

 

...

  if (loyaltyOffer.type === 'GLOBAL') {
  

...

    sanityOffer = sortedSystemwideOffers.find(
      

...

 

...

 offer => offer.loyaltyEngineId === loyaltyOffer.id
 

...

     );
 

...

   } else if (loyaltyOffer.type === 

...

'PERSONALIZED') {
      sanityOffer = liveConfigOffers.find(
    

...

 

...

 

...

 

...

 offer => offer.loyaltyEngineId === loyaltyOffer.id
   

...

 

...

  );
    }

...



    if (sanityOffer) {
      return sanityOffer;
 

...

   }
  })
  .filter(offer => offer 

...

 

!== undefined);

In this example 💡:

  • sanityOffers contains the offers retrieved from Sanity, destructured into liveConfigOffers (personalized offer templates) and sortedSystemwideOffers (system-wide offers).

  • loyaltyEngineOffers contains the offers retrieved from the Loyalty Engine, specifically the loyaltyOffersV2 array.

  • allAvailableUserOffers contains all the available offers for the user.