...
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
There are two ways of querying Sanity:
Using GraphQL
Using GROQ
We strongly suggest using To query Sanity you must us GraphQL, as this is a more widely known querying language. Additionally, using Vendors can use the provided GraphQL playground , vendors will 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 |
...
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 convention.Sample query:
An offer is open only if it has the ruleset
RequiresAuthentication = False
. If the rulesetRequiresAuthentication
is not present in the offer OR if the rulesetRequiresAuthentication = True
, then the offer is closed and should only show for guests that are signed into the kiosk.Sample query:
Code Block | ||
---|---|---|
| ||
query getSanityOffers { LoyaltyOffersUI(id: "feature-loyalty-offers-ui-singleton") { # Available for all users. # You can use these to lookup assets from global. sortedSystemwideOffers { _id loyaltyEngineId name { enRaw } } rules { # These are config. offers (templates). # Returns the #value Youof shouldthe useAuthenticationRequired theserule only to display content (images, names, etc). ... on RequiresAuthentication #{ You can use these to lookup assets from the actual PersonalizedrequiresAuthentication offers. liveConfigOffers { } _id # Add more loyaltyEngineIdrules as needed here (e.g. FirstOrderOnly, LoyaltyBetweenDates, etc) name { } } enRaw # These are config. offers (templates). } # }You should use these } } |
Example reward query
The
_id
is subject to the dataset you are using. For the most part it will bereward-list-singleton
, however, some markets have drifted from this conventionSample query:
Code Block |
---|
query getSanityRewards { RewardList(id: "reward-list-singleton") { rewardCategories { _id label only to display content (images, names, etc). # You can use these to lookup assets from the actual Personalized offers. liveConfigOffers { _id en loyaltyEngineId } rewardsname { ...enRaw on Reward { } } # Sanity} ID of the reward _id # Loyalty Engine ID to be used in the Loyalty Engine query if needed} |
Example reward query
The
_id
is subject to the dataset you are using. For the most part it will bereward-list-singleton
, however, some markets have drifted from this conventionSample query:
Code Block |
---|
query getSanityRewards { RewardList(id: "reward-list-singleton") { rewardCategories { _id loyaltyEngineId label { #en Reward content to display to the user} (name, image, description...) rewards { name { ... on Reward { en # Sanity ID of the reward } _id image { # Loyalty Engine ID to be enused {in the Loyalty Engine query if needed asset { loyaltyEngineId # Reward content to display urlto the user (name, image, description...) name }{ }en } descriptionimage { enRawen { } asset { # Here we add the rules we want to check for the rewardsurl # e.g., LoyaltyPoints (reward points} cost), RewardPrice (reward money price), } # SubtotalSpend (minimum subototal to spend for redeeming} the reward) description { ruleSet { enRaw ... on LoyaltyPoints { } points # Here we add the rules we want to check for }the rewards # e.g.., onLoyaltyPoints RewardPrice(reward {points cost), RewardPrice (reward money price), price # SubtotalSpend (minimum subototal to spend for redeeming the reward) } ruleSet { ... on SubtotalSpend { ... on LoyaltyPoints { minimumSpend points } } # Add more ruleSets as needed here (e..g. FirstOrderOnly,on LoyaltyBetweenDates,RewardPrice etc){ # See theprice screenshot below for how get the list of available ruleSet from GraphQL Playground } } ... on SubtotalSpend { # Benefit of the reward, it can be either a Combo,minimumSpend Item, OfferDiscount #} or Picker # Add more ruleSets as needed here (e.g. FirstOrderOnly, OfferDiscount (It can be a discount for the whole cart orLoyaltyBetweenDates, etc) # See the screenshot below for how get #the alist discountof foravailable aruleSet specificfrom product)GraphQL Playground incentives {} # Benefit ...of onthe OfferDiscountreward, {it can be either a Combo, Item, OfferDiscount discountType # or Picker discountValue # e.g., OfferDiscount (It can be a discount for the whole cart or discountProduct { # a discount for a specific product) ... on Item { incentives { name {... on OfferDiscount { discountType en discountValue } discountProduct { } ... on ComboItem { name { en } } } ... on Combo { } }name { # Reward redemption method available en redemptionMethod # Here we can get} the needed PLUs by looking for a specific vendor } # e.g., Partner constant PLU } vendorConfigs { } partner { } _type # Reward redemption method available constantPlu redemptionMethod # Here we pluTypecan get the needed PLUs by looking for a specific vendor } # e.g., Partner }constant PLU } vendorConfigs { } } } } |
Expand | ||
---|---|---|
| ||
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: |
Option 2) Using a GROQ Query
Replace
staging_bk_de
with the{{stage}}_{{brand}}_{{country}}
that you desire to query.Replace
{{GROQ_QUERY}}
with the desired GROQ query.
Code Block |
---|
partner {
_type
constantPlu
pluType
}
}
}
}
}
}
}
|
Expand | ||
---|---|---|
| ||
To get list of ruleSet available, navigate to https://czqk28jt. |
...
...
Example offers query
Code Block |
---|
https://czqk28jt.api.sanity.io/v2021-10-21/data/query/staging_bk_de?query=*%5B_type+in+%5B%27systemwideOffer%27%2C%27configOffer%27%5D%26%26_id+in*%5B_id%3D%3D%27feature-loyalty-offers-ui-singleton%27%5D%7B%22refs%22%3A%40.liveConfigOffers%5B%5D._ref%2B%40.sortedSystemwideOffers%5B%5D._ref%7D.refs%5B%5D+%5D%7B_id%2C_type%2Cname%2Cincentives%2Cdescription%2ClocalizedImage%2CofferPrice%2CrestaurantAvailabilityValidation%7BvalidationStatus%2ClastValidated%7D%2Crules%2Cdaypart%2CshortCode%2CredemptionMethod%2CloyaltyEngineId%7D |
Example reward query
Code Block |
---|
// Pending... |
Querying the Loyalty Platform
...
GraphQL URL (Playground Available):
...
v1/graphql/dev_bk_aq/default, and search for “ruleSet” in the Schema explorer as seen here: |
Querying the Loyalty Platform
Info |
---|
GraphQL URL (Playground Available): |
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.An example URL for ES (Spain) would be https://euw3-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.An example URL for ES (Spain) would be https://euw3-dev-bk-loyalty-middleware.rbictg.com/graphql
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 theloyaltyEngineId
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 tofalse
. A fixable rule is one that the user can change behavior to make the rule pass, like theminSpend
, the user can add more items to their cart to meet the requirement. An absolute failure likedate-band
will not be returned regardless of what the value of this filter isThis 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 rulesetstoreId
- some incentives can only be redeemed at certain storessubtotalAmount
= some incentives require a minimum spendcartEntries
- some incentives require certain items to be in the cart, this rule uses the sanity id of each rbi productpaymentMethod
- some incentives require certain payment methods to be used
Example offers query
Info |
---|
You should use the
|
Code Block |
---|
query getLoyaltyOffers {
loyaltyOffersV2(loyaltyId: "<USER_LOYALTY_ID>", where: { omitInvalids: false }) {
id
name
type
sanityId
errors {
code
ruleId
}
}
} |
Example offers query response
...
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 theloyaltyEngineId
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 tofalse
. A fixable rule is one that the user can change behavior to make the rule pass, like theminSpend
, the user can add more items to their cart to meet the requirement. An absolute failure likedate-band
will not be returned regardless of what the value of this filter isThis 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 rulesetstoreId
- some incentives can only be redeemed at certain storessubtotalAmount
= some incentives require a minimum spendcartEntries
- some incentives require certain items to be in the cart, this rule uses the sanity id of each rbi productpaymentMethod
- some incentives require certain payment methods to be used
Example offers query
Info |
---|
You should use the
|
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", "idname": "Loyalty Upsize "a4c24211-2d36-47be-bad8-8deea437dbfa"Swap", "nameerrors": "$6[ Let's Get This Bacon Meal", { "type": "GLOBAL", "sanityIdcode": "AAAunsatisfied-AAAcart-AAArequirements", "errors": null },"ruleId": "cart-requirement" { } "id": "fbba4e93-eecd-42b6-a4ed-333856d47bda", ] "name": "Delivery Offer} - $8 The Spicy One] Meal", } } |
Example rewards query
Code Block |
---|
query getLoyaltyRewards { "type": "GLOBAL", loyaltyRewards(loyaltyId: "<USER_LOYALTY_ID>", where: { omitInvalids: false }) { id "sanityId": "BBB-BBB-BBB", name errors { "errors": null code }, ruleId { } } } |
Example rewards query response
Code Block |
---|
{ "iddata": "2a7b2f20-1662-4854-9f4b-e9974200f294",{ "loyaltyRewardsV2": [ "name": "Support 50% Discount Offer",{ "typeid": "PERSONALIZED3b5bdddc-c7c9-42d8-9332-b0c3f36c8e1c", "sanityIdname": "CCC-CCC-CCCValue Powerade Zero", "errors": null }, { "id": "6646b80b728aaedd-dea93234-4bf44eae-81f589af-39055492698735cd5672adcb", "name": "LoyaltyLarge UpsizeDr. SwapPepper", "errors": [null }, { { "codeid": "unsatisfied-cart-requirements480122d7-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 query response
Code Block |
---|
{ "data": { "loyaltyRewardsV2": [ { "id": "3b5bdddc-c7c9-42d8-9332-b0c3f36c8e1c", "name": "Value Powerade Zero", "errors": null }, {} |
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.