Mods IAP Shop Mod - BLKTower/TestWiki GitHub Wiki
The IAP Shop Mod is used to create and manage in-game shop UI for IAP and currency spending.
- Create and manage in-game shop page UI
- Hookup shop items as IAP or custom currency (such as gems)
- Customizable UI for shop items via XML
- Supports portrait and landscape games
Need a feature prioritized or a feature missing from this list? Let @coffee know!
Download the demo maps HERE or play them on the arcade:

- To use this mod, add the
shopregistry module to your project using the latest version^1.0.0.
Shop Mod 2.0.0 has some significant changes that break compatibility with earlier versions. Here are the key changes to look out for when upgrading from an earlier version.
- Initialize the shop ui with shop.NewShopUI() instead of NewLandscapeShopUI or NewPortraitShopUI.
- Initialize the shop sections and default items with ui.shop:Initialize() instead of creating sections and items individually.
- Update shop.InitializeIAP() parameters as table keys.
- Get product items data with item_data:GetItemsData() instead of
item_data.items. - Get product price data with item_data:GetPriceData() instead of
item_data.price. - Replace any direct local calls to OnAnyItemPurchase() with shop.OnAnyItemPurchase.
- Customize item layouts by adding and referencing custom XML and remove any item:UpdateSize() or item:UpdateWidthPreset() calls.
For ease of use, the shop layout (as created by NewShopUI will be referenced as ui.shop throughout this documentation.
The following functions can be used such as:
local shop = require("shop")
local ui = {
root = DCEI.GetUiRoot()
}
-- product definitions
local product_list = {
{
product_id = "com.wildsky.example.iap.gold01",
label = "Handful of Gold",
display = "shop_gold_lv2",
price = { usd = 2.99 },
items = { gold = 1000 }
},
{
product_id = "com.wildsky.example.iap.gold02",
label = "Barrel of Gold",
display = "shop_gold_lv4",
price = { usd = 4.99 },
items = { gold = 5000 }
}
}
-- initialize IAP items
shop.InitializeIAP({
on_purchase_attempt_callback = AttemptAnyItemPurchase,
on_purchase_success_callback = OnAnyItemPurchase,
product_list = product_list,
})
-- creates a new shop UI
ui.shop = shop.NewShopUI({ parent = ui.root })
-- initializes a shop with a "Gold" section and gold items
local shop_data = {
{
name = "gold",
display_name = "Gold",
rows = {
{
"com.wildsky.example.iap.gold01",
"com.wildsky.example.iap.gold02",
}
}
}
}
ui.shop:Initialize(shop_data)
-- show the shop after it's been initialized
ui.shop:Show()Shows the in-game shop UI.
Hides the in-game shop UI. Note that is automatically called when the close button (ui.shop.CloseButton) on the upper right of the shop UI is pressed.
Initializes the shop layout and generates the shop's sections and starting items. The shop_data parameter should be a list of section_data tables that describe each shop section.
In portrait games, shop sections are created in a vertical list separated by banners. In landscape games, shop sections are created as individual shop tabs. Whether a shop is landscape or portrait is automatically determined by the resolution of the game window.
local shop_data = {
{
name = "bundles",
display_name = "Value Packs",
navigation = {
use_display_name = true,
},
rows = {
{
"com.wildsky.test.iap.bundle.starter01"
}
},
custom = {
layout = "Portrait/Shop/Section_StarterPack"
}
},
{
name = "chests",
display_name = "Chests",
navigation = {
use_display_name = true,
},
display_timer = {
label = "Next free chest in:"
},
rows = {
{
"com.wildsky.test.shop.chest01",
"com.wildsky.test.shop.chest02"
}
}
},
}
ui.shop:Initialize(shop_data)-
string
namedefines the section name so it can be referenced elsewhere. -
string
display_namedetermines the text to display on the shop section banner. This is a separate value fromnameso that it can use localized text. -
table
rowsa list ofrowtables that determine the items displayed in each row.-
table
rowa list of product id's to display in the row.
-
table
- (optional) table
itemsa list of product id's to display that can be used instead ofrowswhen a section contains only one row (such as for landscape shops). - (optional) bool
scrollableif true, sets the section to be scrollable horizontally. Only works with landscape shops. Useful for displaying more than 5 items per row. - (optional) table
navigationused in portrait shops to generate a navigation button for the section with additional parameters.- (optional) bool
use_display_nameif true, the navigation button will use the section's display name. - (optional) string
display_nameif set, the navigation button will use this text. Cannot be used withuse_display_name - (optional) string
iconif set, the navigation button will display this icon in addition to any displayed text.
- (optional) bool
- (optional) table
display_timercreates a timer display for the shop section that counts down to the next server day.-
string
labelsets the text of the timer display.
-
string
- (optional) table
customused to define custom attributes of the section.- (optional) string
layoutthe name of the XML layout to use for this shop section. - (optional) string
layout_functhis function will be run using the section layout as self when the section is created.
- (optional) string
Dynamically adds items to a shop section. By default, these items will added to the current row for landscape shops and added to a new row for portrait shops.
Items added to the shop in this way can be set to expire at a given time using the expires_at and expires_in options, which will save the expiry time of the item to player save data. An item's expiration time can be cleared with ClearItemTimerData(). Note that item expiry time is tracked with local OS time and NOT with server time, and thus can used without needing to call InitializeServer().
If an item is given an expiry time and its layout contains a frame with the id TimerFrame, it will attempt to hookup and activate the TimerFrame to display the time remaining. If the timer frame has a TimerValueLabel text frame, this text will display the remaining time using FormatTimeDHM() formatting. If the timer frame has a ClockHand frame, it will become animated. The included UI templates Landscape/Template/TimerFrame and Portrait/Template/TimerFrame are for this expressed purpose.
-- adds an item that will expire in 3 days
local day_length_in_seconds = 86400
local options = { expires_in = day_length_in_seconds * 3 }
ui.shop:AddItemsToSection("bundles", { "com.wildsky.test.iap.bundle.starter01" }, options)-
string
section_namethe section to add the items to. -
table
items_lista list of product id's to add to the section. - (optional) table
options- (optional) number
expires_atif set, the items will be removed from the shop at the time of the given unix timestamp. - (optional) number
expires_inif set, the items will be removed from the shop after this many seconds have passed. - (optional) number
rowif set, the items will be added to the specified row index.
- (optional) number
Dynamically removes items from the shop. Rows with no items will be removed. Sections with no items will be hidden until they have items again.
local items_list = {
"com.wildsky.test.iap.bundle.starter01",
"com.wildsky.test.iap.bundle.starter02",
}
ui.shop:RemoveItems(items_list)Dynamically removes all items from the specified shop section.
ui.shop:RemoveAllItemsFromSection("gold")Dynamically removes ALL items from the shop.
ui.shop:RemoveAllItems()Adds an update function to the shop's update timer. The shop's update timer is a Real Timer that pauses itself when the shop is hidden and resumes itself when the shop is shown. Any update functions added in this way will be executed each time the shop's update timer ticks, using the global tick rate.
This can be useful for creating your own custom timer displays or otherwise want to constantly update something while the shop is open.
local TIME_SPENT_WITH_SHOP_OPEN = 0
local GLOBAL_TICK_RATE = 0.0625
local timer_id = "shop_time_tracking_timer"
function UpdateShopTimeTracking()
TIME_SPENT_WITH_SHOP_OPEN = TIME_SPENT_WITH_SHOP_OPEN + GLOBAL_TICK_RATE
end
-- adds a timer that tracks the total time the shop has been open
ui.shop:AddTimerUpdateFunction(timer_id, UpdateShopTimeTracking)Removes an existing update function from the shop's update timer.
-- stop tracking the total time the shop has been open
local timer_id = "shop_time_tracking_timer"
ui.shop:RemoveTimerUpdateFunction(timer_id)For ease of use, the popup layout (as created by NewPopupUI will be referenced as ui.popup throughout this documentation.
The following functions can be used such as:
local shop = require("shop")
local ui = {
root = DCEI.GetUiRoot()
}
-- product definitions
local product_list = {
{
product_id = "com.wildsky.example.iap.gold01",
label = "Handful of Gold",
display = "shop_gold_lv2",
price = { usd = 2.99 },
items = { gold = 1000 }
},
{
product_id = "com.wildsky.example.iap.gold02",
label = "Barrel of Gold",
display = "shop_gold_lv4",
price = { usd = 4.99 },
items = { gold = 5000 }
}
}
-- initialize IAP items
shop.InitializeIAP({
on_purchase_attempt_callback = AttemptAnyItemPurchase,
on_purchase_success_callback = OnAnyItemPurchase,
product_list = product_list,
})
-- creates a new popup UI
ui.popup = shop.NewPopupUI()
-- shows a popup with the "Handful of Gold" item
local items_list = { "com.wildsky.example.iap.gold01" }
local options = { display_name = "Deal of the Day" }
ui.popup:Show(items_list, options)Shows the UI popup with the given items and display options.
local items_list = { "com.wildsky.example.iap.gold01" }
local options = { display_name = "Deal of the Day" }
ui.popup:Show(items_list, options)-
table
items_lista list of product id's to add to the popup. - (optional) table
options- (optional) string
display_namedetermines the banner text to display on the popup. The default text is "Daily Deals".
- (optional) string
Removes all existing items in the popup and replaces them with the given items.
local items_list = { "com.wildsky.example.iap.gold02" }
ui.popup:Update(items_list)-
table
items_lista list of product id's to update the popup with.
Hides the popup. Note that is automatically called when the close button (ui.popup.CloseButton) on the upper right of the popup UI is pressed.
Adds an update function to the popup's update timer. Functions the same as the shop update timer, but instead pauses when the popup is hidden.
Removes an existing update function from the popup's update timer.
These methods can be used on item layouts in the shop or popup UI. Use shop.GetItemLayout() to get an item's shop layout and shop.GetItemLayoutFromPopup() to get an item's popup layout.
Removes an item, similiar to calling ui.shop.RemoveItems() with a single product id.
local product_id = "com.wildsky.example.iap.gold01"
local item = shop.GetItemLayout(product_id)
item:Remove()Shows a red pip on the top left of item's layout and the item's section banner. All pips are cleared when the shop is closed.
Hide's an item's red pip.
These are the functions exported by the shop mod. They can be used such as:
local shop = require("shop")
local ui = {
root = DCEI.GetUiRoot()
}
-- creates and shows the shop UI
ui.shop = shop.NewShopUI({ parent = ui.root })
-- initialize IAP data
shop.InitializeIAP({
on_purchase_attempt_callback = AttemptAnyItemPurchase,
on_purchase_success_callback = OnAnyItemPurchase,
product_list = GetProductList(),
currency_list = CURRENCIES
})Initializes IAP from IAP data. This requires a product list, functions for purchase attempt and purchase success, and can optionally initialize currencies. This should be called in OnMapStart().
local shop = require("shop")
local ui = {
root = DCEI.GetUiRoot()
}
-- product definitions
local product_list = {
{
product_id = "com.wildsky.example.iap.gold01",
label = "1,000 Gold",
display = "shop_gold_lv2",
price = { usd = 2.99 },
items = { gold = 1000 }
},
{
product_id = "com.wildsky.example.iap.gold02",
label = "5,000 Gold",
display = "shop_gold_lv4",
price = { usd = 4.99 },
items = { gold = 5000 }
}
}
-- currency definitions
local currency_list = {
{
name = "gold",
icon = "icon_item_coin"
}
}
function AttemptAnyItemPurchase(product_id)
-- if your game has products that can be purchased for in-game currency or by watching ads, you should include the purchase attempt logic for those items here
if DCEI.Platform == "WindowsPlayer" then
-- simulate successful purchase when using editor
-- NOTE: shop.OnAnyItemPurchase() calls the on_purchase_success_callback after updating the item's layout and stock data
shop.OnAnyItemPurchase(product_id)
else
-- otherwise continue standard IAP flow
-- NOTE: DCEI.PurchaseIapProduct() takes a few seconds to execute and thus acts similarly to a DCEI.Wait() so avoid subsequent code in the same thread
DCEI.PurchaseIapProduct(product_id)
end
end
function OnAnyItemPurchase(product_id)
local item_data = shop.GetProductData(product_id)
-- deliver purchased items to player
local items = item_data:GetItemsData()
if items and items.gold then
local player_id = 1
DCEI.AddGold(player_id, items.gold)
end
end
-- initialize IAP data
shop.InitializeIAP({
on_purchase_attempt_callback = AttemptAnyItemPurchase,
on_purchase_success_callback = OnAnyItemPurchase,
product_list = product_list,
currency_list = currency_list
})
-- creates a new shop UI
ui.shop = shop.NewShopUI({ parent = ui.root })
-- initializes a shop with a "Gold" section and gold items
local shop_data = {
{
name = "gold",
display_name = "Gold",
rows = {
{
"com.wildsky.example.iap.gold01",
"com.wildsky.example.iap.gold02",
}
}
}
}
ui.shop:Initialize(shop_data)
-- show the shop after it's been initialized
ui.shop:Show()-
function
on_purchase_attempt_callbackthis function is called when an item purchase button is pressed, with the item'sproduct_idas the first argument. See Purchase Attempt Function for more details. -
function
on_purchase_success_callbackthis function is called whenDCEI.PurchaseIapProduct(product_id)succeeds, with the item'sproduct_idas the first argument. See Purchase Success Function for more details. -
table
product_listthe list of IAP initem_dataformat. - (optional) table
currency_listthe list of currencies used in the shop. - (optional) bool
is_game_localizedif set, uses localized text for all built-in text values of the shop instead of the hardcoded english text (ex: DHM formatting). Set this to true if your game is using localization.
Use this function instead of the on_purchase_success_callback defined in InitializeIAP() when you need to directly call your item purchase function, such as when simulating successful purchase in editor play mode or when successfully purchasing items that cost in-game currency or watching ads.
This function will update the item's layout and stock data before calling the locally defined on_purchase_success_callback.
Initializes server time tracking from the given data. This is useful for creating items that refresh stock on a daily basis or rotate depending on the day of the week. In server data you can define a callback that occurs whenever server time is fetched or a new day occurs, along with some additional debugging options.
A new server day occurs at 6AM PST. Note that the editor uses your local OS time as it's not connected to the game server.
Once initalized, the shop will attempt to fetch server time whenever any of the following occurs:
- When ui.shop:Initialize() is called
- When a new server day occurs (at 6AM PST)
- The next time the shop is opened IF the previous fetch failed
You can manually call a shop fetch server attempt with AttemptGetServerTime().
-
function
on_server_time_update_callbackthis function is called whenever server time is successfully fetched, regardless of whether a new day has occurred. This is where you want to add "daily deal" items to the shop, though take care to not add duplicate items. -
function
on_new_server_day_callbackthis function is called when a new server day is detected. This typically occurs when the shop is initialized during the player's first session of the day but can also occur at 6AM PST. This where you want to advance any "daily deal" index and reset the stock limit of items that refresh on a daily basis. - (optional) table
options- (optional) number
update_delaythis debug option will add a delay on shop fetch server time calls to simulate network latency. - (optional) bool
update_failsthis debug option will cause all shop fetch server time calls to fail. - (optional) bool
use_os_timethis debug option will cause shop fetch server time calls to use local OS time. This is only useful for publishing to arcade, where server time currently fails.
- (optional) number
function OnShopFetchNewDay()
-- reset daily ads chest
local product_id = "com.wildsky.test.shop.chest01"
local layout = shop.GetItemLayout(product_id)
shop.ClearItemPurchaseData(product_id)
layout:Update()
ui.shop.sections["chests"].timer_display:Reset()
end
-- initialize server data
shop.InitializeServer({
on_new_server_day_callback = OnShopFetchNewDay
})Manually attempts to fetch server time for shop server time tracking using the data given in InitializeServer(). If successful this will call the on_server_time_update_callback. If successful and a new day has occurred since the last server time check, this will call the on_new_server_day_callback.
Initializes and returns the shop UI layout. If no parent frame is specified by options.parent, the shop will be created in ui.root. By default, whether a shop is landscape or portrait is automatically determined by the resolution of the game window.
The shop UI must be initialized before any shop methods can be used.
- (optional) table
options- (optional) tranform
parentsets the parent frame for the shop UI. - (optional) bool
is_landscapeif true, the shop will be created using landscape mode regardless of game resolution. If false, the shop will be created using portrait mode regardless of game resolution.
- (optional) tranform